Skip to content

Commit 5272874

Browse files
committed
fix: should not auto reconnect when kicked
1 parent 85f47ab commit 5272874

File tree

2 files changed

+15
-4
lines changed

2 files changed

+15
-4
lines changed

packages/loro-websocket/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ type ClientStatusValue = typeof ClientStatus[keyof typeof ClientStatus];
112112
- Retry cadence: attempts start ~500 ms after the first unexpected close and double on each failure (1 s, 2 s, 4 s, …) up to a 15 s ceiling. A successful connection resets the backoff to the 500 ms starting point.
113113
- The timer is canceled when the environment reports `offline`; the client stays in `Disconnected` until an `online` event arrives, at which point the next retry fires immediately.
114114
- Calling `close()` or `destroy()` turns off auto‑retry. Invoke `connect()` later to restart the process with a fresh backoff window.
115+
- Server‑initiated kicks: if the socket closes with a code in the `4400–4499` range or a reason of `permission_changed` / `room_closed`, the client assumes the server intentionally removed the connection and **does not** schedule reconnect attempts. This mirrors the Durable Object kick semantics used in `@loro-protocol/do`.
115116

116117
## Latency & Ping/Pong
117118

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ interface ActiveRoom {
4949
interface SocketListeners {
5050
open: () => void;
5151
error: (event: Event) => void;
52-
close: () => void;
52+
close: (event: CloseEvent) => void;
5353
message: (event: MessageEvent<string | ArrayBuffer>) => void;
5454
}
5555

@@ -309,8 +309,8 @@ export class LoroWebsocketClient {
309309
const error = (event: Event) => {
310310
this.onSocketError(ws, event);
311311
};
312-
const close = () => {
313-
this.onSocketClose(ws);
312+
const close = (event: CloseEvent) => {
313+
this.onSocketClose(ws, event);
314314
};
315315
const message = (event: MessageEvent<string | ArrayBuffer>) => {
316316
void this.onSocketMessage(ws, event);
@@ -354,13 +354,23 @@ export class LoroWebsocketClient {
354354
// Leave further handling to the close event for the active socket
355355
}
356356

357-
private onSocketClose(ws: WebSocket): void {
357+
private onSocketClose(ws: WebSocket, event?: CloseEvent): void {
358358
const isCurrent = ws === this.ws;
359359
this.detachSocketListeners(ws);
360360
if (!isCurrent) {
361361
return;
362362
}
363363

364+
const closeCode = event?.code;
365+
if (closeCode != null && closeCode >= 4400 && closeCode < 4500) {
366+
this.shouldReconnect = false;
367+
}
368+
369+
const closeReason = event?.reason;
370+
if (closeReason === "permission_changed" || closeReason === "room_closed") {
371+
this.shouldReconnect = false;
372+
}
373+
364374
this.clearPingTimer();
365375
// Clear any pending fragment reassembly timers to avoid late callbacks
366376
if (this.fragmentBatches.size) {

0 commit comments

Comments
 (0)