#How to maintain WebSocket client connections in Durable Object?

4 messages · Page 1 of 1 (latest)

sage oxide
#
  1. Would it be the correct understanding that in order to use the Durable Objects Hibernation API, I need to extends the DurableObject class?
  2. If so, how can I delete a certain clientId on close? In the examples, it doesn't extend the DurableObject class so the handleWebsocketSession method is able to set listeners to delete clientConnectionIds. However, when extending the durableObject class, the webSocketClose and webSocketError methods handle the closing of the client, so I'm not able to refer to the same clientId anymore..

See code below:

#

When extending the DurableObject class

import { Environment } from "@/src/types";
import { DurableObject } from "cloudflare:workers";

interface Client {
  websocket: WebSocket;
  id: string;
}

export class WebSocketHibernationServer extends DurableObject {
  clients: Map<string, Client>;

  constructor(ctx: DurableObjectState, env: Environment) {
    super(ctx, env);
    this.clients = new Map();
  }

  async fetch(request: Request): Promise<Response> {
    let pair = new WebSocketPair();
    const [client, server] = Object.values(pair);
    this.ctx.acceptWebSocket(server);
    await this.handleWebSocketSession(server);
    return new Response(null, { status: 101, webSocket: client });
  }

  async handleWebSocketSession(webSocket: WebSocket) {
    // Create our session and add it to the users map.
    const clientId = crypto.randomUUID();
    this.clients.set(clientId, {
      id: clientId,
      websocket: webSocket,
  });

    // I previously added event listeners here but they don't get triggered
    // Previously I was able to delete the clientId from the map on close here
  }

  async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string) {
    this.broadcast(
      `[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`
    );
  }

  async webSocketClose(ws: WebSocket, code: number, reason: string) {
    ws.close(code, reason);
  }

  broadcast(message: string) {
    // Iterate over all the sessions sending them messages.
    this.clients.forEach((client, key) => {
    try {
      client.websocket.send(message);
    } catch (err) {
      this.clients.delete(key);
    }
  });
}
#

Previously, I was able to do...

async handleWebSocketSession(webSocket: WebSocket) {
 // ...
  let closeOrErrorHandler = () => {
    console.log("client disconnected", clientId);
    this.clients.delete(clientId);
  };
  webSocket.addEventListener("close", closeOrErrorHandler);
  webSocket.addEventListener("error", closeOrErrorHandler);
}
#

Or fundamentally, how can I restructure this whole thing so that I'm able to broadcast to all clients but delete the clients when they disconnect? I'm able to do it when NOT extending the "DurableObject" class, but then if I don't extend it, I don't think I'm able to use the Hibernation API via this.ctx.acceptWebsocket(server)