Skip to content
Open
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions packages/loro-websocket/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1544,6 +1544,7 @@ export class LoroWebsocketClient {

private sendJoinPayload(payload: Uint8Array) {
if (this.safeSend(this.ws, payload, "join")) return;
if (!this.shouldReconnect) return;
this.enqueueJoin(payload);
void this.connect();
}
Expand Down
41 changes: 41 additions & 0 deletions packages/loro-websocket/tests/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,47 @@ describe("E2E: RoomError rejoin policy", () => {
}, 8000);
});

describe("React strict-mode: join + immediate close", () => {
let server: SimpleServer;
let port: number;

beforeAll(async () => {
port = await getPort();
server = new SimpleServer({ port });
await server.start();
});

afterAll(async () => {
await server.stop();
}, 15000);

it("close() before auth microtask settles does not resurrect the client", async () => {
const statuses: string[] = [];
const client = new LoroWebsocketClient({
url: `ws://localhost:${port}`,
});
await client.waitConnected();

client.onStatusChange(s => statuses.push(s));

// Simulate React strict-mode: effect fires join(), cleanup fires close()
const adaptor = new LoroAdaptor();
client.join({ roomId: "strict-mode-zombie", crdtAdaptor: adaptor });
client.close(); // shouldReconnect = false

// Let the resolveAuth().then(sendJoinPayload) microtask fire
await new Promise(r => setTimeout(r, 500));

// The client must stay dead — no Connecting/Connected after Disconnected
expect(client.getStatus()).toBe(ClientStatus.Disconnected);
expect(
statuses.filter(s => s === ClientStatus.Connecting)
).toHaveLength(0);

client.destroy();
}, 5000);
});

function installMockWindow(initialOnline = true) {
const originalWindowDescriptor = Object.getOwnPropertyDescriptor(
globalThis,
Expand Down