From e45d66496589802200b4c68aad837bd2c2fec6bd Mon Sep 17 00:00:00 2001 From: Karsten Daemen Date: Wed, 4 Feb 2026 18:34:17 +0100 Subject: [PATCH 1/3] Emit room error before cleanup of room on js client --- packages/loro-websocket/src/client/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/loro-websocket/src/client/index.ts b/packages/loro-websocket/src/client/index.ts index de9f11e..a60b9bc 100644 --- a/packages/loro-websocket/src/client/index.ts +++ b/packages/loro-websocket/src/client/index.ts @@ -717,8 +717,8 @@ export class LoroWebsocketClient { void this.sendRejoinRequest(roomId, msg.roomId, adaptor, active.room, auth); } else { // Remove local room state so client does not auto-retry unless requested - this.cleanupRoom(msg.roomId, msg.crdt); this.emitRoomStatus(roomId, RoomJoinStatus.Error); + this.cleanupRoom(msg.roomId, msg.crdt); } break; } From e13119e64e76e218c28816efb7ea6b099cab5dcd Mon Sep 17 00:00:00 2001 From: Karsten Daemen Date: Tue, 5 May 2026 18:46:38 +0200 Subject: [PATCH 2/3] Do not clean the status listeners on room cleanup --- packages/loro-websocket/src/client/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/loro-websocket/src/client/index.ts b/packages/loro-websocket/src/client/index.ts index a60b9bc..a70428b 100644 --- a/packages/loro-websocket/src/client/index.ts +++ b/packages/loro-websocket/src/client/index.ts @@ -717,8 +717,8 @@ export class LoroWebsocketClient { void this.sendRejoinRequest(roomId, msg.roomId, adaptor, active.room, auth); } else { // Remove local room state so client does not auto-retry unless requested - this.emitRoomStatus(roomId, RoomJoinStatus.Error); this.cleanupRoom(msg.roomId, msg.crdt); + this.emitRoomStatus(roomId, RoomJoinStatus.Error); } break; } @@ -913,7 +913,9 @@ export class LoroWebsocketClient { this.roomAdaptors.delete(id); this.roomIds.delete(id); this.roomAuth.delete(id); - this.roomStatusListeners.delete(id); + // Status listeners are intentionally kept so any emitRoomStatus call + // made immediately after cleanupRoom (e.g. RoomJoinStatus.Error) still + // reaches subscribers. They are cleared when the client is destroyed. } waitConnected() { From 0315b7f52713c49b9a12b00a86e49cc8ddeae084 Mon Sep 17 00:00:00 2001 From: Karsten Daemen Date: Thu, 7 May 2026 02:06:40 +0200 Subject: [PATCH 3/3] Added additional parameters to 'emitRoomStatus' to add more context --- packages/loro-websocket/package.json | 2 +- packages/loro-websocket/src/client/index.ts | 22 ++++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/loro-websocket/package.json b/packages/loro-websocket/package.json index 32db0c0..6cabe17 100644 --- a/packages/loro-websocket/package.json +++ b/packages/loro-websocket/package.json @@ -1,5 +1,5 @@ { - "name": "loro-websocket", + "name": "@karstenda/loro-websocket", "version": "0.6.2", "private": false, "description": "WebSocket client and SimpleServer for syncing CRDTs base on loro-protocol", diff --git a/packages/loro-websocket/src/client/index.ts b/packages/loro-websocket/src/client/index.ts index a70428b..47d7982 100644 --- a/packages/loro-websocket/src/client/index.ts +++ b/packages/loro-websocket/src/client/index.ts @@ -181,7 +181,7 @@ export class LoroWebsocketClient { private roomAuth: Map = new Map(); private roomStatusListeners: Map< string, - Set<(s: RoomJoinStatusValue) => void> + Set<(s: RoomJoinStatusValue, messageType?: MessageType, messageCode?: number) => void> > = new Map(); private socketListeners = new WeakMap(); @@ -625,7 +625,7 @@ export class LoroWebsocketClient { }) .finally(() => { this.pendingRooms.delete(id); - this.emitRoomStatus(id, RoomJoinStatus.Joined); + this.emitRoomStatus(id, RoomJoinStatus.Joined, MessageType.JoinResponseOk); }); }, reject: (error: Error) => { @@ -718,7 +718,7 @@ export class LoroWebsocketClient { } else { // Remove local room state so client does not auto-retry unless requested this.cleanupRoom(msg.roomId, msg.crdt); - this.emitRoomStatus(roomId, RoomJoinStatus.Error); + this.emitRoomStatus(roomId, RoomJoinStatus.Error, MessageType.RoomError, msg.code); } break; } @@ -840,7 +840,7 @@ export class LoroWebsocketClient { } this.pendingRooms.delete(id); - this.emitRoomStatus(id, RoomJoinStatus.Joined); + this.emitRoomStatus(id, RoomJoinStatus.Joined, MessageType.JoinResponseOk); } private async handleJoinError( @@ -857,7 +857,9 @@ export class LoroWebsocketClient { this.pendingRooms.delete(roomId); this.emitRoomStatus( pending.adaptor.crdtType + pending.roomId, - RoomJoinStatus.Error + RoomJoinStatus.Error, + MessageType.JoinError, + msg.code ); return; } @@ -895,7 +897,9 @@ export class LoroWebsocketClient { const err = new Error(`Join failed: ${msg.code} - ${msg.message}`); this.emitRoomStatus( pending.adaptor.crdtType + pending.roomId, - RoomJoinStatus.Error + RoomJoinStatus.Error, + MessageType.JoinError, + msg.code ); // Remove active room references so caller can rejoin manually if this was a rejoin if (pending.isRejoin) { @@ -985,7 +989,7 @@ export class LoroWebsocketClient { roomId: string; crdtAdaptor: CrdtDocAdaptor; auth?: AuthOption; - onStatusChange?: (s: RoomJoinStatusValue) => void; + onStatusChange?: (s: RoomJoinStatusValue, messageType?: MessageType, messageCode?: number) => void; }): Promise { const id = crdtAdaptor.crdtType + roomId; // Check if already joining or joined @@ -1563,12 +1567,12 @@ export class LoroWebsocketClient { } } - private emitRoomStatus(roomKey: string, status: RoomJoinStatusValue) { + private emitRoomStatus(roomKey: string, status: RoomJoinStatusValue, messageType?: MessageType, messageCode?: number) { const set = this.roomStatusListeners.get(roomKey); if (!set || set.size === 0) return; for (const cb of Array.from(set)) { try { - cb(status); + cb(status, messageType, messageCode); } catch (err) { this.logCbError("onRoomStatusChange", err); }