Skip to content

Commit 77818c1

Browse files
authored
refactor: scope broadcasts to subscribed WebSocket clients (#76)
Narrow broadcast audience to clients that have completed the subscribe handshake. Clients receive full state sync on subscribe, so no data is missed.
1 parent 520a14a commit 77818c1

File tree

2 files changed

+41
-2
lines changed

2 files changed

+41
-2
lines changed

packages/control-plane/src/session/durable-object.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,10 +1316,10 @@ export class SessionDO extends DurableObject<Env> {
13161316
}
13171317

13181318
/**
1319-
* Broadcast message to all connected clients.
1319+
* Broadcast message to all authenticated clients.
13201320
*/
13211321
private broadcast(message: ServerMessage): void {
1322-
this.wsManager.forEachClientSocket("all_clients", (ws) => {
1322+
this.wsManager.forEachClientSocket("authenticated_only", (ws) => {
13231323
this.wsManager.send(ws, message);
13241324
});
13251325
}

packages/control-plane/src/session/websocket-manager.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,45 @@ describe("SessionWebSocketManagerImpl", () => {
599599
expect(called).toHaveLength(0);
600600
});
601601

602+
it("broadcast pattern delivers to authenticated clients and skips unauthenticated", () => {
603+
const { manager, sockets, mockRepo } = createManager();
604+
605+
// Authenticated client (in-memory)
606+
const authedWs = createFakeWebSocket();
607+
sockets.set(authedWs, ["wsid:ws-authed"]);
608+
manager.setClient(authedWs, createClientInfo({ ws: authedWs }));
609+
610+
// Post-hibernation client (persisted mapping only, no in-memory ClientInfo)
611+
const hibernatedWs = createFakeWebSocket();
612+
sockets.set(hibernatedWs, ["wsid:ws-hibernated"]);
613+
mockRepo.addMapping("ws-hibernated", {
614+
participant_id: "p-2",
615+
client_id: "c-2",
616+
user_id: "u-2",
617+
github_name: null,
618+
github_login: null,
619+
});
620+
621+
// Unauthenticated client (connected but never subscribed)
622+
const unauthWs = createFakeWebSocket();
623+
sockets.set(unauthWs, ["wsid:ws-unauth"]);
624+
625+
// Sandbox (should never receive)
626+
const sandboxWs = createFakeWebSocket();
627+
sockets.set(sandboxWs, ["sandbox", "sid:sb-1"]);
628+
629+
// Simulate the DO's broadcast() pattern
630+
const message = JSON.stringify({ type: "sandbox_status", status: "ready" });
631+
manager.forEachClientSocket("authenticated_only", (ws) => {
632+
manager.send(ws, message);
633+
});
634+
635+
expect(authedWs.send).toHaveBeenCalledWith(message);
636+
expect(hibernatedWs.send).toHaveBeenCalledWith(message);
637+
expect(unauthWs.send).not.toHaveBeenCalled();
638+
expect(sandboxWs.send).not.toHaveBeenCalled();
639+
});
640+
602641
it("never calls fn for sandbox sockets regardless of mode", () => {
603642
const { manager, sockets } = createManager();
604643
const sandboxWs = createFakeWebSocket();

0 commit comments

Comments
 (0)