Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 83 additions & 73 deletions e2e/node-sandbox/test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { config } from "dotenv";
import WebSocket from "ws";
import { describe, test, expect, vi } from "vitest";

type OpaqueUser = User<JsonObject, BaseUserMeta>;

config();

// Utility functions for client tests
Expand All @@ -14,93 +16,101 @@ function wait(ms: number): Promise<void> {

async function waitFor(
predicate: () => boolean,
timeoutMs = 5000
timeoutMs = 10000
): Promise<void> {
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<JsonObject, BaseUserMeta>) => user.presence?.name === "B")
);

// Find the call where room B sees user A
const roomBCallWithA = roomBOthersCallback.mock.calls.find(([others]) =>
others.some((user: User<JsonObject, BaseUserMeta>) => 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();
}
}
);
});
Loading