From 992c9380c34b9a67c03dd503c26d008836f2899b Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Mon, 15 Feb 2021 01:08:27 +0100 Subject: [PATCH] docs(examples): 2nd part of the "private messaging" example See also: https://socket.io/get-started/private-messaging-part-2/ --- examples/private-messaging/server/index.js | 68 ++++++++++++++++--- .../private-messaging/server/sessionStore.js | 28 ++++++++ examples/private-messaging/src/App.vue | 17 +++++ .../private-messaging/src/components/Chat.vue | 27 ++++++-- 4 files changed, 124 insertions(+), 16 deletions(-) create mode 100644 examples/private-messaging/server/sessionStore.js diff --git a/examples/private-messaging/server/index.js b/examples/private-messaging/server/index.js index adf99facef..cf067238cb 100644 --- a/examples/private-messaging/server/index.js +++ b/examples/private-messaging/server/index.js @@ -5,43 +5,91 @@ const io = require("socket.io")(httpServer, { }, }); +const crypto = require("crypto"); +const randomId = () => crypto.randomBytes(8).toString("hex"); + +const { InMemorySessionStore } = require("./sessionStore"); +const sessionStore = new InMemorySessionStore(); + io.use((socket, next) => { + const sessionID = socket.handshake.auth.sessionID; + if (sessionID) { + const session = sessionStore.findSession(sessionID); + if (session) { + socket.sessionID = sessionID; + socket.userID = session.userID; + socket.username = session.username; + return next(); + } + } const username = socket.handshake.auth.username; if (!username) { return next(new Error("invalid username")); } + socket.sessionID = randomId(); + socket.userID = randomId(); socket.username = username; next(); }); io.on("connection", (socket) => { + // persist session + sessionStore.saveSession(socket.sessionID, { + userID: socket.userID, + username: socket.username, + connected: true, + }); + + // emit session details + socket.emit("session", { + sessionID: socket.sessionID, + userID: socket.userID, + }); + + // join the "userID" room + socket.join(socket.userID); + // fetch existing users const users = []; - for (let [id, socket] of io.of("/").sockets) { + sessionStore.findAllSessions().forEach((session) => { users.push({ - userID: id, - username: socket.username, + userID: session.userID, + username: session.username, + connected: session.connected, }); - } + }); socket.emit("users", users); // notify existing users socket.broadcast.emit("user connected", { - userID: socket.id, + userID: socket.userID, username: socket.username, + connected: true, }); - // forward the private message to the right recipient + // forward the private message to the right recipient (and to other tabs of the sender) socket.on("private message", ({ content, to }) => { - socket.to(to).emit("private message", { + socket.to(to).to(socket.userID).emit("private message", { content, - from: socket.id, + from: socket.userID, + to, }); }); // notify users upon disconnection - socket.on("disconnect", () => { - socket.broadcast.emit("user disconnected", socket.id); + socket.on("disconnect", async () => { + const matchingSockets = await io.in(socket.userID).allSockets(); + const isDisconnected = matchingSockets.size === 0; + if (isDisconnected) { + // notify other users + socket.broadcast.emit("user disconnected", socket.userID); + // update the connection status of the session + sessionStore.saveSession(socket.sessionID, { + userID: socket.userID, + username: socket.username, + connected: false, + }); + } }); }); diff --git a/examples/private-messaging/server/sessionStore.js b/examples/private-messaging/server/sessionStore.js new file mode 100644 index 0000000000..42dd294d14 --- /dev/null +++ b/examples/private-messaging/server/sessionStore.js @@ -0,0 +1,28 @@ +/* abstract */ class SessionStore { + findSession(id) {} + saveSession(id, session) {} + findAllSessions() {} +} + +class InMemorySessionStore extends SessionStore { + constructor() { + super(); + this.sessions = new Map(); + } + + findSession(id) { + return this.sessions.get(id); + } + + saveSession(id, session) { + this.sessions.set(id, session); + } + + findAllSessions() { + return [...this.sessions.values()]; + } +} + +module.exports = { + InMemorySessionStore +}; diff --git a/examples/private-messaging/src/App.vue b/examples/private-messaging/src/App.vue index feb6d51ead..3d250a9549 100644 --- a/examples/private-messaging/src/App.vue +++ b/examples/private-messaging/src/App.vue @@ -32,6 +32,23 @@ export default { }, }, created() { + const sessionID = localStorage.getItem("sessionID"); + + if (sessionID) { + this.usernameAlreadySelected = true; + socket.auth = { sessionID }; + socket.connect(); + } + + socket.on("session", ({ sessionID, userID }) => { + // attach the session ID to the next reconnection attempts + socket.auth = { sessionID }; + // store it in the localStorage + localStorage.setItem("sessionID", sessionID); + // save the ID of the user + socket.userID = userID; + }); + socket.on("connect_error", (err) => { if (err.message === "invalid username") { this.usernameAlreadySelected = false; diff --git a/examples/private-messaging/src/components/Chat.vue b/examples/private-messaging/src/components/Chat.vue index 1e2d61a771..ac1dd09cf9 100644 --- a/examples/private-messaging/src/components/Chat.vue +++ b/examples/private-messaging/src/components/Chat.vue @@ -68,18 +68,25 @@ export default { }); const initReactiveProperties = (user) => { - user.connected = true; user.messages = []; user.hasNewMessages = false; }; socket.on("users", (users) => { users.forEach((user) => { - user.self = user.userID === socket.id; + for (let i = 0; i < this.users.length; i++) { + const existingUser = this.users[i]; + if (existingUser.userID === user.userID) { + existingUser.connected = user.connected; + return; + } + } + user.self = user.userID === socket.userID; initReactiveProperties(user); + this.users.push(user); }); // put the current user first, and sort by username - this.users = users.sort((a, b) => { + this.users.sort((a, b) => { if (a.self) return -1; if (b.self) return 1; if (a.username < b.username) return -1; @@ -88,6 +95,13 @@ export default { }); socket.on("user connected", (user) => { + for (let i = 0; i < this.users.length; i++) { + const existingUser = this.users[i]; + if (existingUser.userID === user.userID) { + existingUser.connected = true; + return; + } + } initReactiveProperties(user); this.users.push(user); }); @@ -102,13 +116,14 @@ export default { } }); - socket.on("private message", ({ content, from }) => { + socket.on("private message", ({ content, from, to }) => { for (let i = 0; i < this.users.length; i++) { const user = this.users[i]; - if (user.userID === from) { + const fromSelf = socket.userID === from; + if (user.userID === (fromSelf ? to : from)) { user.messages.push({ content, - fromSelf: false, + fromSelf, }); if (user !== this.selectedUser) { user.hasNewMessages = true;