Skip to content

Commit 9296353

Browse files
Merge 3e5477b into c5f08a9
2 parents c5f08a9 + 3e5477b commit 9296353

22 files changed

+2378
-23
lines changed

.changeset/lemon-baboons-lick.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@firebase/remote-config': minor
3+
'firebase': minor
4+
'@firebase/remote-config-types': minor
5+
---
6+
7+
Added support for Realtime Remote Config for the web. This feature introduces a new `onConfigUpdate` API and allows web applications to receive near-instant configuration updates without requiring periodic polling.

common/api-review/remote-config.api.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,23 @@
55
```ts
66

77
import { FirebaseApp } from '@firebase/app';
8+
import { FirebaseError } from '@firebase/app';
89

910
// @public
1011
export function activate(remoteConfig: RemoteConfig): Promise<boolean>;
1112

13+
// @public
14+
export interface ConfigUpdate {
15+
getUpdatedKeys(): Set<string>;
16+
}
17+
18+
// @public
19+
export interface ConfigUpdateObserver {
20+
complete: () => void;
21+
error: (error: FirebaseError) => void;
22+
next: (configUpdate: ConfigUpdate) => void;
23+
}
24+
1225
// @public
1326
export interface CustomSignals {
1427
// (undocumented)
@@ -29,11 +42,15 @@ export interface FetchResponse {
2942
config?: FirebaseRemoteConfigObject;
3043
eTag?: string;
3144
status: number;
45+
templateVersion?: number;
3246
}
3347

3448
// @public
3549
export type FetchStatus = 'no-fetch-yet' | 'success' | 'failure' | 'throttle';
3650

51+
// @public
52+
export type FetchType = 'BASE' | 'REALTIME';
53+
3754
// @public
3855
export interface FirebaseRemoteConfigObject {
3956
// (undocumented)
@@ -64,6 +81,9 @@ export function isSupported(): Promise<boolean>;
6481
// @public
6582
export type LogLevel = 'debug' | 'error' | 'silent';
6683

84+
// @public
85+
export function onConfigUpdate(remoteConfig: RemoteConfig, observer: ConfigUpdateObserver): Unsubscribe;
86+
6787
// @public
6888
export interface RemoteConfig {
6989
app: FirebaseApp;
@@ -93,6 +113,9 @@ export function setCustomSignals(remoteConfig: RemoteConfig, customSignals: Cust
93113
// @public
94114
export function setLogLevel(remoteConfig: RemoteConfig, logLevel: LogLevel): void;
95115

116+
// @public
117+
export type Unsubscribe = () => void;
118+
96119
// @public
97120
export interface Value {
98121
asBoolean(): boolean;

docs-devsite/_toc.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,10 @@ toc:
607607
- title: remote-config
608608
path: /docs/reference/js/remote-config.md
609609
section:
610+
- title: ConfigUpdate
611+
path: /docs/reference/js/remote-config.configupdate.md
612+
- title: ConfigUpdateObserver
613+
path: /docs/reference/js/remote-config.configupdateobserver.md
610614
- title: CustomSignals
611615
path: /docs/reference/js/remote-config.customsignals.md
612616
- title: FetchResponse
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
Project: /docs/reference/js/_project.yaml
2+
Book: /docs/reference/_book.yaml
3+
page_type: reference
4+
5+
{% comment %}
6+
DO NOT EDIT THIS FILE!
7+
This is generated by the JS SDK team, and any local changes will be
8+
overwritten. Changes should be made in the source code at
9+
https://github.com/firebase/firebase-js-sdk
10+
{% endcomment %}
11+
12+
# ConfigUpdate interface
13+
Contains information about which keys have been updated.
14+
15+
<b>Signature:</b>
16+
17+
```typescript
18+
export interface ConfigUpdate
19+
```
20+
21+
## Methods
22+
23+
| Method | Description |
24+
| --- | --- |
25+
| [getUpdatedKeys()](./remote-config.configupdate.md#configupdategetupdatedkeys) | Parameter keys whose values have been updated from the currently activated values. Includes keys that are added, deleted, or whose value, value source, or metadata has changed. |
26+
27+
## ConfigUpdate.getUpdatedKeys()
28+
29+
Parameter keys whose values have been updated from the currently activated values. Includes keys that are added, deleted, or whose value, value source, or metadata has changed.
30+
31+
<b>Signature:</b>
32+
33+
```typescript
34+
getUpdatedKeys(): Set<string>;
35+
```
36+
<b>Returns:</b>
37+
38+
Set&lt;string&gt;
39+
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
Project: /docs/reference/js/_project.yaml
2+
Book: /docs/reference/_book.yaml
3+
page_type: reference
4+
5+
{% comment %}
6+
DO NOT EDIT THIS FILE!
7+
This is generated by the JS SDK team, and any local changes will be
8+
overwritten. Changes should be made in the source code at
9+
https://github.com/firebase/firebase-js-sdk
10+
{% endcomment %}
11+
12+
# ConfigUpdateObserver interface
13+
Observer interface for receiving real-time Remote Config update notifications.
14+
15+
NOTE: Although an `complete` callback can be provided, it will never be called because the ConfigUpdate stream is never-ending.
16+
17+
<b>Signature:</b>
18+
19+
```typescript
20+
export interface ConfigUpdateObserver
21+
```
22+
23+
## Properties
24+
25+
| Property | Type | Description |
26+
| --- | --- | --- |
27+
| [complete](./remote-config.configupdateobserver.md#configupdateobservercomplete) | () =&gt; void | Called when the stream is gracefully terminated. |
28+
| [error](./remote-config.configupdateobserver.md#configupdateobservererror) | (error: [FirebaseError](./util.firebaseerror.md#firebaseerror_class)<!-- -->) =&gt; void | Called if an error occurs during the stream. |
29+
| [next](./remote-config.configupdateobserver.md#configupdateobservernext) | (configUpdate: [ConfigUpdate](./remote-config.configupdate.md#configupdate_interface)<!-- -->) =&gt; void | Called when a new ConfigUpdate is available. |
30+
31+
## ConfigUpdateObserver.complete
32+
33+
Called when the stream is gracefully terminated.
34+
35+
<b>Signature:</b>
36+
37+
```typescript
38+
complete: () => void;
39+
```
40+
41+
## ConfigUpdateObserver.error
42+
43+
Called if an error occurs during the stream.
44+
45+
<b>Signature:</b>
46+
47+
```typescript
48+
error: (error: FirebaseError) => void;
49+
```
50+
51+
## ConfigUpdateObserver.next
52+
53+
Called when a new ConfigUpdate is available.
54+
55+
<b>Signature:</b>
56+
57+
```typescript
58+
next: (configUpdate: ConfigUpdate) => void;
59+
```

docs-devsite/remote-config.fetchresponse.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface FetchResponse
2727
| [config](./remote-config.fetchresponse.md#fetchresponseconfig) | [FirebaseRemoteConfigObject](./remote-config.firebaseremoteconfigobject.md#firebaseremoteconfigobject_interface) | Defines the map of parameters returned as "entries" in the fetch response body.<p>Only defined for 200 responses. |
2828
| [eTag](./remote-config.fetchresponse.md#fetchresponseetag) | string | Defines the ETag response header value.<p>Only defined for 200 and 304 responses. |
2929
| [status](./remote-config.fetchresponse.md#fetchresponsestatus) | number | The HTTP status, which is useful for differentiating success responses with data from those without.<p>The Remote Config client is modeled after the native <code>Fetch</code> interface, so HTTP status is first-class.<p>Disambiguation: the fetch response returns a legacy "state" value that is redundant with the HTTP status code. The former is normalized into the latter. |
30+
| [templateVersion](./remote-config.fetchresponse.md#fetchresponsetemplateversion) | number | The version number of the config template fetched from the server. |
3031

3132
## FetchResponse.config
3233

@@ -65,3 +66,13 @@ The HTTP status, which is useful for differentiating success responses with data
6566
```typescript
6667
status: number;
6768
```
69+
70+
## FetchResponse.templateVersion
71+
72+
The version number of the config template fetched from the server.
73+
74+
<b>Signature:</b>
75+
76+
```typescript
77+
templateVersion?: number;
78+
```

docs-devsite/remote-config.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ The Firebase Remote Config Web SDK. This SDK does not work in a Node.js environm
2828
| [getNumber(remoteConfig, key)](./remote-config.md#getnumber_476c09f) | Gets the value for the given key as a number.<!-- -->Convenience method for calling <code>remoteConfig.getValue(key).asNumber()</code>. |
2929
| [getString(remoteConfig, key)](./remote-config.md#getstring_476c09f) | Gets the value for the given key as a string. Convenience method for calling <code>remoteConfig.getValue(key).asString()</code>. |
3030
| [getValue(remoteConfig, key)](./remote-config.md#getvalue_476c09f) | Gets the [Value](./remote-config.value.md#value_interface) for the given key. |
31+
| [onConfigUpdate(remoteConfig, observer)](./remote-config.md#onconfigupdate_8b13b26) | Starts listening for real-time config updates from the Remote Config backend and automatically fetches updates from the RC backend when they are available. |
3132
| [setCustomSignals(remoteConfig, customSignals)](./remote-config.md#setcustomsignals_aeeb95e) | Sets the custom signals for the app instance. |
3233
| [setLogLevel(remoteConfig, logLevel)](./remote-config.md#setloglevel_039a45b) | Defines the log level to use. |
3334
| <b>function()</b> |
@@ -37,6 +38,8 @@ The Firebase Remote Config Web SDK. This SDK does not work in a Node.js environm
3738

3839
| Interface | Description |
3940
| --- | --- |
41+
| [ConfigUpdate](./remote-config.configupdate.md#configupdate_interface) | Contains information about which keys have been updated. |
42+
| [ConfigUpdateObserver](./remote-config.configupdateobserver.md#configupdateobserver_interface) | Observer interface for receiving real-time Remote Config update notifications.<!-- -->NOTE: Although an <code>complete</code> callback can be provided, it will never be called because the ConfigUpdate stream is never-ending. |
4043
| [CustomSignals](./remote-config.customsignals.md#customsignals_interface) | Defines the type for representing custom signals and their values.<p>The values in CustomSignals must be one of the following types:<ul> <li><code>string</code> <li><code>number</code> <li><code>null</code> </ul> |
4144
| [FetchResponse](./remote-config.fetchresponse.md#fetchresponse_interface) | Defines a successful response (200 or 304).<p>Modeled after the native <code>Response</code> interface, but simplified for Remote Config's use case. |
4245
| [FirebaseRemoteConfigObject](./remote-config.firebaseremoteconfigobject.md#firebaseremoteconfigobject_interface) | Defines a self-descriptive reference for config key-value pairs. |
@@ -50,7 +53,9 @@ The Firebase Remote Config Web SDK. This SDK does not work in a Node.js environm
5053
| Type Alias | Description |
5154
| --- | --- |
5255
| [FetchStatus](./remote-config.md#fetchstatus) | Summarizes the outcome of the last attempt to fetch config from the Firebase Remote Config server.<ul> <li>"no-fetch-yet" indicates the [RemoteConfig](./remote-config.remoteconfig.md#remoteconfig_interface) instance has not yet attempted to fetch config, or that SDK initialization is incomplete.</li> <li>"success" indicates the last attempt succeeded.</li> <li>"failure" indicates the last attempt failed.</li> <li>"throttle" indicates the last attempt was rate-limited.</li> </ul> |
56+
| [FetchType](./remote-config.md#fetchtype) | Indicates the type of fetch request.<ul> <li>"BASE" indicates a standard fetch request.</li> <li>"REALTIME" indicates a fetch request triggered by a real-time update.</li> </ul> |
5357
| [LogLevel](./remote-config.md#loglevel) | Defines levels of Remote Config logging. |
58+
| [Unsubscribe](./remote-config.md#unsubscribe) | A function that unsubscribes from a real-time event stream. |
5459
| [ValueSource](./remote-config.md#valuesource) | Indicates the source of a value.<ul> <li>"static" indicates the value was defined by a static constant.</li> <li>"default" indicates the value was defined by default config.</li> <li>"remote" indicates the value was defined by fetched config.</li> </ul> |
5560

5661
## function(app, ...)
@@ -282,6 +287,31 @@ export declare function getValue(remoteConfig: RemoteConfig, key: string): Value
282287

283288
The value for the given key.
284289

290+
### onConfigUpdate(remoteConfig, observer) {:#onconfigupdate_8b13b26}
291+
292+
Starts listening for real-time config updates from the Remote Config backend and automatically fetches updates from the RC backend when they are available.
293+
294+
If a connection to the Remote Config backend is not already open, calling this method will open it. Multiple listeners can be added by calling this method again, but subsequent calls re-use the same connection to the backend.
295+
296+
<b>Signature:</b>
297+
298+
```typescript
299+
export declare function onConfigUpdate(remoteConfig: RemoteConfig, observer: ConfigUpdateObserver): Unsubscribe;
300+
```
301+
302+
#### Parameters
303+
304+
| Parameter | Type | Description |
305+
| --- | --- | --- |
306+
| remoteConfig | [RemoteConfig](./remote-config.remoteconfig.md#remoteconfig_interface) | The [RemoteConfig](./remote-config.remoteconfig.md#remoteconfig_interface) instance. |
307+
| observer | [ConfigUpdateObserver](./remote-config.configupdateobserver.md#configupdateobserver_interface) | The [ConfigUpdateObserver](./remote-config.configupdateobserver.md#configupdateobserver_interface) to be notified of config updates. |
308+
309+
<b>Returns:</b>
310+
311+
[Unsubscribe](./remote-config.md#unsubscribe)
312+
313+
An [Unsubscribe](./remote-config.md#unsubscribe) function to remove the listener.
314+
285315
### setCustomSignals(remoteConfig, customSignals) {:#setcustomsignals_aeeb95e}
286316

287317
Sets the custom signals for the app instance.
@@ -355,6 +385,18 @@ Summarizes the outcome of the last attempt to fetch config from the Firebase Rem
355385
export type FetchStatus = 'no-fetch-yet' | 'success' | 'failure' | 'throttle';
356386
```
357387

