From fabc70c036203b04679fc5b659fc12e8a1a4a425 Mon Sep 17 00:00:00 2001 From: barnabasmolnar Date: Fri, 4 Aug 2023 02:54:22 +0200 Subject: [PATCH 01/10] [WIP] Follow mode POC. --- src/index.ts | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/index.ts b/src/index.ts index 65cc345..7424a8c 100755 --- a/src/index.ts +++ b/src/index.ts @@ -78,6 +78,43 @@ try { }, ); + socket.on( + "on-user-follow", + async (payload: { + userToFollow: { + clientId: string; + username: string; + }; + action: "subscribe" | "unsubscribe"; + }) => { + const roomID = `follow_${payload.userToFollow.clientId}`; + switch (payload.action) { + case "subscribe": + console.log("subscribe"); + await socket.join(roomID); + const sockets = await io.in(roomID).fetchSockets(); + console.log(sockets.map((s) => s.id)); + + if (sockets.length === 1) { + io.to(payload.userToFollow.clientId).emit("broadcast-follow"); + } + + break; + case "unsubscribe": + console.log("unsubscribe"); + await socket.leave(roomID); + const _sockets = await io.in(roomID).fetchSockets(); + console.log(_sockets.map((s) => s.id)); + + if (_sockets.length === 0) { + io.to(payload.userToFollow.clientId).emit("broadcast-unfollow"); + } + + break; + } + }, + ); + socket.on("disconnecting", async () => { socketDebug(`${socket.id} has disconnected`); for (const roomID in socket.rooms) { From 8df0a73034b258f9a3e78201c85dc97d9c620fb2 Mon Sep 17 00:00:00 2001 From: barnabasmolnar Date: Fri, 4 Aug 2023 17:43:09 +0200 Subject: [PATCH 02/10] cleanup --- src/index.ts | 66 ++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7424a8c..7078667 100755 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,12 @@ import express from "express"; import http from "http"; import { Server as SocketIO } from "socket.io"; +type UserToFollow = { clientId: string; username: string }; +type OnUserFollowedPayload = { + userToFollow: UserToFollow; + action: "follow" | "unfollow"; +}; + const serverDebug = debug("server"); const ioDebug = debug("io"); const socketDebug = debug("socket"); @@ -78,43 +84,31 @@ try { }, ); - socket.on( - "on-user-follow", - async (payload: { - userToFollow: { - clientId: string; - username: string; - }; - action: "subscribe" | "unsubscribe"; - }) => { - const roomID = `follow_${payload.userToFollow.clientId}`; - switch (payload.action) { - case "subscribe": - console.log("subscribe"); - await socket.join(roomID); - const sockets = await io.in(roomID).fetchSockets(); - console.log(sockets.map((s) => s.id)); - - if (sockets.length === 1) { - io.to(payload.userToFollow.clientId).emit("broadcast-follow"); - } - - break; - case "unsubscribe": - console.log("unsubscribe"); - await socket.leave(roomID); - const _sockets = await io.in(roomID).fetchSockets(); - console.log(_sockets.map((s) => s.id)); - - if (_sockets.length === 0) { - io.to(payload.userToFollow.clientId).emit("broadcast-unfollow"); - } - - break; - } - }, - ); + socket.on("on-user-follow", async (payload: OnUserFollowedPayload) => { + const roomID = `follow_${payload.userToFollow.clientId}`; + switch (payload.action) { + case "follow": + await socket.join(roomID); + const sockets = await io.in(roomID).fetchSockets(); + + if (sockets.length === 1) { + io.to(payload.userToFollow.clientId).emit("broadcast-follow"); + } + + break; + case "unfollow": + await socket.leave(roomID); + const _sockets = await io.in(roomID).fetchSockets(); + + if (_sockets.length === 0) { + io.to(payload.userToFollow.clientId).emit("broadcast-unfollow"); + } + + break; + } + }); + // TODO follow-mode unfollow on disconnect? socket.on("disconnecting", async () => { socketDebug(`${socket.id} has disconnected`); for (const roomID in socket.rooms) { From 0718ec5447d064432f46c6e4c48a2017c1421ec9 Mon Sep 17 00:00:00 2001 From: barnabasmolnar Date: Mon, 7 Aug 2023 00:53:12 +0200 Subject: [PATCH 03/10] Clean up follow on disconnecting. --- src/index.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7078667..375b444 100755 --- a/src/index.ts +++ b/src/index.ts @@ -108,7 +108,6 @@ try { } }); - // TODO follow-mode unfollow on disconnect? socket.on("disconnecting", async () => { socketDebug(`${socket.id} has disconnected`); for (const roomID in socket.rooms) { @@ -116,12 +115,19 @@ try { (_socket) => _socket.id !== socket.id, ); - if (otherClients.length > 0) { + const isFollowRoom = roomID.startsWith("follow_"); + + if (!isFollowRoom && otherClients.length > 0) { socket.broadcast.to(roomID).emit( "room-user-change", otherClients.map((socket) => socket.id), ); } + + if (isFollowRoom && otherClients.length === 0) { + const clientId = roomID.replace("follow_", ""); + io.to(clientId).emit("broadcast-unfollow"); + } } }); From 9517936277163edc0b5e0d5520c90f3674045386 Mon Sep 17 00:00:00 2001 From: barnabasmolnar Date: Fri, 11 Aug 2023 01:09:12 +0200 Subject: [PATCH 04/10] Fix issue with disconnecting listener. --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 375b444..138a468 100755 --- a/src/index.ts +++ b/src/index.ts @@ -110,7 +110,7 @@ try { socket.on("disconnecting", async () => { socketDebug(`${socket.id} has disconnected`); - for (const roomID in socket.rooms) { + for (const roomID of Array.from(socket.rooms)) { const otherClients = (await io.in(roomID).fetchSockets()).filter( (_socket) => _socket.id !== socket.id, ); From 01e78bf2a0791cb2cdc58638233e1f489c070282 Mon Sep 17 00:00:00 2001 From: barnabasmolnar Date: Tue, 12 Dec 2023 03:14:01 +0100 Subject: [PATCH 05/10] Made follow mode more robust. --- src/index.ts | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 138a468..c5b63ef 100755 --- a/src/index.ts +++ b/src/index.ts @@ -46,6 +46,14 @@ try { allowEIO3: true, }); + // we want to keep track of the last position of the followed user's scene + // bounds so that we can send it to the new follower without having to + // make an interaction on the scene + const withLatestSceneBounds = new Map< + string, + { data: ArrayBuffer; iv: Uint8Array } + >(); + io.on("connection", (socket) => { ioDebug("connection established!"); io.to(`${socket.id}`).emit("init-room"); @@ -77,6 +85,9 @@ try { socket.on( "server-volatile-broadcast", (roomID: string, encryptedData: ArrayBuffer, iv: Uint8Array) => { + if (roomID.startsWith("follow_")) { + withLatestSceneBounds.set(roomID, { data: encryptedData, iv }); + } socketDebug(`${socket.id} sends volatile update to ${roomID}`); socket.volatile.broadcast .to(roomID) @@ -89,19 +100,43 @@ try { switch (payload.action) { case "follow": await socket.join(roomID); - const sockets = await io.in(roomID).fetchSockets(); - if (sockets.length === 1) { - io.to(payload.userToFollow.clientId).emit("broadcast-follow"); + // Immediately send the latest scene bounds to the new follower + // without having to wait for a scene interaction + const latestSceneBounds = withLatestSceneBounds.get(roomID); + if (latestSceneBounds) { + io.to(socket.id).emit( + "client-broadcast", + latestSceneBounds.data, + latestSceneBounds.iv, + ); } + const sockets = await io.in(roomID).fetchSockets(); + const followedBy = sockets.map((socket) => socket.id); + + // Notify the user to follow that someone has followed them + // More precisely, send the list of users that are following them + // so that they can make decisions based on that + io.to(payload.userToFollow.clientId).emit( + "follow-room-user-change", + followedBy, + ); + break; case "unfollow": await socket.leave(roomID); const _sockets = await io.in(roomID).fetchSockets(); + const _followedBy = _sockets.map((socket) => socket.id); + + io.to(payload.userToFollow.clientId).emit( + "follow-room-user-change", + _followedBy, + ); + // cleanup if (_sockets.length === 0) { - io.to(payload.userToFollow.clientId).emit("broadcast-unfollow"); + withLatestSceneBounds.delete(roomID); } break; From 511f7841c9ccb6bbd341ec1e021aa11fb3e2c750 Mon Sep 17 00:00:00 2001 From: barnabasmolnar Date: Wed, 13 Dec 2023 02:35:21 +0100 Subject: [PATCH 06/10] removed scene bounds cache --- src/index.ts | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/src/index.ts b/src/index.ts index c5b63ef..635caec 100755 --- a/src/index.ts +++ b/src/index.ts @@ -46,14 +46,6 @@ try { allowEIO3: true, }); - // we want to keep track of the last position of the followed user's scene - // bounds so that we can send it to the new follower without having to - // make an interaction on the scene - const withLatestSceneBounds = new Map< - string, - { data: ArrayBuffer; iv: Uint8Array } - >(); - io.on("connection", (socket) => { ioDebug("connection established!"); io.to(`${socket.id}`).emit("init-room"); @@ -85,9 +77,6 @@ try { socket.on( "server-volatile-broadcast", (roomID: string, encryptedData: ArrayBuffer, iv: Uint8Array) => { - if (roomID.startsWith("follow_")) { - withLatestSceneBounds.set(roomID, { data: encryptedData, iv }); - } socketDebug(`${socket.id} sends volatile update to ${roomID}`); socket.volatile.broadcast .to(roomID) @@ -101,17 +90,6 @@ try { case "follow": await socket.join(roomID); - // Immediately send the latest scene bounds to the new follower - // without having to wait for a scene interaction - const latestSceneBounds = withLatestSceneBounds.get(roomID); - if (latestSceneBounds) { - io.to(socket.id).emit( - "client-broadcast", - latestSceneBounds.data, - latestSceneBounds.iv, - ); - } - const sockets = await io.in(roomID).fetchSockets(); const followedBy = sockets.map((socket) => socket.id); @@ -134,11 +112,6 @@ try { _followedBy, ); - // cleanup - if (_sockets.length === 0) { - withLatestSceneBounds.delete(roomID); - } - break; } }); From 7669f11c345ed86f903118dcacfdbf5edce84e02 Mon Sep 17 00:00:00 2001 From: barnabasmolnar Date: Wed, 13 Dec 2023 20:56:37 +0100 Subject: [PATCH 07/10] clientId => socketId --- src/index.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 635caec..101a797 100755 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,11 @@ import express from "express"; import http from "http"; import { Server as SocketIO } from "socket.io"; -type UserToFollow = { clientId: string; username: string }; +type UserToFollow = { + socketId: string; + userId: string | null; + username: string; +}; type OnUserFollowedPayload = { userToFollow: UserToFollow; action: "follow" | "unfollow"; @@ -85,7 +89,7 @@ try { ); socket.on("on-user-follow", async (payload: OnUserFollowedPayload) => { - const roomID = `follow_${payload.userToFollow.clientId}`; + const roomID = `follow_${payload.userToFollow.socketId}`; switch (payload.action) { case "follow": await socket.join(roomID); @@ -96,7 +100,7 @@ try { // Notify the user to follow that someone has followed them // More precisely, send the list of users that are following them // so that they can make decisions based on that - io.to(payload.userToFollow.clientId).emit( + io.to(payload.userToFollow.socketId).emit( "follow-room-user-change", followedBy, ); @@ -107,7 +111,7 @@ try { const _sockets = await io.in(roomID).fetchSockets(); const _followedBy = _sockets.map((socket) => socket.id); - io.to(payload.userToFollow.clientId).emit( + io.to(payload.userToFollow.socketId).emit( "follow-room-user-change", _followedBy, ); @@ -133,8 +137,8 @@ try { } if (isFollowRoom && otherClients.length === 0) { - const clientId = roomID.replace("follow_", ""); - io.to(clientId).emit("broadcast-unfollow"); + const socketId = roomID.replace("follow_", ""); + io.to(socketId).emit("broadcast-unfollow"); } } }); From 3b9bf79b2083e2023ba7a3e0ecd25fd757983357 Mon Sep 17 00:00:00 2001 From: barnabasmolnar Date: Wed, 13 Dec 2023 21:47:56 +0100 Subject: [PATCH 08/10] remove unnecessary userId from type --- src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 101a797..e1a19f4 100755 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,6 @@ import { Server as SocketIO } from "socket.io"; type UserToFollow = { socketId: string; - userId: string | null; username: string; }; type OnUserFollowedPayload = { From 082050366d3a6b212ea7341c843da917d306b38e Mon Sep 17 00:00:00 2001 From: dwelle <5153846+dwelle@users.noreply.github.com> Date: Thu, 14 Dec 2023 15:34:51 +0100 Subject: [PATCH 09/10] feat: rename events --- src/index.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/index.ts b/src/index.ts index e1a19f4..44b63e0 100755 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,7 @@ type UserToFollow = { }; type OnUserFollowedPayload = { userToFollow: UserToFollow; - action: "follow" | "unfollow"; + action: "FOLLOW" | "UNFOLLOW"; }; const serverDebug = debug("server"); @@ -87,35 +87,36 @@ try { }, ); - socket.on("on-user-follow", async (payload: OnUserFollowedPayload) => { + socket.on("user-follow", async (payload: OnUserFollowedPayload) => { const roomID = `follow_${payload.userToFollow.socketId}`; + switch (payload.action) { - case "follow": + case "FOLLOW": { await socket.join(roomID); const sockets = await io.in(roomID).fetchSockets(); const followedBy = sockets.map((socket) => socket.id); - // Notify the user to follow that someone has followed them - // More precisely, send the list of users that are following them - // so that they can make decisions based on that io.to(payload.userToFollow.socketId).emit( - "follow-room-user-change", + "user-follow-room-change", followedBy, ); break; - case "unfollow": + } + case "UNFOLLOW": { await socket.leave(roomID); - const _sockets = await io.in(roomID).fetchSockets(); - const _followedBy = _sockets.map((socket) => socket.id); + + const sockets = await io.in(roomID).fetchSockets(); + const followedBy = sockets.map((socket) => socket.id); io.to(payload.userToFollow.socketId).emit( - "follow-room-user-change", - _followedBy, + "user-follow-room-change", + followedBy, ); break; + } } }); From 32fde3aec1310667c00a8f6406afc2b3a57fdf9b Mon Sep 17 00:00:00 2001 From: dwelle <5153846+dwelle@users.noreply.github.com> Date: Fri, 15 Dec 2023 15:09:47 +0100 Subject: [PATCH 10/10] feat: change follow room naming scheme --- src/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 44b63e0..03bf0c0 100755 --- a/src/index.ts +++ b/src/index.ts @@ -88,7 +88,7 @@ try { ); socket.on("user-follow", async (payload: OnUserFollowedPayload) => { - const roomID = `follow_${payload.userToFollow.socketId}`; + const roomID = `follow@${payload.userToFollow.socketId}`; switch (payload.action) { case "FOLLOW": { @@ -127,7 +127,7 @@ try { (_socket) => _socket.id !== socket.id, ); - const isFollowRoom = roomID.startsWith("follow_"); + const isFollowRoom = roomID.startsWith("follow@"); if (!isFollowRoom && otherClients.length > 0) { socket.broadcast.to(roomID).emit( @@ -137,7 +137,7 @@ try { } if (isFollowRoom && otherClients.length === 0) { - const socketId = roomID.replace("follow_", ""); + const socketId = roomID.replace("follow@", ""); io.to(socketId).emit("broadcast-unfollow"); } }