Skip to content

Commit dcd7bcf

Browse files
committed
Merge branch 'api-definition' into realtime-autofetch
2 parents 36e19aa + 91b1fc3 commit dcd7bcf

File tree

1 file changed

+63
-38
lines changed

1 file changed

+63
-38
lines changed

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

Lines changed: 63 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
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';
24-
import { VisibilityMonitor } from '@firebase/database/dist/src/core/util/VisibilityMonitor';
23+
24+
const ORIGINAL_RETRIES = 8;
2525

2626
export class RealtimeHandler {
2727
constructor(
@@ -32,9 +32,7 @@ export class RealtimeHandler {
3232
private readonly projectId: string,
3333
private readonly apiKey: string,
3434
private readonly appId: string
35-
) {
36-
VisibilityMonitor.getInstance().on('visible', this.onVisibilityChange_, this);
37-
}
35+
) { }
3836

3937
private streamController?: AbortController;
4038
private observers: Set<ConfigUpdateObserver> = new Set<ConfigUpdateObserver>();
@@ -48,9 +46,9 @@ export class RealtimeHandler {
4846
* Adds an observer to the realtime updates.
4947
* @param observer The observer to add.
5048
*/
51-
addObserver(observer: ConfigUpdateObserver): void {
49+
async addObserver(observer: ConfigUpdateObserver): Promise<void> {
5250
this.observers.add(observer);
53-
this.beginRealtime();
51+
await this.beginRealtime();
5452
}
5553

5654
/**
@@ -61,11 +59,14 @@ export class RealtimeHandler {
6159
if (this.observers.has(observer)) {
6260
this.observers.delete(observer);
6361
}
62+
if (this.observers.size === 0) {
63+
this.stopRealtime();
64+
}
6465
}
6566

66-
private beginRealtime(): void {
67+
private async beginRealtime(): Promise<void> {
6768
if (this.observers.size > 0) {
68-
this.makeRealtimeHttpConnection(0);
69+
await this.makeRealtimeHttpConnection(0);
6970
}
7071
}
7172

@@ -81,18 +82,17 @@ export class RealtimeHandler {
8182
return hasActiveListeners && isNotDisabled && isForeground && isNoConnectionActive;
8283
}
8384

84-
85-
private makeRealtimeHttpConnection(delayMillis: number): void {
85+
private async makeRealtimeHttpConnection(delayMillis: number): Promise<void> {
8686
if (this.scheduledConnectionTimeoutId) {
8787
clearTimeout(this.scheduledConnectionTimeoutId);
8888
}
8989
if (!this.canEstablishStreamConnection()) {
9090
return;
9191
}
92-
this.scheduledConnectionTimeoutId = setTimeout(() => {
92+
this.scheduledConnectionTimeoutId = setTimeout(async () => {
9393
if (this.retriesRemaining > 0) {
9494
this.retriesRemaining--;
95-
this.beginRealtimeHttpStream();
95+
await this.beginRealtimeHttpStream();
9696
} else if (!this.isInBackground) {
9797
const error = ERROR_FACTORY.create(ErrorCode.CONFIG_UPDATE_STREAM_ERROR, { originalErrorMessage: 'Unable to connect to the server. Check your connection and try again.' });
9898
this.propagateError(error);
@@ -154,12 +154,13 @@ export class RealtimeHandler {
154154
await this.storage.setLastKnownTemplateVersion(0);
155155
}
156156

157-
const backoffEndTime = new Date(metadata.backoffEndTimeMillis).getTime();
157+
const backoffEndTime = metadata.backoffEndTimeMillis.getTime();
158+
158159
if (Date.now() < backoffEndTime) {
159-
this.retryHttpConnectionWhenBackoffEnds();
160+
await this.retryHttpConnectionWhenBackoffEnds();
160161
return;
161162
}
162-
let response;
163+
let response: Response | undefined;
163164
try {
164165
const [installationId, installationTokenResult] = await Promise.all([
165166
this.firebaseInstallations.getId(),
@@ -168,8 +169,11 @@ export class RealtimeHandler {
168169
const headers = {
169170
'Content-Type': 'application/json',
170171
'Content-Encoding': 'gzip',
172+
'X-Google-GFE-Can-Retry': 'yes',
173+
'X-Accept-Response-Streaming': 'true',
171174
'If-None-Match': '*',
172-
'authentication-token': installationTokenResult
175+
'authentication-token': installationTokenResult,
176+
'Accept': 'application/json'
173177
};
174178
const url = this.getRealtimeUrl();
175179
const requestBody = {
@@ -181,43 +185,43 @@ export class RealtimeHandler {
181185
appInstanceId: installationId
182186
};
183187

184-
response = await fetch(url, {
188+
response = await fetch(url, {
185189
method: "POST",
186190
headers,
187191
body: JSON.stringify(requestBody)
188192
});
189-
if (response.status === 200 && response.body) {
193+
if (response?.status === 200 && response?.body) {
190194
this.resetRetryCount();
191-
this.resetRealtimeBackoff();
195+
await this.resetRealtimeBackoff();
192196
//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
194198
} else {
195199
throw new FirebaseError('http-status-error', `HTTP Error: ${response.status}`);
196200
}
197-
} catch (error: any) {
201+
} catch (error) {
198202
if (this.isInBackground) {
199203
// It's possible the app was backgrounded while the connection was open, which
200204
// threw an exception trying to read the response. No real error here, so treat
201205
// this as a success, even if we haven't read a 200 response code yet.
202206
this.resetRetryCount();
207+
} else {
208+
console.error('Exception connecting to real-time RC backend. Retrying the connection...:', error);
203209
}
204210
} finally {
205211
this.isConnectionActive = false;
206212
const statusCode = response?.status;
207213
const connectionFailed = !this.isInBackground && (!statusCode || this.isStatusCodeRetryable(statusCode));
208214

209215
if (connectionFailed) {
210-
this.updateBackoffMetadataWithLastFailedStreamConnectionTime(new Date());
216+
await this.updateBackoffMetadataWithLastFailedStreamConnectionTime(new Date());
211217
}
212218

213219
if (connectionFailed || statusCode === 200) {
214-
this.retryHttpConnectionWhenBackoffEnds();
220+
await this.retryHttpConnectionWhenBackoffEnds();
215221
} else {
216-
//still have to implement this part
217222
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);
221225
}
222226
const firebaseError = ERROR_FACTORY.create(ErrorCode.CONFIG_UPDATE_STREAM_ERROR, {
223227
httpStatus: statusCode,
@@ -249,7 +253,7 @@ export class RealtimeHandler {
249253
const backoffEndTime = new Date(metadata.backoffEndTimeMillis).getTime();
250254
const currentTime = Date.now();
251255
const retrySeconds = Math.max(0, backoffEndTime - currentTime);
252-
this.makeRealtimeHttpConnection(retrySeconds);
256+
await this.makeRealtimeHttpConnection(retrySeconds);
253257
}
254258

255259
private async resetRealtimeBackoff(): Promise<void> {
@@ -259,6 +263,7 @@ export class RealtimeHandler {
259263
});
260264
}
261265

266+
262267
private getRealtimeUrl(): URL {
263268
const urlBase =
264269
window.FIREBASE_REMOTE_CONFIG_URL_BASE ||
@@ -268,15 +273,35 @@ export class RealtimeHandler {
268273
return new URL(urlString);
269274
}
270275

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);
279292
}
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.';
280304
}
281305
}
282306
}
307+

0 commit comments

Comments
 (0)