Add awareness broadcast to session

This commit is contained in:
Jared Miller 2026-01-27 20:36:35 -05:00
parent cd4b1def5b
commit 93bb462ffa
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
3 changed files with 46 additions and 3 deletions

View file

@ -6,10 +6,17 @@ export type ClientMessage =
| { type: "update"; data: number[] } // yjs update as byte array | { type: "update"; data: number[] } // yjs update as byte array
| { type: "awareness"; data: number[] }; | { type: "awareness"; data: number[] };
export type AwarenessState = {
clientId: number;
cursor?: { line: number; col: number };
selection?: { startLine: number; startCol: number; endLine: number; endCol: number };
name?: string;
};
export type ServerMessage = export type ServerMessage =
| { type: "sync"; data: number[] } // full yjs state | { type: "sync"; data: number[] } // full yjs state
| { type: "update"; data: number[] } | { type: "update"; data: number[] }
| { type: "awareness"; data: number[] } | { type: "awareness"; data: AwarenessState }
| { type: "peers"; count: number } | { type: "peers"; count: number }
| { type: "error"; message: string }; | { type: "error"; message: string };

View file

@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"; import { describe, expect, test } from "bun:test";
import * as Y from "yjs"; import * as Y from "yjs";
import { getOrCreateSession, Session, type WsData } from "./session"; import { getOrCreateSession, Session, type Client, type WsData } from "./session";
describe("Session", () => { describe("Session", () => {
test("creates yjs doc on init", () => { test("creates yjs doc on init", () => {
@ -57,3 +57,26 @@ describe("getOrCreateSession", () => {
expect(s1).not.toBe(s2); expect(s1).not.toBe(s2);
}); });
}); });
describe("awareness", () => {
test("broadcasts awareness to other clients", () => {
const session = new Session("test-room");
const sent1: unknown[] = [];
const sent2: unknown[] = [];
const client1 = { ws: { send: (m: string) => sent1.push(JSON.parse(m)) } } as Client;
const client2 = { ws: { send: (m: string) => sent2.push(JSON.parse(m)) } } as Client;
session.join(client1);
session.join(client2);
const awareness = { clientId: 1, cursor: { line: 5, col: 10 } };
session.broadcastAwareness(client1, awareness);
// client1 should NOT receive their own awareness
expect(sent1.filter(m => m.type === "awareness")).toHaveLength(0);
// client2 should receive it
expect(sent2.filter(m => m.type === "awareness")).toHaveLength(1);
expect(sent2.find(m => m.type === "awareness")?.data).toEqual(awareness);
});
});

View file

@ -1,6 +1,6 @@
import type { ServerWebSocket } from "bun"; import type { ServerWebSocket } from "bun";
import * as Y from "yjs"; import * as Y from "yjs";
import { encode } from "./protocol"; import { encode, type AwarenessState } from "./protocol";
export interface WsData { export interface WsData {
room: string | null; room: string | null;
@ -84,6 +84,19 @@ export class Session {
} }
} }
} }
broadcastAwareness(sender: Client, state: AwarenessState): void {
const message = encode({ type: "awareness", data: state });
for (const client of this.clients) {
if (client !== sender) {
try {
client.ws.send(message);
} catch {
// client disconnected, ignore
}
}
}
}
} }
// room name -> session // room name -> session