Skip to content

Commit 4cfd43b

Browse files
Add support to set/update custom signals
- Also takes care of setting the signals in the fetch request
1 parent e577a40 commit 4cfd43b

File tree

8 files changed

+130
-5
lines changed

8 files changed

+130
-5
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ import { FirebaseApp } from '@firebase/app';
99
// @public
1010
export function activate(remoteConfig: RemoteConfig): Promise<boolean>;
1111

12+
// @alpha
13+
export type CustomSignals = {
14+
[key: string]: string | number;
15+
};
16+
1217
// @public
1318
export function ensureInitialized(remoteConfig: RemoteConfig): Promise<void>;
1419

@@ -62,6 +67,11 @@ export interface RemoteConfigSettings {
6267
minimumFetchIntervalMillis: number;
6368
}
6469

70+
// Warning: (ae-incompatible-release-tags) The symbol "setCustomSignals" is marked as @public, but its signature references "CustomSignals" which is marked as @alpha
71+
//
72+
// @public (undocumented)
73+
export function setCustomSignals(remoteConfig: RemoteConfig, customSignals: CustomSignals): Promise<void>;
74+
6575
// @public
6676
export function setLogLevel(remoteConfig: RemoteConfig, logLevel: LogLevel): void;
6777

packages/remote-config-types/index.d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,20 @@ export type FetchStatus = 'no-fetch-yet' | 'success' | 'failure' | 'throttle';
173173
*/
174174
export type LogLevel = 'debug' | 'error' | 'silent';
175175