388+
## FetchType
389+
390+
Indicates the type of fetch request.
391+
392+
<ul> <li>"BASE" indicates a standard fetch request.</li> <li>"REALTIME" indicates a fetch request triggered by a real-time update.</li> </ul>
393+
394+
<b>Signature:</b>
395+
396+
```typescript
397+
export type FetchType = 'BASE' | 'REALTIME';
398+
```
399+
358400
## LogLevel
359401

360402
Defines levels of Remote Config logging.
@@ -365,6 +407,16 @@ Defines levels of Remote Config logging.
365407
export type LogLevel = 'debug' | 'error' | 'silent';
366408
```
367409

410+
## Unsubscribe
411+
412+
A function that unsubscribes from a real-time event stream.
413+
414+
<b>Signature:</b>
415+
416+
```typescript
417+
export type Unsubscribe = () => void;
418+
```
419+
368420
## ValueSource
369421

370422
Indicates the source of a value.

packages/remote-config/src/api.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import {
2222
LogLevel as RemoteConfigLogLevel,
2323
RemoteConfig,
2424
Value,
25-
RemoteConfigOptions
25+
RemoteConfigOptions,
26+
ConfigUpdateObserver,
27+
Unsubscribe
2628
} from './public_types';
2729
import { RemoteConfigAbortSignal } from './client/remote_config_fetch_client';
2830
import {
@@ -66,6 +68,9 @@ export function getRemoteConfig(
6668
rc._initializePromise = Promise.all([
6769
rc._storage.setLastSuccessfulFetchResponse(options.initialFetchResponse),
6870
rc._storage.setActiveConfigEtag(options.initialFetchResponse?.eTag || ''),
71+
rc._storage.setActiveConfigTemplateVersion(
72+
options.initialFetchResponse.templateVersion || 0
73+
),
6974
rc._storageCache.setLastSuccessfulFetchTimestampMillis(Date.now()),
7075
rc._storageCache.setLastFetchStatus('success'),
7176
rc._storageCache.setActiveConfig(
@@ -98,6 +103,7 @@ export async function activate(remoteConfig: RemoteConfig): Promise<boolean> {
98103
!lastSuccessfulFetchResponse ||
99104
!lastSuccessfulFetchResponse.config ||
100105
!lastSuccessfulFetchResponse.eTag ||
106+
!lastSuccessfulFetchResponse.templateVersion ||
101107
lastSuccessfulFetchResponse.eTag === activeConfigEtag
102108
) {
103109
// Either there is no successful fetched config, or is the same as current active
@@ -106,7 +112,10 @@ export async function activate(remoteConfig: RemoteConfig): Promise<boolean> {
106112
}
107113
await Promise.all([
108114
rc._storageCache.setActiveConfig(lastSuccessfulFetchResponse.config),
109-
rc._storage.setActiveConfigEtag(lastSuccessfulFetchResponse.eTag)
115+
rc._storage.setActiveConfigEtag(lastSuccessfulFetchResponse.eTag),
116+
rc._storage.setActiveConfigTemplateVersion(
117+
lastSuccessfulFetchResponse.templateVersion
118+
)
110119
]);
111120
return true;
112121
}
@@ -351,3 +360,30 @@ export async function setCustomSignals(
351360
);
352361
}
353362
}
363+
364+
// TODO: Add public document for the Remote Config Realtime API guide on the Web Platform.
365+
/**
366+
* Starts listening for real-time config updates from the Remote Config backend and automatically
367+
* fetches updates from the RC backend when they are available.
368+
*
369+
* @remarks
370+
* If a connection to the Remote Config backend is not already open, calling this method will
371+
* open it. Multiple listeners can be added by calling this method again, but subsequent calls
372+
* re-use the same connection to the backend.
373+
*
374+
* @param remoteConfig - The {@link RemoteConfig} instance.
375+
* @param observer - The {@link ConfigUpdateObserver} to be notified of config updates.
376+
* @returns An {@link Unsubscribe} function to remove the listener.
377+
*
378+
* @public
379+
*/
380+
export function onConfigUpdate(
381+
remoteConfig: RemoteConfig,
382+
observer: ConfigUpdateObserver
383+
): Unsubscribe {
384+
const rc = getModularInstance(remoteConfig) as RemoteConfigImpl;
385+
rc._realtimeHandler.addObserver(observer);
386+
return () => {
387+
rc._realtimeHandler.removeObserver(observer);
388+
};
389+
}

0 commit comments

Comments
 (0)