Skip to content

Commit 638e674

Browse files
committed
Merge branch 'api-definition' into realtime-visibility
2 parents b758777 + 91b1fc3 commit 638e674

File tree

1 file changed

+65
-26
lines changed

1 file changed

+65
-26
lines changed

packages/remote-config/src/client/realtime_handler.ts

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { ConfigUpdateObserver, FetchResponse } from '../public_types';
19-
const ORIGINAL_RETRIES = 8;
18+
import { ConfigUpdateObserver } from '../public_types';
2019
import { ERROR_FACTORY, ErrorCode } from '../errors';
2120
import { _FirebaseInstallationsInternal } from '@firebase/installations';
2221
import { Storage } from '../storage/storage';
2322
import { calculateBackoffMillis, FirebaseError } from '@firebase/util';
2423
import { VisibilityMonitor } from './VisibilityMonitor';
2524

25+
const ORIGINAL_RETRIES = 8;
26+
2627
export class RealtimeHandler {
2728
constructor(
2829
private readonly firebaseInstallations: _FirebaseInstallationsInternal,
@@ -48,9 +49,9 @@ export class RealtimeHandler {
4849
* Adds an observer to the realtime updates.
4950
* @param observer The observer to add.
5051
*/
51-
addObserver(observer: ConfigUpdateObserver): void {
52+
async addObserver(observer: ConfigUpdateObserver): Promise<void> {
5253
this.observers.add(observer);
53-
this.beginRealtime();
54+
await this.beginRealtime();
5455
}
5556

5657
/**
@@ -61,11 +62,14 @@ export class RealtimeHandler {
6162
if (this.observers.has(observer)) {
6263
this.observers.delete(observer);
6364
}
65+
if (this.observers.size === 0) {
66+
this.stopRealtime();
67+
}
6468
}
6569

66-
private beginRealtime(): void {
70+
private async beginRealtime(): Promise<void> {
6771
if (this.observers.size > 0) {
68-
this.makeRealtimeHttpConnection(0);
72+
await this.makeRealtimeHttpConnection(0);
6973
}
7074
}
7175

@@ -81,18 +85,17 @@ export class RealtimeHandler {
8185
return hasActiveListeners && isNotDisabled && isForeground && isNoConnectionActive;
8286
}
8387

84-
85-
private makeRealtimeHttpConnection(delayMillis: number): void {
88+
private async makeRealtimeHttpConnection(delayMillis: number): Promise<void> {
8689
if (this.scheduledConnectionTimeoutId) {
8790
clearTimeout(this.scheduledConnectionTimeoutId);
8891
}
8992
if (!this.canEstablishStreamConnection()) {
9093
return;
9194
}
92-
this.scheduledConnectionTimeoutId = setTimeout(() => {
95+
this.scheduledConnectionTimeoutId = setTimeout(async () => {
9396
if (this.retriesRemaining > 0) {
9497
this.retriesRemaining--;
95-
this.beginRealtimeHttpStream();
98+
await this.beginRealtimeHttpStream();
9699
} else if (!this.isInBackground) {
97100
const error = ERROR_FACTORY.create(ErrorCode.CONFIG_UPDATE_STREAM_ERROR, { originalErrorMessage: 'Unable to connect to the server. Check your connection and try again.' });
98101
this.propagateError(error);
@@ -154,12 +157,13 @@ export class RealtimeHandler {
154157
await this.storage.setLastKnownTemplateVersion(0);
155158
}
156159

157-
const backoffEndTime = new Date(metadata.backoffEndTimeMillis).getTime();
160+
const backoffEndTime = metadata.backoffEndTimeMillis.getTime();
161+
158162
if (Date.now() < backoffEndTime) {
159-
this.retryHttpConnectionWhenBackoffEnds();
163+
await this.retryHttpConnectionWhenBackoffEnds();
160164
return;
161165
}
162-
let response;
166+
let response: Response | undefined;
163167
try {
164168
const [installationId, installationTokenResult] = await Promise.all([
165169
this.firebaseInstallations.getId(),
@@ -168,8 +172,11 @@ export class RealtimeHandler {
168172
const headers = {
169173
'Content-Type': 'application/json',
170174
'Content-Encoding': 'gzip',
175+
'X-Google-GFE-Can-Retry': 'yes',
176+
'X-Accept-Response-Streaming': 'true',
171177
'If-None-Match': '*',
172-
'authentication-token': installationTokenResult
178+
'authentication-token': installationTokenResult,
179+
'Accept': 'application/json'
173180
};
174181
const url = this.getRealtimeUrl();
175182
const requestBody = {
@@ -181,43 +188,43 @@ export class RealtimeHandler {
181188
appInstanceId: installationId
182189
};
183190

184-
response = await fetch(url, {
191+
response = await fetch(url, {
185192
method: "POST",
186193
headers,
187194
body: JSON.stringify(requestBody)
188195
});
189-
if (response.status === 200 && response.body) {
196+
if (response?.status === 200 && response?.body) {
190197
this.resetRetryCount();
191-
this.resetRealtimeBackoff();
198+
await this.resetRealtimeBackoff();
192199
//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
194201
} else {
195202
throw new FirebaseError('http-status-error', `HTTP Error: ${response.status}`);
196203
}
197-
} catch (error: any) {
204+
} catch (error) {
198205
if (this.isInBackground) {
199206
// It's possible the app was backgrounded while the connection was open, which
200207
// threw an exception trying to read the response. No real error here, so treat
201208
// this as a success, even if we haven't read a 200 response code yet.
202209
this.resetRetryCount();
210+
} else {
211+
console.error('Exception connecting to real-time RC backend. Retrying the connection...:', error);
203212
}
204213
} finally {
205214
this.isConnectionActive = false;
206215
const statusCode = response?.status;
207216
const connectionFailed = !this.isInBackground && (!statusCode || this.isStatusCodeRetryable(statusCode));
208217

209218
if (connectionFailed) {
210-
this.updateBackoffMetadataWithLastFailedStreamConnectionTime(new Date());
219+
await this.updateBackoffMetadataWithLastFailedStreamConnectionTime(new Date());
211220
}
212221

213222
if (connectionFailed || statusCode === 200) {
214-
this.retryHttpConnectionWhenBackoffEnds();
223+
await this.retryHttpConnectionWhenBackoffEnds();
215224
} else {
216-
//still have to implement this part
217225
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);
221228
}
222229
const firebaseError = ERROR_FACTORY.create(ErrorCode.CONFIG_UPDATE_STREAM_ERROR, {
223230
httpStatus: statusCode,
@@ -249,7 +256,7 @@ export class RealtimeHandler {
249256
const backoffEndTime = new Date(metadata.backoffEndTimeMillis).getTime();
250257
const currentTime = Date.now();
251258
const retrySeconds = Math.max(0, backoffEndTime - currentTime);
252-
this.makeRealtimeHttpConnection(retrySeconds);
259+
await this.makeRealtimeHttpConnection(retrySeconds);
253260
}
254261

255262
private async resetRealtimeBackoff(): Promise<void> {
@@ -259,6 +266,7 @@ export class RealtimeHandler {
259266
});
260267
}
261268

269+
262270
private getRealtimeUrl(): URL {
263271
const urlBase =
264272
window.FIREBASE_REMOTE_CONFIG_URL_BASE ||
@@ -279,4 +287,35 @@ export class RealtimeHandler {
279287
}
280288
}
281289
}
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+
}
282320
}
321+

0 commit comments

Comments
 (0)