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