Start with strongly typed Socket-Client
/** WS Client type */
type Client = {
id: string;
name: string;
socket: WebSocket;
};
Next create a clients collection - I use a Map
/** collection of WebSocket clients, maped by id */
const clients: Map<string, Client> = new Map();
We then export a connectClient function
/** Registers a new WebSocket client
* handles all socket events and messaging
* @param { WebSocket } socket - WebSocket connection
* @param { string | null } id - request.headers.get('sec-websocket-key')
*/
export const connectClient = (socket: WebSocket, id: string | null) => {
const thisClient = id ? id : "0";
// create a new client object
const client: Client = {
id: thisClient,
name: "",
socket: socket,
};
// register this new client in our collection
clients.set(client.id, client);
// handle any socket errors
socket.onerror = (e) => {
const msg = (e instanceof ErrorEvent) ? e.message : e.type;
console.log("socket client error:", msg);
};
// handle the socket close event
socket.onclose = (ev: CloseEvent) => {
const { code, reason, wasClean } = ev;
console.log(
`Client: ${thisClient} was closed! code: ${code}, reason: ${reason} wasClean? ${wasClean}`,
);
clients.delete(thisClient);
};
// handle all socket message events
socket.onmessage = (message) => {
console.log(`WS message from: ${thisClient}, payload: ${message.data}`);
// Ensure that all message are passed through and delivered,
// even if the server has no idea what they are.
// Relay this message to all `other` client(s)
for (const client of clients.values()) {
if (client.socket.readyState === 1) { // 1 == open
if (client.id !== thisClient) { // skip self
client.socket.send(message.data);
}
}
}
};
};
In the server, we handle all WebSocket upgrades
by calling
our connectClient
function above.
We pass both the new socket object and the clients id to this function.
import { connectClient } from './wsCients.ts'
const port = 9093
// serve HTTP requests on incoming connections to handler
Deno.serve({port}, async (request: Request): Promise<Response> => {
// handle each new http WebSocket upgrade request
try {
console.info(request)
const upgrade = request.headers.get("upgrade")?.toLowerCase() || "";
// is this a websocket request?
if (upgrade === "websocket") {
const { socket, response } = Deno.upgradeWebSocket(request);
connectClient(socket, request.headers.get('sec-websocket-key'));
return response
}
const errMsg = `Error: Request was not a valid WebSocket request! (405)`
console.error(errMsg)
return await Promise.resolve(new Response(errMsg, { status: 405 }))
} catch (err: unknown) {
const errMsg = `Internal server error!
${JSON.stringify(err)}`
console.error(errMsg)
return await Promise.resolve(new Response(errMsg, { status: 500 }))
}
})