176+
/**
177+
* Defines the type for representing custom signals and their values.
178+
*
179+
* <p>The values in CustomSignals must be one of the following types:
180+
*
181+
* <ul>
182+
* <li><code>string</code>
183+
* <li><code>number</code>
184+
* </ul>
185+
*
186+
* @alpha
187+
*/
188+
export type CustomSignals = {[key: string]: string | number};
189+
176190
declare module '@firebase/component' {
177191
interface NameServiceMapping {
178192
'remoteConfig-compat': RemoteConfig;

packages/remote-config/src/api.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import { _getProvider, FirebaseApp, getApp } from '@firebase/app';
1919
import {
20+
CustomSignals,
2021
LogLevel as RemoteConfigLogLevel,
2122
RemoteConfig,
2223
Value
@@ -108,6 +109,7 @@ export async function fetchConfig(remoteConfig: RemoteConfig): Promise<void> {
108109
// * it applies to all retries (like curl's max-time arg)
109110
// * it is consistent with the Fetch API's signal input
110111
const abortSignal = new RemoteConfigAbortSignal();
112+
const customSignals = rc._storageCache.getCustomSignals();
111113

112114
setTimeout(async () => {
113115
// Note a very low delay, eg < 10ms, can elapse before listeners are initialized.
@@ -118,7 +120,8 @@ export async function fetchConfig(remoteConfig: RemoteConfig): Promise<void> {
118120
try {
119121
await rc._client.fetch({
120122
cacheMaxAgeMillis: rc.settings.minimumFetchIntervalMillis,
121-
signal: abortSignal
123+
signal: abortSignal,
124+
customSignals: rc._storageCache.getCustomSignals()
122125
});
123126

124127
await rc._storageCache.setLastFetchStatus('success');
@@ -258,3 +261,9 @@ export function setLogLevel(
258261
function getAllKeys(obj1: {} = {}, obj2: {} = {}): string[] {
259262
return Object.keys({ ...obj1, ...obj2 });
260263
}
264+
265+
export async function setCustomSignals(
266+
remoteConfig: RemoteConfig, customSignals: CustomSignals): Promise<void> {
267+
const rc = getModularInstance(remoteConfig) as RemoteConfigImpl;
268+
return rc._storageCache.setCustomSignals(customSignals);
269+
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { CustomSignals } from "../public_types";
19+
1820
/**
1921
* Defines a client, as in https://en.wikipedia.org/wiki/Client%E2%80%93server_model, for the
2022
* Remote Config server (https://firebase.google.com/docs/reference/remote-config/rest).
@@ -99,6 +101,13 @@ export interface FetchRequest {
99101
* <p>Comparable to passing `headers = { 'If-None-Match': <eTag> }` to the native Fetch API.
100102
*/
101103
eTag?: string;
104+
105+
106+
/** The custom signals stored for the app instance.
107+
*
108+
* <p>Optional in case no custom signals are set for the instance.
109+
*/
110+
customSignals?: CustomSignals;
102111
}
103112

104113
/**

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { CustomSignals } from '../public_types';
1819
import {
1920
FetchResponse,
2021
RemoteConfigFetchClient,
@@ -41,6 +42,7 @@ interface FetchRequestBody {
4142
app_instance_id_token: string;
4243
app_id: string;
4344
language_code: string;
45+
custom_signals?: CustomSignals;
4446
/* eslint-enable camelcase */
4547
}
4648

@@ -92,7 +94,8 @@ export class RestClient implements RemoteConfigFetchClient {
9294
app_instance_id: installationId,
9395
app_instance_id_token: installationToken,
9496
app_id: this.appId,
95-
language_code: getUserLanguage()
97+
language_code: getUserLanguage(),
98+
custom_signals: request.customSignals
9699
/* eslint-enable camelcase */
97100
};
98101

packages/remote-config/src/public_types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,20 @@ export type FetchStatus = 'no-fetch-yet' | 'success' | 'failure' | 'throttle';
134134
*/
135135
export type LogLevel = 'debug' | 'error' | 'silent';
136136

137+
/**
138+
* Defines the type for representing custom signals and their values.
139+
*
140+
* <p>The values in CustomSignals must be one of the following types:
141+
*
142+
* <ul>
143+
* <li><code>string</code>
144+
* <li><code>number</code>
145+
* </ul>
146+
*
147+
* @alpha
148+
*/
149+
export type CustomSignals = {[key: string]: string | number};
150+
137151
declare module '@firebase/component' {
138152
interface NameServiceMapping {
139153
'remote-config': RemoteConfig;

packages/remote-config/src/storage/storage.ts

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

18-
import { FetchStatus } from '@firebase/remote-config-types';
18+
import { FetchStatus, CustomSignals } from '@firebase/remote-config-types';
1919
import {
2020
FetchResponse,
2121
FirebaseRemoteConfigObject
@@ -70,7 +70,8 @@ type ProjectNamespaceKeyFieldValue =
7070
| 'last_successful_fetch_timestamp_millis'
7171
| 'last_successful_fetch_response'
7272
| 'settings'
73-
| 'throttle_metadata';
73+
| 'throttle_metadata'
74+
| 'custom_signals';
7475

7576
// Visible for testing.
7677
export function openDatabase(): Promise<IDBDatabase> {
@@ -181,6 +182,53 @@ export class Storage {
181182
return this.delete('throttle_metadata');
182183
}
183184

185+
getCustomSignals(): Promise<CustomSignals | undefined> {
186+
return this.get<CustomSignals>('custom_signals');
187+
}
188+
189+
async setCustomSignals(customSignals: CustomSignals): Promise<void> {
190+
const db = await this.openDbPromise;
191+
return new Promise((resolve, reject) => {
192+
const transaction = db.transaction([APP_NAMESPACE_STORE], 'readwrite');
193+
const objectStore = transaction.objectStore(APP_NAMESPACE_STORE);
194+
const compositeKey = this.createCompositeKey("custom_signals");
195+
try {
196+
const storedSignalsRequest = objectStore.get(compositeKey);
197+
storedSignalsRequest.onerror = event => {
198+
reject(toFirebaseError(event, ErrorCode.STORAGE_GET));
199+
};
200+
storedSignalsRequest.onsuccess = event => {
201+
const storedSignals = (event.target as IDBRequest).result.value;
202+
const combinedSignals = {
203+
...storedSignals,
204+
...customSignals
205+
};
206+
const signalsToUpdate = Object.fromEntries(Object.entries(combinedSignals).filter(([_, v]) => v !== null));
207+
if (signalsToUpdate) {
208+
const setSignalsRequest = objectStore.put({compositeKey, signalsToUpdate});
209+
setSignalsRequest.onerror = event => {
210+
reject(toFirebaseError(event, ErrorCode.STORAGE_SET));
211+
};
212+
setSignalsRequest.onsuccess = event => {
213+
const result = (event.target as IDBRequest).result;
214+
if (result) {
215+
resolve(result.value);
216+
} else {
217+
resolve(undefined);
218+
}
219+
};
220+
}
221+
};
222+
} catch (e) {
223+
reject(
224+
ERROR_FACTORY.create(ErrorCode.STORAGE_SET, {
225+
originalErrorMessage: (e as Error)?.message
226+
})
227+
);
228+
}
229+
});
230+
}
231+
184232
async get<T>(key: ProjectNamespaceKeyFieldValue): Promise<T | undefined> {
185233
const db = await this.openDbPromise;
186234
return new Promise((resolve, reject) => {

packages/remote-config/src/storage/storage_cache.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { FetchStatus } from '@firebase/remote-config-types';
18+
import { FetchStatus, CustomSignals } from '@firebase/remote-config-types';
1919
import { FirebaseRemoteConfigObject } from '../client/remote_config_fetch_client';
2020
import { Storage } from './storage';
2121

@@ -31,6 +31,8 @@ export class StorageCache {
3131
private lastFetchStatus?: FetchStatus;
3232
private lastSuccessfulFetchTimestampMillis?: number;
3333
private activeConfig?: FirebaseRemoteConfigObject;
34+
private customSignals?: CustomSignals;
35+
3436

3537
/**
3638
* Memory-only getters
@@ -47,6 +49,11 @@ export class StorageCache {
4749
return this.activeConfig;
4850
}
4951

52+
getCustomSignals(): CustomSignals | undefined {
53+
return this.customSignals;
54+
}
55+
56+
5057
/**
5158
* Read-ahead getter
5259
*/
@@ -55,6 +62,7 @@ export class StorageCache {
5562
const lastSuccessfulFetchTimestampMillisPromise =
5663
this.storage.getLastSuccessfulFetchTimestampMillis();
5764
const activeConfigPromise = this.storage.getActiveConfig();
65+
const customSignalsPromise = this.storage.getCustomSignals();
5866

5967
// Note:
6068
// 1. we consistently check for undefined to avoid clobbering defined values
@@ -78,6 +86,11 @@ export class StorageCache {
7886
if (activeConfig) {
7987
this.activeConfig = activeConfig;
8088
}
89+
90+
const customSignals = await customSignalsPromise;
91+
if (customSignals) {
92+
this.customSignals = customSignals;
93+
}
8194
}
8295

8396
/**
@@ -99,4 +112,9 @@ export class StorageCache {
99112
this.activeConfig = activeConfig;
100113
return this.storage.setActiveConfig(activeConfig);
101114
}
115+
116+
setCustomSignals(customSignals: CustomSignals): Promise<void> {
117+
this.customSignals = customSignals;
118+
return this.storage.setCustomSignals(customSignals);
119+
}
102120
}

0 commit comments

Comments
 (0)