diff --git a/e2e/node-sandbox/test/client.test.ts b/e2e/node-sandbox/test/client.test.ts index fffb9b31e2f..ae8a3668a5c 100644 --- a/e2e/node-sandbox/test/client.test.ts +++ b/e2e/node-sandbox/test/client.test.ts @@ -5,6 +5,8 @@ import { config } from "dotenv"; import WebSocket from "ws"; import { describe, test, expect, vi } from "vitest"; +type OpaqueUser = User; + config(); // Utility functions for client tests @@ -14,93 +16,101 @@ function wait(ms: number): Promise { async function waitFor( predicate: () => boolean, - timeoutMs = 5000 + timeoutMs = 10000 ): Promise { const result = predicate(); if (result) { return; } - const time = new Date().getTime(); + const startTime = new Date().getTime(); - while (new Date().getTime() - time < timeoutMs) { + while (new Date().getTime() - startTime < timeoutMs) { await wait(100); if (predicate()) { return; } } - throw new Error("TIMEOUT"); + throw new Error(`TIMEOUT after ${timeoutMs}ms`); } describe("@liveblocks/client package e2e", () => { - test("presence should work in node environment", async () => { - // First, create the room with proper permissions - const serverClient = new Liveblocks({ - secret: process.env.LIVEBLOCKS_SECRET_KEY!, - // @ts-expect-error hidden config - baseUrl: - process.env.LIVEBLOCKS_BASE_URL ?? - process.env.NEXT_PUBLIC_LIVEBLOCKS_BASE_URL ?? - "https://api.liveblocks.io", - }); - - await serverClient.createRoom( - "node-e2e", - { defaultAccesses: ["room:write"] }, - { idempotent: true } - ); - - const clientA = createClient({ - publicApiKey: - process.env.PUBLIC_LIVEBLOCKS_PUBLIC_KEY ?? - process.env.NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY!, - polyfills: { WebSocket }, - // @ts-expect-error hidden config - baseUrl: process.env.NEXT_PUBLIC_LIVEBLOCKS_BASE_URL!, - }); - - const clientB = createClient({ - publicApiKey: - process.env.PUBLIC_LIVEBLOCKS_PUBLIC_KEY ?? - process.env.NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY!, - polyfills: { WebSocket }, - // @ts-expect-error hidden config - baseUrl: process.env.NEXT_PUBLIC_LIVEBLOCKS_BASE_URL, - }); - - const { room: roomA, leave: leaveA } = clientA.enterRoom("node-e2e", { - initialPresence: { name: "A" }, - }); - const { room: roomB, leave: leaveB } = clientB.enterRoom("node-e2e", { - initialPresence: { name: "B" }, - }); - - const roomAOthersCallback = vi.fn(); - const roomBOthersCallback = vi.fn(); - - roomA.subscribe("others", roomAOthersCallback); - roomB.subscribe("others", roomBOthersCallback); - - // Wait until both callbacks have been called at least once - await waitFor(() => roomAOthersCallback.mock.calls.length > 0); - await waitFor(() => roomBOthersCallback.mock.calls.length > 0); - - // Find the call where room A sees user B - const roomACallWithB = roomAOthersCallback.mock.calls.find(([others]) => - others.some((user: User) => user.presence?.name === "B") - ); - - // Find the call where room B sees user A - const roomBCallWithA = roomBOthersCallback.mock.calls.find(([others]) => - others.some((user: User) => user.presence?.name === "A") - ); - - // Assert that both rooms saw the expected users - expect(roomACallWithB, "Room A should have received user B in others").toBeDefined(); - expect(roomBCallWithA, "Room B should have received user A in others").toBeDefined(); - - leaveA(); - leaveB(); - }); + test( + "presence should work in node environment", + { timeout: 15000 }, + async () => { + // First, create the room with proper permissions + const serverClient = new Liveblocks({ + secret: process.env.LIVEBLOCKS_SECRET_KEY!, + // @ts-expect-error hidden config + baseUrl: + process.env.LIVEBLOCKS_BASE_URL ?? + process.env.NEXT_PUBLIC_LIVEBLOCKS_BASE_URL ?? + "https://api.liveblocks.io", + }); + + await serverClient.createRoom( + "node-e2e", + { defaultAccesses: ["room:write"] }, + { idempotent: true } + ); + + const clientA = createClient({ + publicApiKey: + process.env.PUBLIC_LIVEBLOCKS_PUBLIC_KEY ?? + process.env.NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY!, + polyfills: { WebSocket }, + // @ts-expect-error hidden config + baseUrl: process.env.NEXT_PUBLIC_LIVEBLOCKS_BASE_URL!, + }); + + const clientB = createClient({ + publicApiKey: + process.env.PUBLIC_LIVEBLOCKS_PUBLIC_KEY ?? + process.env.NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY!, + polyfills: { WebSocket }, + // @ts-expect-error hidden config + baseUrl: process.env.NEXT_PUBLIC_LIVEBLOCKS_BASE_URL, + }); + + const { room: roomA, leave: leaveA } = clientA.enterRoom("node-e2e", { + initialPresence: { name: "A" }, + }); + const { room: roomB, leave: leaveB } = clientB.enterRoom("node-e2e", { + initialPresence: { name: "B" }, + }); + + try { + let callbackACalled = false; + let callbackBCalled = false; + let roomASawB = false; + let roomBSawA = false; + + roomA.subscribe("others", (others) => { + callbackACalled = true; + if (others.some((user: OpaqueUser) => user.presence?.name === "B")) { + roomASawB = true; + } + }); + + roomB.subscribe("others", (others) => { + callbackBCalled = true; + if (others.some((user: OpaqueUser) => user.presence?.name === "A")) { + roomBSawA = true; + } + }); + + // Wait for both callbacks to be fired + await waitFor(() => callbackACalled && callbackBCalled, 10000); + + // Assert that both rooms saw the expected users + expect(roomASawB, "Room A should have seen user B").toBe(true); + expect(roomBSawA, "Room B should have seen user A").toBe(true); + } finally { + leaveA(); + leaveB(); + } + } + ); });