Skip to content

Instantly share code, notes, and snippets.

@nhrones
Created January 19, 2024 05:00
Show Gist options
  • Save nhrones/d37855a844e114b801fac545ef1c5c89 to your computer and use it in GitHub Desktop.
Save nhrones/d37855a844e114b801fac545ef1c5c89 to your computer and use it in GitHub Desktop.
WebSocket Clients Collection

wsClient.ts module

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);
        }
      }
    }
  };
};

server.ts

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 }))
   }
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment