@@ -39,12 +39,6 @@ const SUBSCRIPTION_LIFECYCLE_FRAMES = new Set(['ready', 'nosub']);
3939// bridge so SDK's own callAsync flows aren't surfaced into Meteor.
4040const isSdkInternalId = ( id : unknown ) : boolean => typeof id === 'string' && id . startsWith ( 'rc-ddp-client-' ) ;
4141
42- const getMeteorMethodInvokers = ( ) : Record < string , unknown > => {
43- return ( Meteor . connection as unknown as { _methodInvokers ?: Record < string , unknown > } ) . _methodInvokers ?? { } ;
44- } ;
45-
46- const meteorHasInvoker = ( id : unknown ) : boolean => typeof id === 'string' && id in getMeteorMethodInvokers ( ) ;
47-
4842const shouldBridgeToMeteor = ( frame : ParsedDdpFrame ) : boolean => {
4943 if ( ! frame || typeof frame . msg !== 'string' ) return false ;
5044
@@ -53,29 +47,15 @@ const shouldBridgeToMeteor = (frame: ParsedDdpFrame): boolean => {
5347 }
5448
5549 if ( frame . msg === 'result' ) {
56- if ( isSdkInternalId ( frame . id ) ) return false ;
57- // Drop result frames whose Meteor invoker is gone — feeding them
58- // surfaces as "Received method result but no methods outstanding" and
59- // can race with `updated` to leave Meteor's _processOneDataMessage in a
60- // half-processed state (force-logout cycles invalidate the in-flight
61- // method's invoker before its result returns over the SDK socket).
62- return meteorHasInvoker ( frame . id ) ;
50+ return ! isSdkInternalId ( frame . id ) ;
6351 }
6452 if ( frame . msg === 'updated' ) {
6553 const methods = Array . isArray ( frame . methods ) ? ( frame . methods as unknown [ ] ) : [ ] ;
66- if ( methods . length === 0 ) return false ;
6754 // If any of the methodIds in the `updated` frame is SDK-internal, drop
6855 // the whole frame: Meteor processes every id and would throw on the
6956 // first miss. In practice an `updated` frame carries ids from a single
7057 // originating method call, so this is all-or-nothing anyway.
71- if ( methods . some ( isSdkInternalId ) ) return false ;
72- // Same defence as for `result` — bridge only when Meteor still has an
73- // invoker for every methodId. Without this, `_process_updated` throws
74- // "No callback invoker for method N" out of an async generator and the
75- // throw escapes the try/catch below as an unhandled rejection,
76- // aborting Meteor's frame queue (the next login's result never lands,
77- // userId stays null, and the page is stuck on /login).
78- return methods . every ( meteorHasInvoker ) ;
58+ return methods . length > 0 && ! methods . some ( isSdkInternalId ) ;
7959 }
8060
8161 return false ;
@@ -89,12 +69,24 @@ export const installDdpSdkCollectionBridge = (): void => {
8969 ddp . onMessage ( ( frame ) => {
9070 if ( ! shouldBridgeToMeteor ( frame ) ) return ;
9171
92- // Guard against frames that would collide with Meteor's own subscription
93- // ids. DDPSDK generates its own ids (rc-ddp-client-<n>); Meteor.connection's
94- // invokers ignore frames whose id is not in its tables, so a plain
95- // re-feed is safe.
72+ // `_streamHandlers.onMessage` returns a Promise (the message handler is an
73+ // async generator). A throw inside the inner `_process_updated` /
74+ // `_process_result` (e.g. "No callback invoker for method N" when a
75+ // stale frame arrives after a force-logout cycle invalidates the
76+ // invoker) would otherwise escape this scope as an unhandled rejection,
77+ // aborting Meteor's frame queue and leaving subsequent login result
78+ // frames unprocessed. Wrap the call so both sync throws and async
79+ // rejections are contained — Meteor keeps draining the queue even when
80+ // individual frames hit dead invokers.
9681 try {
97- Meteor . connection . _streamHandlers . onMessage ( DDPCommon . stringifyDDP ( frame as Parameters < typeof DDPCommon . stringifyDDP > [ 0 ] ) ) ;
82+ const result = Meteor . connection . _streamHandlers . onMessage (
83+ DDPCommon . stringifyDDP ( frame as Parameters < typeof DDPCommon . stringifyDDP > [ 0 ] ) ,
84+ ) as unknown ;
85+ if ( result && typeof ( result as Promise < unknown > ) . then === 'function' ) {
86+ ( result as Promise < unknown > ) . catch ( ( err ) => {
87+ console . warn ( '[ddpSdk] bridge frame drop (async)' , frame . msg , err ) ;
88+ } ) ;
89+ }
9890 } catch ( err ) {
9991 console . warn ( '[ddpSdk] bridge frame drop' , frame . msg , err ) ;
10092 }
0 commit comments