Skip to content

Commit 5a8e4e6

Browse files
committed
Reproduce error with LoroWebsocketClient when mounting in effect in react strict mode via unit test; fix the error
1 parent edf4065 commit 5a8e4e6

2 files changed

Lines changed: 42 additions & 0 deletions

File tree

packages/loro-websocket/src/client/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1544,6 +1544,7 @@ export class LoroWebsocketClient {
15441544

15451545
private sendJoinPayload(payload: Uint8Array) {
15461546
if (this.safeSend(this.ws, payload, "join")) return;
1547+
if (!this.shouldReconnect) return;
15471548
this.enqueueJoin(payload);
15481549
void this.connect();
15491550
}

packages/loro-websocket/tests/e2e.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,47 @@ describe("E2E: RoomError rejoin policy", () => {
10721072
}, 8000);
10731073
});
10741074

1075+
describe("React strict-mode: join + immediate close", () => {
1076+
let server: SimpleServer;
1077+
let port: number;
1078+
1079+
beforeAll(async () => {
1080+
port = await getPort();
1081+
server = new SimpleServer({ port });
1082+
await server.start();
1083+
});
1084+
1085+
afterAll(async () => {
1086+
await server.stop();
1087+
}, 15000);
1088+
1089+
it("close() before auth microtask settles does not resurrect the client", async () => {
1090+
const statuses: ClientStatus[] = [];
1091+
const client = new LoroWebsocketClient({
1092+
url: `ws://localhost:${port}`,
1093+
});
1094+
await client.waitConnected();
1095+
1096+
client.onStatusChange(s => statuses.push(s));
1097+
1098+
// Simulate React strict-mode: effect fires join(), cleanup fires close()
1099+
const adaptor = new LoroAdaptor();
1100+
client.join({ roomId: "strict-mode-zombie", crdtAdaptor: adaptor });
1101+
client.close(); // shouldReconnect = false
1102+
1103+
// Let the resolveAuth().then(sendJoinPayload) microtask fire
1104+
await new Promise(r => setTimeout(r, 500));
1105+
1106+
// The client must stay dead — no Connecting/Connected after Disconnected
1107+
expect(client.getStatus()).toBe(ClientStatus.Disconnected);
1108+
expect(
1109+
statuses.filter(s => s === ClientStatus.Connecting)
1110+
).toHaveLength(0);
1111+
1112+
client.destroy();
1113+
}, 5000);
1114+
});
1115+
10751116
function installMockWindow(initialOnline = true) {
10761117
const originalWindowDescriptor = Object.getOwnPropertyDescriptor(
10771118
globalThis,

0 commit comments

Comments
 (0)