@@ -54,6 +54,28 @@ const RPC_STREAM_SERVICE = 'google.firestore.v1.Firestore';
5454
5555const XHR_TIMEOUT_SECS = 15 ;
5656
57+ // Closure events are guarded and exceptions are swallowed, so catch any
58+ // exception and rethrow using a setTimeout so they become visible again.
59+ // Note that eventually this function could go away if we are confident
60+ // enough the code is exception free.
61+ const unguardedEventListen = < T > (
62+ target : EventTarget ,
63+ type : string | number ,
64+ fn : ( param : T ) => void
65+ ) : void => {
66+ // TODO(dimond): closure typing seems broken because WebChannel does
67+ // not implement goog.events.Listenable
68+ target . listen ( type , ( param : unknown ) => {
69+ try {
70+ fn ( param as T ) ;
71+ } catch ( e ) {
72+ setTimeout ( ( ) => {
73+ throw e ;
74+ } , 0 ) ;
75+ }
76+ } ) ;
77+ } ;
78+
5779export class WebChannelConnection extends RestConnection {
5880 private readonly forceLongPolling : boolean ;
5981 private readonly autoDetectLongPolling : boolean ;
@@ -71,6 +93,29 @@ export class WebChannelConnection extends RestConnection {
7193 this . longPollingOptions = info . longPollingOptions ;
7294 }
7395
96+ /**
97+ * Track if the STAT_EVENT listener has been initialized.
98+ */
99+ static statEventListenerInitialized : boolean = false ;
100+
101+ /**
102+ * Initialize STAT_EVENT listener once. Subsequent calls are a no-op.
103+ * getStatEventTarget() returns the same target every time.
104+ */
105+ static ensureStatEventListenerInitialized ( ) : void {
106+ if ( ! WebChannelConnection . statEventListenerInitialized ) {
107+ const requestStats = getStatEventTarget ( ) ;
108+ unguardedEventListen < StatEvent > ( requestStats , Event . STAT_EVENT , event => {
109+ if ( event . stat === Stat . PROXY ) {
110+ logDebug ( LOG_TAG , `STAT_EVENT: detected buffering proxy` ) ;
111+ } else if ( event . stat === Stat . NOPROXY ) {
112+ logDebug ( LOG_TAG , `STAT_EVENT: detected no buffering proxy` ) ;
113+ }
114+ } ) ;
115+ WebChannelConnection . statEventListenerInitialized = true ;
116+ }
117+ }
118+
74119 protected performRPCRequest < Req , Resp > (
75120 rpcName : string ,
76121 url : string ,
@@ -183,7 +228,6 @@ export class WebChannelConnection extends RestConnection {
183228 '/channel'
184229 ] ;
185230 const webchannelTransport = this . createWebChannelTransport ( ) ;
186- const requestStats = getStatEventTarget ( ) ;
187231 const request : WebChannelOptions = {
188232 // Required for backend stickiness, routing behavior is based on this
189233 // parameter.
@@ -286,28 +330,6 @@ export class WebChannelConnection extends RestConnection {
286330 closeFn : ( ) => channel . close ( )
287331 } ) ;
288332
289- // Closure events are guarded and exceptions are swallowed, so catch any
290- // exception and rethrow using a setTimeout so they become visible again.
291- // Note that eventually this function could go away if we are confident
292- // enough the code is exception free.
293- const unguardedEventListen = < T > (
294- target : EventTarget ,
295- type : string | number ,
296- fn : ( param : T ) => void
297- ) : void => {
298- // TODO(dimond): closure typing seems broken because WebChannel does
299- // not implement goog.events.Listenable
300- target . listen ( type , ( param : unknown ) => {
301- try {
302- fn ( param as T ) ;
303- } catch ( e ) {
304- setTimeout ( ( ) => {
305- throw e ;
306- } , 0 ) ;
307- }
308- } ) ;
309- } ;
310-
311333 unguardedEventListen ( channel , WebChannel . EventType . OPEN , ( ) => {
312334 if ( ! closed ) {
313335 logDebug (
@@ -410,19 +432,8 @@ export class WebChannelConnection extends RestConnection {
410432 }
411433 ) ;
412434
413- unguardedEventListen < StatEvent > ( requestStats , Event . STAT_EVENT , event => {
414- if ( event . stat === Stat . PROXY ) {
415- logDebug (
416- LOG_TAG ,
417- `RPC '${ rpcName } ' stream ${ streamId } detected buffering proxy`
418- ) ;
419- } else if ( event . stat === Stat . NOPROXY ) {
420- logDebug (
421- LOG_TAG ,
422- `RPC '${ rpcName } ' stream ${ streamId } detected no buffering proxy`
423- ) ;
424- }
425- } ) ;
435+ // Ensure that event listeners are configured for STAT_EVENTs.
436+ WebChannelConnection . ensureStatEventListenerInitialized ( ) ;
426437
427438 setTimeout ( ( ) => {
428439 // Technically we could/should wait for the WebChannel opened event,
0 commit comments