Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions common/api-review/remote-config.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function fetchConfig(remoteConfig: RemoteConfig): Promise<void>;
export interface FetchResponse {
config?: FirebaseRemoteConfigObject;
eTag?: string;
experiments?: FirebaseExperimentDescription[];
status: number;
templateVersion?: number;
}
Expand All @@ -51,6 +52,22 @@ export type FetchStatus = 'no-fetch-yet' | 'success' | 'failure' | 'throttle';
// @public
export type FetchType = 'BASE' | 'REALTIME';

// @public
export interface FirebaseExperimentDescription {
// (undocumented)
affectedParameterKeys?: string[];
// (undocumented)
experimentId: string;
// (undocumented)
experimentStartTime: string;
// (undocumented)
timeToLiveMillis: string;
// (undocumented)
triggerTimeoutMillis: string;
// (undocumented)
variantId: string;
}

// @public
export interface FirebaseRemoteConfigObject {
// (undocumented)
Expand Down
2 changes: 2 additions & 0 deletions docs-devsite/_toc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,8 @@ toc:
path: /docs/reference/js/remote-config.customsignals.md
- title: FetchResponse
path: /docs/reference/js/remote-config.fetchresponse.md
- title: FirebaseExperimentDescription
path: /docs/reference/js/remote-config.firebaseexperimentdescription.md
- title: FirebaseRemoteConfigObject
path: /docs/reference/js/remote-config.firebaseremoteconfigobject.md
- title: RemoteConfig
Expand Down
13 changes: 13 additions & 0 deletions docs-devsite/remote-config.fetchresponse.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface FetchResponse
| --- | --- | --- |
| [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. |
| [eTag](./remote-config.fetchresponse.md#fetchresponseetag) | string | Defines the ETag response header value.<p>Only defined for 200 and 304 responses. |
| [experiments](./remote-config.fetchresponse.md#fetchresponseexperiments) | [FirebaseExperimentDescription](./remote-config.firebaseexperimentdescription.md#firebaseexperimentdescription_interface)<!-- -->\[\] | A/B Test and Rollout experiment metadata. |
| [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. |
| [templateVersion](./remote-config.fetchresponse.md#fetchresponsetemplateversion) | number | The version number of the config template fetched from the server. |

Expand Down Expand Up @@ -53,6 +54,18 @@ Defines the ETag response header value.
eTag?: string;
```

## FetchResponse.experiments

A/B Test and Rollout experiment metadata.

Only defined for 200 responses.

<b>Signature:</b>

```typescript
experiments?: FirebaseExperimentDescription[];
```

## FetchResponse.status

The HTTP status, which is useful for differentiating success responses with data from those without.
Expand Down
78 changes: 78 additions & 0 deletions docs-devsite/remote-config.firebaseexperimentdescription.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
Project: /docs/reference/js/_project.yaml
Book: /docs/reference/_book.yaml
page_type: reference

{% comment %}
DO NOT EDIT THIS FILE!
This is generated by the JS SDK team, and any local changes will be
overwritten. Changes should be made in the source code at
https://github.com/firebase/firebase-js-sdk
{% endcomment %}

# FirebaseExperimentDescription interface
Defines experiment and variant attached to a config parameter.

<b>Signature:</b>

```typescript
export interface FirebaseExperimentDescription
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [affectedParameterKeys](./remote-config.firebaseexperimentdescription.md#firebaseexperimentdescriptionaffectedparameterkeys) | string\[\] | |
| [experimentId](./remote-config.firebaseexperimentdescription.md#firebaseexperimentdescriptionexperimentid) | string | |
| [experimentStartTime](./remote-config.firebaseexperimentdescription.md#firebaseexperimentdescriptionexperimentstarttime) | string | |
| [timeToLiveMillis](./remote-config.firebaseexperimentdescription.md#firebaseexperimentdescriptiontimetolivemillis) | string | |
| [triggerTimeoutMillis](./remote-config.firebaseexperimentdescription.md#firebaseexperimentdescriptiontriggertimeoutmillis) | string | |
| [variantId](./remote-config.firebaseexperimentdescription.md#firebaseexperimentdescriptionvariantid) | string | |

## FirebaseExperimentDescription.affectedParameterKeys

<b>Signature:</b>

```typescript
affectedParameterKeys?: string[];
```

## FirebaseExperimentDescription.experimentId

<b>Signature:</b>

```typescript
experimentId: string;
```

## FirebaseExperimentDescription.experimentStartTime

<b>Signature:</b>

```typescript
experimentStartTime: string;
```

## FirebaseExperimentDescription.timeToLiveMillis

<b>Signature:</b>

```typescript
timeToLiveMillis: string;
```

## FirebaseExperimentDescription.triggerTimeoutMillis

<b>Signature:</b>

```typescript
triggerTimeoutMillis: string;
```

## FirebaseExperimentDescription.variantId

<b>Signature:</b>

```typescript
variantId: string;
```
1 change: 1 addition & 0 deletions docs-devsite/remote-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ The Firebase Remote Config Web SDK. This SDK does not work in a Node.js environm
| [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. |
| [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> |
| [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. |
| [FirebaseExperimentDescription](./remote-config.firebaseexperimentdescription.md#firebaseexperimentdescription_interface) | Defines experiment and variant attached to a config parameter. |
| [FirebaseRemoteConfigObject](./remote-config.firebaseremoteconfigobject.md#firebaseremoteconfigobject_interface) | Defines a self-descriptive reference for config key-value pairs. |
| [RemoteConfig](./remote-config.remoteconfig.md#remoteconfig_interface) | The Firebase Remote Config service interface. |
| [RemoteConfigOptions](./remote-config.remoteconfigoptions.md#remoteconfigoptions_interface) | Options for Remote Config initialization. |
Expand Down
8 changes: 6 additions & 2 deletions packages/remote-config/src/client/rest_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
import {
CustomSignals,
FetchResponse,
FirebaseRemoteConfigObject
FirebaseRemoteConfigObject,
FirebaseExperimentDescription
} from '../public_types';
import {
RemoteConfigFetchClient,
Expand Down Expand Up @@ -143,6 +144,7 @@ export class RestClient implements RemoteConfigFetchClient {
let config: FirebaseRemoteConfigObject | undefined;
let state: string | undefined;
let templateVersion: number | undefined;
let experiments: FirebaseExperimentDescription[] | undefined;

// JSON parsing throws SyntaxError if the response body isn't a JSON string.
// Requesting application/json and checking for a 200 ensures there's JSON data.
Expand All @@ -158,6 +160,7 @@ export class RestClient implements RemoteConfigFetchClient {
config = responseBody['entries'];
state = responseBody['state'];
templateVersion = responseBody['templateVersion'];
experiments = responseBody['experimentDescriptions'];
}

// Normalizes based on legacy state.
Expand All @@ -168,6 +171,7 @@ export class RestClient implements RemoteConfigFetchClient {
} else if (state === 'NO_TEMPLATE' || state === 'EMPTY_CONFIG') {
// These cases can be fixed remotely, so normalize to safe value.
config = {};
experiments = [];
}

// Normalize to exception-based control flow for non-success cases.
Expand All @@ -180,6 +184,6 @@ export class RestClient implements RemoteConfigFetchClient {
});
}

return { status, eTag: responseEtag, config, templateVersion };
return { status, eTag: responseEtag, config, templateVersion, experiments };
}
}
36 changes: 34 additions & 2 deletions packages/remote-config/src/public_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,34 @@ export interface FirebaseRemoteConfigObject {
[key: string]: string;
}

/**
* Defines experiment and variant attached to a config parameter.
*
* @public
*/
export interface FirebaseExperimentDescription {
// A string of max length 22 characters and of format: _exp_<experiment_id>
experimentId: string;

// The variant of the experiment assigned to the app instance.
variantId: string;

// When the experiment was started.
experimentStartTime: string;

// How long the experiment can remain in STANDBY state. Valid range from 1 ms
// to 6 months.
triggerTimeoutMillis: string;

// How long the experiment can remain in ON state. Valid range from 1 ms to 6
// months.
timeToLiveMillis: string;

// A repeated of Remote Config parameter keys that this experiment is
// affecting the value of.
affectedParameterKeys?: string[];
}

/**
* Defines a successful response (200 or 304).
*
Expand Down Expand Up @@ -99,8 +127,12 @@ export interface FetchResponse {
*/
templateVersion?: number;

// Note: we're not extracting experiment metadata until
// ABT and Analytics have Web SDKs.
/**
* A/B Test and Rollout experiment metadata.
*
* @remarks Only defined for 200 responses.
*/
experiments?: FirebaseExperimentDescription[];
}

/**
Expand Down
14 changes: 12 additions & 2 deletions packages/remote-config/test/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,16 @@ describe('Remote Config API', () => {
status: 200,
eTag: 'asdf',
config: { 'foobar': 'hello world' },
templateVersion: 1
templateVersion: 1,
experiments: [
{
experimentId: '_exp_1',
variantId: '1',
experimentStartTime: '2025-04-06T14:13:57.597Z',
triggerTimeoutMillis: '15552000000',
timeToLiveMillis: '15552000000'
}
]
};
let fetchStub: sinon.SinonStub;

Expand Down Expand Up @@ -106,7 +115,8 @@ describe('Remote Config API', () => {
Promise.resolve({
entries: response.config,
state: 'OK',
templateVersion: response.templateVersion
templateVersion: response.templateVersion,
experimentDescriptions: response.experiments
})
} as Response)
);
Expand Down
26 changes: 20 additions & 6 deletions packages/remote-config/test/client/rest_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,16 @@ describe('RestClient', () => {
eTag: 'etag',
state: 'UPDATE',
entries: { color: 'sparkling' },
templateVersion: 1
templateVersion: 1,
experimentDescriptions: [
{
experimentId: '_exp_1',
variantId: '1',
experimentStartTime: '2025-04-06T14:13:57.597Z',
triggerTimeoutMillis: '15552000000',
timeToLiveMillis: '15552000000'
}
]
};

fetchStub.returns(
Expand All @@ -90,7 +99,8 @@ describe('RestClient', () => {
Promise.resolve({
entries: expectedResponse.entries,
state: expectedResponse.state,
templateVersion: expectedResponse.templateVersion
templateVersion: expectedResponse.templateVersion,
experimentDescriptions: expectedResponse.experimentDescriptions
})
} as Response)
);
Expand All @@ -101,7 +111,8 @@ describe('RestClient', () => {
status: expectedResponse.status,
eTag: expectedResponse.eTag,
config: expectedResponse.entries,
templateVersion: expectedResponse.templateVersion
templateVersion: expectedResponse.templateVersion,
experiments: expectedResponse.experimentDescriptions
});
});

Expand Down Expand Up @@ -191,7 +202,8 @@ describe('RestClient', () => {
status: 304,
eTag: 'response-etag',
config: undefined,
templateVersion: undefined
templateVersion: undefined,
experiments: undefined
});
});

Expand Down Expand Up @@ -230,7 +242,8 @@ describe('RestClient', () => {
status: 304,
eTag: 'etag',
config: undefined,
templateVersion: undefined
templateVersion: undefined,
experiments: undefined
});
});

Expand All @@ -248,7 +261,8 @@ describe('RestClient', () => {
status: 200,
eTag: 'etag',
config: {},
templateVersion: undefined
templateVersion: undefined,
experiments: []
});
}
});
Expand Down
15 changes: 13 additions & 2 deletions packages/remote-config/test/remote_config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,15 @@ describe('RemoteConfig', () => {
const CONFIG = { key: 'val' };
const NEW_ETAG = 'new_etag';
const TEMPLATE_VERSION = 1;
const EXPERIMENTS = [
{
'experimentId': '_exp_1',
'variantId': '1',
'experimentStartTime': '2025-04-06T14:13:57.597Z',
'triggerTimeoutMillis': '15552000000',
'timeToLiveMillis': '15552000000'
}
];

let getLastSuccessfulFetchResponseStub: sinon.SinonStub;
let getActiveConfigEtagStub: sinon.SinonStub;
Expand Down Expand Up @@ -456,7 +465,8 @@ describe('RemoteConfig', () => {
Promise.resolve({
config: CONFIG,
eTag: NEW_ETAG,
templateVersion: TEMPLATE_VERSION
templateVersion: TEMPLATE_VERSION,
experiments: EXPERIMENTS
})
);
getActiveConfigEtagStub.returns(Promise.resolve(ETAG));
Expand All @@ -476,7 +486,8 @@ describe('RemoteConfig', () => {
Promise.resolve({
config: CONFIG,
eTag: NEW_ETAG,
templateVersion: TEMPLATE_VERSION
templateVersion: TEMPLATE_VERSION,
experiments: EXPERIMENTS
})
);
getActiveConfigEtagStub.returns(Promise.resolve());
Expand Down
Loading