@@ -618,6 +618,41 @@ describe("E2E: Client-Server Sync", () => {
618618 }
619619 } , 15000 ) ;
620620
621+ it ( "dedupes concurrent join calls even before auth resolves" , async ( ) => {
622+ const port = await getPort ( ) ;
623+ const tokens : string [ ] = [ ] ;
624+
625+ const server = new SimpleServer ( {
626+ port,
627+ authenticate : async ( _roomId , _crdt , auth ) => {
628+ tokens . push ( new TextDecoder ( ) . decode ( auth ) ) ;
629+ return "write" ;
630+ } ,
631+ } ) ;
632+ await server . start ( ) ;
633+
634+ const client = new LoroWebsocketClient ( { url : `ws://localhost:${ port } ` } ) ;
635+ await client . waitConnected ( ) ;
636+
637+ const adaptor = new LoroAdaptor ( ) ;
638+ const auth = ( ) => new TextEncoder ( ) . encode ( "token-once" ) ;
639+
640+ const joinPromise1 = client . join ( { roomId : "dedupe" , crdtAdaptor : adaptor , auth } ) ;
641+ const joinPromise2 = client . join ( { roomId : "dedupe" , crdtAdaptor : adaptor , auth } ) ;
642+
643+ expect ( joinPromise1 ) . toBe ( joinPromise2 ) ;
644+
645+ const [ room1 , room2 ] = await Promise . all ( [ joinPromise1 , joinPromise2 ] ) ;
646+ expect ( room1 ) . toBe ( room2 ) ;
647+
648+ await waitUntil ( ( ) => tokens . length >= 1 , 5000 , 25 ) ;
649+ expect ( tokens ) . toHaveLength ( 1 ) ;
650+
651+ await room1 . destroy ( ) ;
652+ client . destroy ( ) ;
653+ await server . stop ( ) ;
654+ } , 15000 ) ;
655+
621656 it ( "destroy rejects pending ping waiters" , async ( ) => {
622657 const client = new LoroWebsocketClient ( { url : `ws://localhost:${ port } ` } ) ;
623658 await client . waitConnected ( ) ;
0 commit comments