Skip to content

Commit a7575eb

Browse files
committed
adeed the unit test case for call addObserver on the realtime handler with the correct arguments, return a function that removes the observer
1 parent 4871c98 commit a7575eb

File tree

7 files changed

+258
-53
lines changed

7 files changed

+258
-53
lines changed

packages/remote-config/src/api.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ import {
2424
Value,
2525
RemoteConfigOptions,
2626
ConfigUpdateObserver,
27-
Unsubscribe,
28-
ConfigUpdate
27+
Unsubscribe
2928
} from './public_types';
3029
import { RemoteConfigAbortSignal } from './client/remote_config_fetch_client';
3130
import {
@@ -372,5 +371,5 @@ export function onConfigUpdate(
372371
rc._realtimeHandler.addObserver(observer);
373372
return () => {
374373
rc._realtimeHandler.removeObserver(observer);
375-
}
376-
};
374+
};
375+
}

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

Lines changed: 172 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,42 +15,185 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { ConfigUpdateObserver } from "../public_types";
18+
import { ConfigUpdateObserver, /*FetchResponse*/ } from '../public_types';
1919
const MAX_HTTP_RETRIES = 8;
20+
// import { ERROR_FACTORY, ErrorCode } from '../errors';
21+
// import { FetchRequest } from './remote_config_fetch_client';
22+
import { _FirebaseInstallationsInternal } from '@firebase/installations';
23+
export class RealtimeHandler {
24+
constructor (
25+
private readonly firebaseInstallations: _FirebaseInstallationsInternal,
26+
)
27+
{ }
2028

21-
export class RealtimeHandler{
22-
constructor ( )
23-
{ }
24-
25-
private observers: Set<ConfigUpdateObserver> = new Set<ConfigUpdateObserver>();
26-
27-
/**
28-
* Adds an observer to the realtime updates.
29-
* @param observer The observer to add.
30-
*/
31-
public addObserver(observer: ConfigUpdateObserver): void {
32-
this.observers.add(observer);
33-
this.beginRealtime();
34-
}
35-
36-
/**
37-
* Removes an observer from the realtime updates.
38-
* @param observer The observer to remove.
39-
*/
40-
public removeObserver(observer: ConfigUpdateObserver): void {
41-
if (this.observers.has(observer)) {
42-
this.observers.delete(observer);
43-
}
29+
private streamController?: AbortController;
30+
private observers: Set<ConfigUpdateObserver> = new Set<ConfigUpdateObserver>();
31+
private isConnectionActive: boolean = false;
32+
private retriesRemaining: number = MAX_HTTP_RETRIES;
33+
private isRealtimeDisabled: boolean = false;
34+
private isInBackground: boolean = false;
35+
private backoffCount: number = 0;
36+
private scheduledConnectionTimeoutId?: ReturnType<typeof setTimeout>;
37+
// private backoffManager: BackoffManager = new BackoffManager();
38+
39+
40+
/**
41+
* Adds an observer to the realtime updates.
42+
* @param observer The observer to add.
43+
*/
44+
addObserver(observer: ConfigUpdateObserver): void {
45+
this.observers.add(observer);
46+
//this.beginRealtime();
47+
}
48+
49+
/**
50+
* Removes an observer from the realtime updates.
51+
* @param observer The observer to remove.
52+
*/
53+
removeObserver(observer: ConfigUpdateObserver): void {
54+
if (this.observers.has(observer)) {
55+
this.observers.delete(observer);
4456
}
57+
}
4558

46-
private beginRealtime(): void {
47-
if (this.observers.size > 0) {
48-
this.makeRealtimeHttpConnection(0)
49-
}
59+
private beginRealtime(): void {
60+
if (this.observers.size > 0) {
61+
this.retriesRemaining = MAX_HTTP_RETRIES;
62+
this.backoffCount = 0;
63+
// this.makeRealtimeHttpConnection(0);
5064
}
65+
}
66+
67+
// private canMakeHttpConnection(): void {
68+
69+
// }
70+
71+
private canEstablishStreamConnection(): boolean {
72+
const hasActiveListeners = this.observers.size > 0;
73+
const isNotDisabled = !this.isRealtimeDisabled;
74+
const isForeground = !this.isInBackground;
75+
return hasActiveListeners && isNotDisabled && isForeground;
76+
}
77+
78+
// private async makeRealtimeHttpConnection(delayMillis: number): void {
79+
// if (this.scheduledConnectionTimeoutId) {
80+
// clearTimeout(this.scheduledConnectionTimeoutId);
81+
// }
5182

52-
private makeRealtimeHttpConnection(retryMilliseconds: number): void {
83+
// this.scheduledConnectionTimeoutId = setTimeout(() => {
84+
// // Check 1: Can we connect at all? Mirrors Java's first check.
85+
// if (!this.canEstablishStreamConnection()) {
86+
// return;
87+
// }
88+
// if (this.retriesRemaining > 0) {
89+
// this.retriesRemaining--;
90+
// await this.beginRealtimeHttpStream();
91+
// } else if (!this.isInBackground) {
92+
// throw ERROR_FACTORY.create(ErrorCode.REALTIME_UPDATE_STREAM_ERROR);
93+
// }
94+
// }, delayMillis);
95+
// }
5396

97+
98+
private checkAndSetHttpConnectionFlagIfNotRunning(): boolean {
99+
if (this.canEstablishStreamConnection()) {
100+
this.streamController = new AbortController();
101+
this.isConnectionActive = true;
102+
return true;
54103
}
104+
return false;
105+
}
106+
107+
// private retryHttpConnectionWhenBackoffEnds(): void {
108+
// const currentTime = Date.now();
109+
// const timeToWait = Math.max(0, this.backoffManager.backoffEndTimeMillis - currentTime);
110+
// this.makeRealtimeHttpConnection(timeToWait);
111+
// }
112+
113+
114+
// private async createFetchRequest(): Promise<FetchRequest> {
115+
// const [installationId, installationTokenResult] = await Promise.all([
116+
// this.firebaseInstallations.getId(),
117+
// this.firebaseInstallations.getToken(false)
118+
// ]);
119+
120+
// const url = this._getRealtimeUrl();
121+
122+
// const requestBody = {
123+
// project: extractProjectNumberFromAppId(this.firebaseApp.options.appId!),
124+
// namespace: 'firebase',
125+
// lastKnownVersionNumber: this.templateVersion.toString(),
126+
// appId: this.firebaseApp.options.appId,
127+
// sdkVersion: '20.0.4',
128+
// appInstanceId: installationId
129+
// };
130+
131+
// const request: FetchRequest = {
132+
// url: url.toString(),
133+
// method: 'POST',
134+
// signal: this.streamController!.signal,
135+
// body: JSON.stringify(requestBody),
136+
// headers: {
137+
// 'Content-Type': 'application/json',
138+
// 'Accept': 'application/json',
139+
// 'X-Goog-Api-Key': this.firebaseApp.options.apiKey!,
140+
// 'X-Goog-Firebase-Installations-Auth': installationTokenResult.token,
141+
// 'X-Accept-Response-Streaming': 'true',
142+
// 'X-Google-GFE-Can-Retry': 'yes'
143+
// }
144+
// };
145+
// return request;
146+
// }
147+
148+
// //method which is responsible for making an realtime HTTP connection
149+
// private async beginRealtimeHttpStream(): void {
150+
// if (!this.checkAndSetHttpConnectionFlagIfNotRunning()) {
151+
// return;
152+
// }
153+
154+
// const currentTime = Date.now();
155+
// if (currentTime < this.backoffManager.backoffEndTimeMillis) {
156+
// this.retryHttpConnectionWhenBackoffEnds();
157+
// return;
158+
// }
159+
160+
// let response: FetchResponse | undefined;
161+
162+
// try {
163+
// const request = await this.createFetchRequest();
164+
165+
// response = await this.fetchClient.fetch(request);
166+
167+
// if (response.status === 200 && response.body) {
168+
// this.retriesRemaining = MAX_HTTP_RETRIES;
169+
// this.backoffCount = 0;
170+
// this.backoffManager.reset();
171+
// this.saveRealtimeBackoffMetadata();
172+
173+
// const parser = new StreamParser(response.body, this.observers);
174+
// await parser.listen();
175+
// } else {
176+
// throw new FirebaseError('http-status-error', `HTTP Error: ${response.status}`);
177+
// }
178+
// } catch (error) {
179+
// if (error.name === 'AbortError') {
180+
// return;
181+
// }
182+
// } finally {
183+
// this.isConnectionActive = false;
184+
185+
// const statusCode = response?.status;
186+
// const connectionFailed = !this.isInBackground && (!statusCode || this.isStatusCodeRetryable(statusCode));
55187

188+
// if (connectionFailed) {
189+
// this.handleStreamError();
190+
// } else if (statusCode && statusCode !== 200) {
191+
// const firebaseError = new FirebaseError('config-update-stream-error',
192+
// `Unable to connect to the server. HTTP status code: ${statusCode}`);
193+
// this.propagateError(firebaseError);
194+
// } else {
195+
// this.makeRealtimeHttpConnection(0);
196+
// }
197+
// }
198+
// }
56199
}

packages/remote-config/src/errors.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ export const enum ErrorCode {
3333
FETCH_PARSE = 'fetch-client-parse',
3434
FETCH_STATUS = 'fetch-status',
3535
INDEXED_DB_UNAVAILABLE = 'indexed-db-unavailable',
36-
CUSTOM_SIGNAL_MAX_ALLOWED_SIGNALS = 'custom-signal-max-allowed-signals'
36+
CUSTOM_SIGNAL_MAX_ALLOWED_SIGNALS = 'custom-signal-max-allowed-signals',
37+
REALTIME_UPDATE_STREAM_ERROR = 'stream-error'
3738
}
3839

3940
const ERROR_DESCRIPTION_MAP: { readonly [key in ErrorCode]: string } = {
@@ -72,7 +73,9 @@ const ERROR_DESCRIPTION_MAP: { readonly [key in ErrorCode]: string } = {
7273
[ErrorCode.INDEXED_DB_UNAVAILABLE]:
7374
'Indexed DB is not supported by current browser',
7475
[ErrorCode.CUSTOM_SIGNAL_MAX_ALLOWED_SIGNALS]:
75-
'Setting more than {$maxSignals} custom signals is not supported.'
76+
'Setting more than {$maxSignals} custom signals is not supported.',
77+
[ErrorCode.REALTIME_UPDATE_STREAM_ERROR]:
78+
'The stream was not able to connect to the backend.'
7679
};
7780

7881
// Note this is effectively a type system binding a code to params. This approach overlaps with the

packages/remote-config/src/public_types.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -223,20 +223,20 @@ declare module '@firebase/component' {
223223
* @public
224224
*/
225225
export interface ConfigUpdateObserver {
226-
/**
227-
* Called when a new ConfigUpdate is available.
228-
*/
229-
next: (configUpdate: ConfigUpdate) => void;
226+
/**
227+
* Called when a new ConfigUpdate is available.
228+
*/
229+
next: (configUpdate: ConfigUpdate) => void;
230230

231-
/**
232-
* Called if an error occurs during the stream.
233-
*/
234-
error: (error: FirebaseError) => void;
231+
/**
232+
* Called if an error occurs during the stream.
233+
*/
234+
error: (error: FirebaseError) => void;
235235

236-
/**
237-
* Called when the stream is gracefully terminated.
238-
*/
239-
complete: () => void;
236+
/**
237+
* Called when the stream is gracefully terminated.
238+
*/
239+
complete: () => void;
240240
}
241241

242242
/**
@@ -248,13 +248,13 @@ export type Unsubscribe = () => void;
248248

249249
/**
250250
* Contains information about which keys have been updated.
251-
*
251+
*
252252
* @public
253253
*/
254254
export interface ConfigUpdate {
255255
/**
256256
* Parameter keys whose values have been updated from the currently activated values.
257257
* Includes keys that are added, deleted, or whose value, value source, or metadata has changed.
258258
*/
259-
readonly updatedKeys: string[]
259+
readonly updatedKeys: string[];
260260
}

packages/remote-config/src/register.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export function registerRemoteConfig(): void {
108108
logger
109109
);
110110

111-
const realtimehandler = new RealtimeHandler();
111+
const realtimehandler = new RealtimeHandler(installations);
112112

113113
const remoteConfigInstance = new RemoteConfigImpl(
114114
app,

0 commit comments

Comments
 (0)