Open JC3 opened 7 years ago
express-ws
is a fairly low-level implementation - it really doesn't do anything beyond glueing together Express and ws
, so you'd be responsible for managing connection-related metadata yourself.
The exact way to implement this depends on your requirements, really. For example, let's say that you're building a chat application - the following would apply:
Given those requirements, you might build a user-store.js
that looks something like this:
'use strict';
module.exports = function createUserStore() {
let users = {};
return {
addConnection: function(connection, user) {
if (users[user.id] == null) {
users[user.id] = {
username: user.username,
sockets: []
}
}
users[user.id].sockets.push(connection);
},
removeConnection: function(connection, user) {
if (users[user.id] != null) {
let userSocketList = users[user.id].sockets;
let socketIndex = userSocketList.indexOf(connection);
if (socketIndex !== -1) {
userSocketList.splice(socketIndex, 1);
}
}
},
broadcastMessage: function(connection, user, message) {
if (users[user.id] != null) {
function sendMessageToSocket(socket) {
socket.send(JSON.stringify({
type: "message",
message: message, // We can access this because of lexical scope
source: user.id
}));
}
/* Broadcast to other users first. */
Object.keys(users).forEach((userId) => {
if (userId !== user.id) {
let user = users[userId];
user.sockets.forEach((socket) => {
sendMessageToSocket(socket);
});
}
});
/* Then broadcast to other connections of the sending user - but
* not the connection that the message originally came from! */
users[user.id].sockets.forEach((socket) => {
sendMessageToSocket(socket);
});
} else {
throw new Error("Tried to send a message from a non-existent user");
}
}
}
}
... and then use it from your app something like this:
/* .... */
const createUserStore = require("./user-store");
let userStore = createUserStore();
/* ... */
app.ws("/endpoint", (ws, req) => {
userStore.addConnection(ws, req.user); // Let's assume `req.user` was set by middleware or such
ws.on("message", (message) => {
let parsed = JSON.parse(message);
userStore.broadcastMessage(ws, req.user, parsed.message);
});
ws.on("close", (code, reason) => {
userStore.removeConnection(ws, req.user);
});
});
(Note: This code is untested, so there may be some small bugs... it's just for illustrative purposes, to demonstrate building a project-specific abstraction.)
Of course your requirements will probably be different and you might need an entirely different kind of abstraction, but you'll generally want to take the approach of "build a separate abstraction and make it keep track of sockets in the appropriate manner".
So, if I need to maintain per-client state, should I just... add things to the websocket? Like:
Or is there some "real" way to do this that I am missing?