15
15
* limitations under the License.
16
16
*/
17
17
18
- import { ConfigUpdateObserver , FetchResponse } from '../public_types' ;
19
- const ORIGINAL_RETRIES = 8 ;
18
+ import { ConfigUpdateObserver } from '../public_types' ;
20
19
import { ERROR_FACTORY , ErrorCode } from '../errors' ;
21
20
import { _FirebaseInstallationsInternal } from '@firebase/installations' ;
22
21
import { Storage } from '../storage/storage' ;
23
22
import { calculateBackoffMillis , FirebaseError } from '@firebase/util' ;
24
23
import { VisibilityMonitor } from './VisibilityMonitor' ;
25
24
25
+ const ORIGINAL_RETRIES = 8 ;
26
+
26
27
export class RealtimeHandler {
27
28
constructor (
28
29
private readonly firebaseInstallations : _FirebaseInstallationsInternal ,
@@ -48,9 +49,9 @@ export class RealtimeHandler {
48
49
* Adds an observer to the realtime updates.
49
50
* @param observer The observer to add.
50
51
*/
51
- addObserver ( observer : ConfigUpdateObserver ) : void {
52
+ async addObserver ( observer : ConfigUpdateObserver ) : Promise < void > {
52
53
this . observers . add ( observer ) ;
53
- this . beginRealtime ( ) ;
54
+ await this . beginRealtime ( ) ;
54
55
}
55
56
56
57
/**
@@ -61,11 +62,14 @@ export class RealtimeHandler {
61
62
if ( this . observers . has ( observer ) ) {
62
63
this . observers . delete ( observer ) ;
63
64
}
65
+ if ( this . observers . size === 0 ) {
66
+ this . stopRealtime ( ) ;
67
+ }
64
68
}
65
69
66
- private beginRealtime ( ) : void {
70
+ private async beginRealtime ( ) : Promise < void > {
67
71
if ( this . observers . size > 0 ) {
68
- this . makeRealtimeHttpConnection ( 0 ) ;
72
+ await this . makeRealtimeHttpConnection ( 0 ) ;
69
73
}
70
74
}
71
75
@@ -81,18 +85,17 @@ export class RealtimeHandler {
81
85
return hasActiveListeners && isNotDisabled && isForeground && isNoConnectionActive ;
82
86
}
83
87
84
-
85
- private makeRealtimeHttpConnection ( delayMillis : number ) : void {
88
+ private async makeRealtimeHttpConnection ( delayMillis : number ) : Promise < void > {
86
89
if ( this . scheduledConnectionTimeoutId ) {
87
90
clearTimeout ( this . scheduledConnectionTimeoutId ) ;
88
91
}
89
92
if ( ! this . canEstablishStreamConnection ( ) ) {
90
93
return ;
91
94
}
92
- this . scheduledConnectionTimeoutId = setTimeout ( ( ) => {
95
+ this . scheduledConnectionTimeoutId = setTimeout ( async ( ) => {
93
96
if ( this . retriesRemaining > 0 ) {
94
97
this . retriesRemaining -- ;
95
- this . beginRealtimeHttpStream ( ) ;
98
+ await this . beginRealtimeHttpStream ( ) ;
96
99
} else if ( ! this . isInBackground ) {
97
100
const error = ERROR_FACTORY . create ( ErrorCode . CONFIG_UPDATE_STREAM_ERROR , { originalErrorMessage : 'Unable to connect to the server. Check your connection and try again.' } ) ;
98
101
this . propagateError ( error ) ;
@@ -154,12 +157,13 @@ export class RealtimeHandler {
154
157
await this . storage . setLastKnownTemplateVersion ( 0 ) ;
155
158
}
156
159
157
- const backoffEndTime = new Date ( metadata . backoffEndTimeMillis ) . getTime ( ) ;
160
+ const backoffEndTime = metadata . backoffEndTimeMillis . getTime ( ) ;
161
+
158
162
if ( Date . now ( ) < backoffEndTime ) {
159
- this . retryHttpConnectionWhenBackoffEnds ( ) ;
163
+ await this . retryHttpConnectionWhenBackoffEnds ( ) ;
160
164
return ;
161
165
}
162
- let response ;
166
+ let response : Response | undefined ;
163
167
try {
164
168
const [ installationId , installationTokenResult ] = await Promise . all ( [
165
169
this . firebaseInstallations . getId ( ) ,
@@ -168,8 +172,11 @@ export class RealtimeHandler {
168
172
const headers = {
169
173
'Content-Type' : 'application/json' ,
170
174
'Content-Encoding' : 'gzip' ,
175
+ 'X-Google-GFE-Can-Retry' : 'yes' ,
176
+ 'X-Accept-Response-Streaming' : 'true' ,
171
177
'If-None-Match' : '*' ,
172
- 'authentication-token' : installationTokenResult
178
+ 'authentication-token' : installationTokenResult ,
179
+ 'Accept' : 'application/json'
173
180
} ;
174
181
const url = this . getRealtimeUrl ( ) ;
175
182
const requestBody = {
@@ -181,43 +188,43 @@ export class RealtimeHandler {
181
188
appInstanceId : installationId
182
189
} ;
183
190
184
- response = await fetch ( url , {
191
+ response = await fetch ( url , {
185
192
method : "POST" ,
186
193
headers,
187
194
body : JSON . stringify ( requestBody )
188
195
} ) ;
189
- if ( response . status === 200 && response . body ) {
196
+ if ( response ? .status === 200 && response ? .body ) {
190
197
this . resetRetryCount ( ) ;
191
- this . resetRealtimeBackoff ( ) ;
198
+ await this . resetRealtimeBackoff ( ) ;
192
199
//code related to start StartAutofetch
193
- //and then give the notification for al the observers
200
+ //and then give the notification to all the observers
194
201
} else {
195
202
throw new FirebaseError ( 'http-status-error' , `HTTP Error: ${ response . status } ` ) ;
196
203
}
197
- } catch ( error : any ) {
204
+ } catch ( error ) {
198
205
if ( this . isInBackground ) {
199
206
// It's possible the app was backgrounded while the connection was open, which
200
207
// threw an exception trying to read the response. No real error here, so treat
201
208
// this as a success, even if we haven't read a 200 response code yet.
202
209
this . resetRetryCount ( ) ;
210
+ } else {
211
+ console . error ( 'Exception connecting to real-time RC backend. Retrying the connection...:' , error ) ;
203
212
}
204
213
} finally {
205
214
this . isConnectionActive = false ;
206
215
const statusCode = response ?. status ;
207
216
const connectionFailed = ! this . isInBackground && ( ! statusCode || this . isStatusCodeRetryable ( statusCode ) ) ;
208
217
209
218
if ( connectionFailed ) {
210
- this . updateBackoffMetadataWithLastFailedStreamConnectionTime ( new Date ( ) ) ;
219
+ await this . updateBackoffMetadataWithLastFailedStreamConnectionTime ( new Date ( ) ) ;
211
220
}
212
221
213
222
if ( connectionFailed || statusCode === 200 ) {
214
- this . retryHttpConnectionWhenBackoffEnds ( ) ;
223
+ await this . retryHttpConnectionWhenBackoffEnds ( ) ;
215
224
} else {
216
- //still have to implement this part
217
225
let errorMessage = `Unable to connect to the server. Try again in a few minutes. HTTP status code: ${ statusCode } ` ;
218
- if ( statusCode === 403 ) {
219
- //still have to implemet this parseErrorResponseBody method
220
- // errorMessage = await this.parseErrorResponseBody(response?.body);
226
+ if ( statusCode === 403 && response ) {
227
+ errorMessage = await this . parseErrorResponseBody ( response . body ) ;
221
228
}
222
229
const firebaseError = ERROR_FACTORY . create ( ErrorCode . CONFIG_UPDATE_STREAM_ERROR , {
223
230
httpStatus : statusCode ,
@@ -249,7 +256,7 @@ export class RealtimeHandler {
249
256
const backoffEndTime = new Date ( metadata . backoffEndTimeMillis ) . getTime ( ) ;
250
257
const currentTime = Date . now ( ) ;
251
258
const retrySeconds = Math . max ( 0 , backoffEndTime - currentTime ) ;
252
- this . makeRealtimeHttpConnection ( retrySeconds ) ;
259
+ await this . makeRealtimeHttpConnection ( retrySeconds ) ;
253
260
}
254
261
255
262
private async resetRealtimeBackoff ( ) : Promise < void > {
@@ -259,6 +266,7 @@ export class RealtimeHandler {
259
266
} ) ;
260
267
}
261
268
269
+
262
270
private getRealtimeUrl ( ) : URL {
263
271
const urlBase =
264
272
window . FIREBASE_REMOTE_CONFIG_URL_BASE ||
@@ -279,4 +287,35 @@ export class RealtimeHandler {
279
287
}
280
288
}
281
289
}
290
+ private async parseErrorResponseBody (
291
+ body : ReadableStream < Uint8Array > | null
292
+ ) : Promise < string > {
293
+ if ( ! body ) {
294
+ return 'Response body is empty.' ;
295
+ }
296
+
297
+ try {
298
+ const reader = body . getReader ( ) ;
299
+ const chunks : Uint8Array [ ] = [ ] ;
300
+ while ( true ) {
301
+ const { done, value } = await reader . read ( ) ;
302
+ if ( done ) {
303
+ break ;
304
+ }
305
+ chunks . push ( value ) ;
306
+ }
307
+ const blob = new Blob ( chunks ) ;
308
+ const text = await blob . text ( ) ;
309
+
310
+ const jsonResponse = JSON . parse ( text ) ;
311
+ return (
312
+ jsonResponse . error ?. message ||
313
+ jsonResponse . message ||
314
+ 'Unknown error from server.'
315
+ ) ;
316
+ } catch ( e ) {
317
+ return 'Could not parse error response body, or body is not JSON.' ;
318
+ }
319
+ }
282
320
}
321
+
0 commit comments