diff --git a/src/awareness.test.ts b/src/awareness.test.ts new file mode 100644 index 0000000..fdc3850 --- /dev/null +++ b/src/awareness.test.ts @@ -0,0 +1,55 @@ +import { describe, test, expect, beforeAll, afterAll } from "bun:test"; +import type { Server } from "bun"; + +describe("awareness routing", () => { + let server: Server; + const PORT = 4042; + + beforeAll(async () => { + // Import and start server on test port + process.env.PORT = String(PORT); + const mod = await import("./index"); + server = mod.server; + }); + + afterAll(() => { + server?.stop(); + }); + + test("awareness message routes to other peers in same room", async () => { + const ws1 = new WebSocket(`ws://localhost:${PORT}/ws`); + const ws2 = new WebSocket(`ws://localhost:${PORT}/ws`); + + const received: unknown[] = []; + + await Promise.all([ + new Promise(r => ws1.onopen = r), + new Promise(r => ws2.onopen = r), + ]); + + ws2.onmessage = (e) => { + received.push(JSON.parse(e.data)); + }; + + // Both join same room + ws1.send(JSON.stringify({ type: "join", room: "test" })); + ws2.send(JSON.stringify({ type: "join", room: "test" })); + + await Bun.sleep(50); + + // ws1 sends awareness + ws1.send(JSON.stringify({ + type: "awareness", + data: { clientId: 1, cursor: { line: 10, col: 5 } } + })); + + await Bun.sleep(50); + + const awareness = received.find(m => m.type === "awareness"); + expect(awareness).toBeDefined(); + expect(awareness.data.cursor).toEqual({ line: 10, col: 5 }); + + ws1.close(); + ws2.close(); + }); +}); diff --git a/src/index.ts b/src/index.ts index e243d00..339eef8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,7 +14,7 @@ function isValidRoomName(name: unknown): name is string { return /^[a-zA-Z0-9_-]+$/.test(name); } -Bun.serve({ +export const server = Bun.serve({ port: PORT, fetch(req, server) { const url = new URL(req.url); @@ -77,6 +77,15 @@ Bun.serve({ } break; } + case "awareness": { + if (ws.data.room) { + const session = getSession(ws.data.room); + if (session && "data" in msg) { + session.broadcastAwareness(client, msg.data); + } + } + break; + } } }, close(ws) { diff --git a/src/protocol.ts b/src/protocol.ts index bf45488..5516a27 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -46,9 +46,7 @@ function isClientMessage(obj: unknown): obj is ClientMessage { Array.isArray(msg.data) && msg.data.every((n) => typeof n === "number") ); case "awareness": - return ( - Array.isArray(msg.data) && msg.data.every((n) => typeof n === "number") - ); + return typeof msg.data === "object" && msg.data !== null; default: return false; }