@metamask-previews/remote-feature-flag-controller 7.0.0-preview-c1f9a0f → 7.0.0-preview-01e391e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -9,14 +9,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Added
11
11
 
12
+ - Add `ClientConfigApiServiceInvalidateQueriesAction`, `ClientConfigApiServiceCacheUpdatedEvent` and `ClientConfigApiServiceGranularCacheUpdatedEvent`, also included in the new `ClientConfigApiServiceActions` and `ClientConfigApiServiceEvents` types ([#11217](https://github.com/MetaMask/core/pull/11217))
13
+ - Add `ClientConfigApiServiceMessenger`, `ClientConfigApiServiceFetchRemoteFeatureFlagsAction` and `ClientConfigApiServiceMethodActions` exports ([#11217](https://github.com/MetaMask/core/pull/11217))
12
14
  - Add support for Node.js 26 ([#10981](https://github.com/MetaMask/core/pull/10981))
13
15
 
14
16
  ### Changed
15
17
 
18
+ - **BREAKING:** `ClientConfigApiService` now extends `BaseDataService` from `@metamask/base-data-service` ([#11217](https://github.com/MetaMask/core/pull/11217))
19
+ - The constructor now requires a `messenger` (`ClientConfigApiServiceMessenger`). Messengers built from `ClientConfigApiServiceActions` and `ClientConfigApiServiceEvents` pick up the new `ClientConfigApiService:invalidateQueries` action and `ClientConfigApiService:cacheUpdated` events automatically.
20
+ - The `retries`, `maximumConsecutiveFailures`, `circuitBreakDuration`, `onBreak` and `onDegraded` constructor options are replaced by `policyOptions` (`maxRetries`, `maxConsecutiveFailures`, `circuitBreakDuration`). The defaults are unchanged.
21
+ - `fetch` and `config` are still required.
22
+ - **BREAKING:** `RemoteFeatureFlagController` no longer takes a `clientConfigApiService` option, and now calls `ClientConfigApiService:fetchRemoteFeatureFlags` through its messenger ([#11217](https://github.com/MetaMask/core/pull/11217))
23
+ - `RemoteFeatureFlagControllerMessenger` now requires that action to be delegated to it. Construct a `ClientConfigApiService` with its own messenger and delegate the action to the controller's messenger.
24
+ - Concurrent calls to `ClientConfigApiService.fetchRemoteFeatureFlags` now share a single request ([#11217](https://github.com/MetaMask/core/pull/11217))
25
+ - Every call still hits the network, so a sequential call always refetches.
26
+ - `ClientConfigApiService.fetchRemoteFeatureFlags` now treats a non 2xx response as a failure inside its request policy ([#11217](https://github.com/MetaMask/core/pull/11217))
27
+ - Previously the status was checked after the policy ran, so a 4xx or 5xx response was not retried and did not count toward the circuit breaker. Now it throws an `HttpError` (`Failed to fetch remote feature flags: <status>`), so 5xx responses are retried and repeated failures open the circuit breaker.
28
+ - `ClientConfigApiService.fetchRemoteFeatureFlags` now validates the response with a struct ([#11217](https://github.com/MetaMask/core/pull/11217))
29
+ - A response that is not an array of objects throws `Query function for "ClientConfigApiService:fetchRemoteFeatureFlags" returned an unexpected response: ...` instead of `Feature flags api did not return an array`. Arrays containing non object items, which used to be ignored, now fail too.
30
+ - Add dependency `@metamask/base-data-service` ([#11217](https://github.com/MetaMask/core/pull/11217))
31
+ - Add dependency `@metamask/superstruct` ([#11217](https://github.com/MetaMask/core/pull/11217))
16
32
  - Bump `uuid` from `^8.3.2` to `^14.0.0` ([#10117](https://github.com/MetaMask/core/pull/10117), [#10243](https://github.com/MetaMask/core/pull/10243), [#10941](https://github.com/MetaMask/core/pull/10941))
17
33
  - Bump `@metamask/utils` from `^11.12.0` to `^12.0.1` ([#10192](https://github.com/MetaMask/core/pull/10192), [#11107](https://github.com/MetaMask/core/pull/11107))
18
34
  - Bump `@metamask/base-controller` from `^10.0.0` to `^10.1.0` ([#10864](https://github.com/MetaMask/core/pull/10864))
19
35
 
36
+ ### Removed
37
+
38
+ - **BREAKING:** Remove `onBreak` and `onDegraded` from `ClientConfigApiService` ([#11217](https://github.com/MetaMask/core/pull/11217))
39
+ - **BREAKING:** Remove the `AbstractClientConfigApiService` type ([#11217](https://github.com/MetaMask/core/pull/11217))
40
+
41
+ ### Fixed
42
+
43
+ - Correct the `ClientConfigApiService.fetchRemoteFeatureFlags` documentation, which claimed a structured error object and a cached data fallback. The method throws on failure, and `RemoteFeatureFlagController` keeps its existing state when it does ([#11217](https://github.com/MetaMask/core/pull/11217))
44
+
20
45
  ## [7.0.0]
21
46
 
22
47
  ### Changed
@@ -0,0 +1,23 @@
1
+ /**
2
+ * This file is auto generated.
3
+ * Do not edit manually.
4
+ */
5
+ import type { ClientConfigApiService } from './client-config-api-service.js';
6
+ /**
7
+ * Fetches feature flags from the API with specific client, distribution, and
8
+ * environment parameters. Always requests fresh data from the network, and
9
+ * concurrent calls share a single request. Throws if the request fails or
10
+ * the response is not an array of flag objects.
11
+ *
12
+ * @returns The feature flags merged into a single object, and the time they
13
+ * were fetched.
14
+ */
15
+ export type ClientConfigApiServiceFetchRemoteFeatureFlagsAction = {
16
+ type: `ClientConfigApiService:fetchRemoteFeatureFlags`;
17
+ handler: ClientConfigApiService['fetchRemoteFeatureFlags'];
18
+ };
19
+ /**
20
+ * Union of all ClientConfigApiService action types.
21
+ */
22
+ export type ClientConfigApiServiceMethodActions = ClientConfigApiServiceFetchRemoteFeatureFlagsAction;
23
+ //# sourceMappingURL=client-config-api-service-method-action-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-config-api-service-method-action-types.d.ts","sourceRoot":"","sources":["../../src/client-config-api-service/client-config-api-service-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAE7E;;;;;;;;GAQG;AACH,MAAM,MAAM,mDAAmD,GAAG;IAChE,IAAI,EAAE,gDAAgD,CAAC;IACvD,OAAO,EAAE,sBAAsB,CAAC,yBAAyB,CAAC,CAAC;CAC5D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mCAAmC,GAC7C,mDAAmD,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * This file is auto generated.
3
+ * Do not edit manually.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=client-config-api-service-method-action-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-config-api-service-method-action-types.js","sourceRoot":"","sources":["../../src/client-config-api-service/client-config-api-service-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { ClientConfigApiService } from './client-config-api-service.js';\n\n/**\n * Fetches feature flags from the API with specific client, distribution, and\n * environment parameters. Always requests fresh data from the network, and\n * concurrent calls share a single request. Throws if the request fails or\n * the response is not an array of flag objects.\n *\n * @returns The feature flags merged into a single object, and the time they\n * were fetched.\n */\nexport type ClientConfigApiServiceFetchRemoteFeatureFlagsAction = {\n type: `ClientConfigApiService:fetchRemoteFeatureFlags`;\n handler: ClientConfigApiService['fetchRemoteFeatureFlags'];\n};\n\n/**\n * Union of all ClientConfigApiService action types.\n */\nexport type ClientConfigApiServiceMethodActions =\n ClientConfigApiServiceFetchRemoteFeatureFlagsAction;\n"]}
@@ -1,26 +1,68 @@
1
- import type { ServicePolicy } from '@metamask/controller-utils';
2
- import type { IDisposable } from 'cockatiel';
1
+ import { BaseDataService } from '@metamask/base-data-service';
2
+ import type { CreateServicePolicyOptions, DataServiceCacheUpdatedEvent, DataServiceGranularCacheUpdatedEvent, DataServiceInvalidateQueriesAction } from '@metamask/base-data-service';
3
+ import type { Messenger } from '@metamask/messenger';
3
4
  import type { ClientType, DistributionType, EnvironmentType, ServiceResponse } from '../remote-feature-flag-controller-types.js';
4
- import type { AbstractClientConfigApiService } from './abstract-client-config-api-service.js';
5
+ import type { ClientConfigApiServiceMethodActions } from './client-config-api-service-method-action-types.js';
5
6
  /**
6
- * This service is responsible for fetching feature flags from the ClientConfig API.
7
+ * The name of the {@link ClientConfigApiService}, used to namespace the
8
+ * service's actions and events.
7
9
  */
8
- export declare class ClientConfigApiService implements AbstractClientConfigApiService {
10
+ export declare const serviceName = "ClientConfigApiService";
11
+ /**
12
+ * Invalidates cached queries for {@link ClientConfigApiService}.
13
+ */
14
+ export type ClientConfigApiServiceInvalidateQueriesAction = DataServiceInvalidateQueriesAction<typeof serviceName>;
15
+ /**
16
+ * Actions that {@link ClientConfigApiService} exposes to other consumers.
17
+ */
18
+ export type ClientConfigApiServiceActions = ClientConfigApiServiceMethodActions | ClientConfigApiServiceInvalidateQueriesAction;
19
+ /**
20
+ * Actions from other messengers that {@link ClientConfigApiServiceMessenger}
21
+ * calls.
22
+ */
23
+ type AllowedActions = never;
24
+ /**
25
+ * Published when {@link ClientConfigApiService}'s cache is updated.
26
+ */
27
+ export type ClientConfigApiServiceCacheUpdatedEvent = DataServiceCacheUpdatedEvent<typeof serviceName>;
28
+ /**
29
+ * Published when a key within {@link ClientConfigApiService}'s cache is
30
+ * updated.
31
+ */
32
+ export type ClientConfigApiServiceGranularCacheUpdatedEvent = DataServiceGranularCacheUpdatedEvent<typeof serviceName>;
33
+ /**
34
+ * Events that {@link ClientConfigApiService} exposes to other consumers.
35
+ */
36
+ export type ClientConfigApiServiceEvents = ClientConfigApiServiceCacheUpdatedEvent | ClientConfigApiServiceGranularCacheUpdatedEvent;
37
+ /**
38
+ * Events from other messengers that {@link ClientConfigApiService} subscribes
39
+ * to.
40
+ */
41
+ type AllowedEvents = never;
42
+ /**
43
+ * The messenger restricted to actions and events accessed by
44
+ * {@link ClientConfigApiService}.
45
+ */
46
+ export type ClientConfigApiServiceMessenger = Messenger<typeof serviceName, ClientConfigApiServiceActions | AllowedActions, ClientConfigApiServiceEvents | AllowedEvents>;
47
+ /**
48
+ * This service is responsible for fetching feature flags from the ClientConfig
49
+ * API.
50
+ *
51
+ * Its {@link ClientConfigApiService.fetchRemoteFeatureFlags} method is
52
+ * automatically registered on the messenger so that
53
+ * `RemoteFeatureFlagController` can call it.
54
+ */
55
+ export declare class ClientConfigApiService extends BaseDataService<typeof serviceName, ClientConfigApiServiceMessenger> {
9
56
  #private;
10
57
  /**
11
58
  * Constructs a new ClientConfigApiService object.
12
59
  *
13
60
  * @param args - The arguments.
61
+ * @param args.messenger - The messenger suited for this service.
14
62
  * @param args.fetch - A function that can be used to make an HTTP request.
15
63
  * If your JavaScript environment supports `fetch` natively, you'll probably
16
64
  * want to pass that; otherwise you can pass an equivalent (such as `fetch`
17
65
  * via `node-fetch`).
18
- * @param args.retries - Number of retry attempts for each fetch request.
19
- * @param args.maximumConsecutiveFailures - The maximum number of consecutive
20
- * failures allowed before breaking the circuit and pausing further fetch
21
- * attempts.
22
- * @param args.circuitBreakDuration - The amount of time to wait when the
23
- * circuit breaks from too many consecutive failures.
24
66
  * @param args.config - The configuration object, includes client,
25
67
  * distribution, and environment.
26
68
  * @param args.config.client - The client type (e.g., 'extension', 'mobile').
@@ -28,81 +70,29 @@ export declare class ClientConfigApiService implements AbstractClientConfigApiSe
28
70
  * 'flask').
29
71
  * @param args.config.environment - The environment type (e.g., 'prod', 'rc',
30
72
  * 'dev').
73
+ * @param args.policyOptions - Options to pass to `createServicePolicy`, which
74
+ * is used to wrap each request. See {@link CreateServicePolicyOptions}.
31
75
  */
32
- constructor(args: {
76
+ constructor({ messenger, fetch: fetchFunction, config, policyOptions, }: {
77
+ messenger: ClientConfigApiServiceMessenger;
33
78
  fetch: typeof fetch;
34
- retries?: number;
35
- maximumConsecutiveFailures?: number;
36
- circuitBreakDuration?: number;
37
79
  config: {
38
80
  client: ClientType;
39
81
  distribution: DistributionType;
40
82
  environment: EnvironmentType;
41
83
  };
84
+ policyOptions?: CreateServicePolicyOptions;
42
85
  });
43
86
  /**
44
- * Constructs a new ClientConfigApiService object.
45
- *
46
- * @deprecated This signature is deprecated; please use the `onBreak` and
47
- * `onDegraded` methods instead.
48
- * @param args - The arguments.
49
- * @param args.fetch - A function that can be used to make an HTTP request.
50
- * If your JavaScript environment supports `fetch` natively, you'll probably
51
- * want to pass that; otherwise you can pass an equivalent (such as `fetch`
52
- * via `node-fetch`).
53
- * @param args.retries - Number of retry attempts for each fetch request.
54
- * @param args.maximumConsecutiveFailures - The maximum number of consecutive
55
- * failures allowed before breaking the circuit and pausing further fetch
56
- * attempts.
57
- * @param args.circuitBreakDuration - The amount of time to wait when the
58
- * circuit breaks from too many consecutive failures.
59
- * @param args.onBreak - Callback for when the circuit breaks, useful
60
- * for capturing metrics about network failures.
61
- * @param args.onDegraded - Callback for when the API responds successfully
62
- * but takes too long to respond (5 seconds or more).
63
- * @param args.config - The configuration object, includes client,
64
- * distribution, and environment.
65
- * @param args.config.client - The client type (e.g., 'extension', 'mobile').
66
- * @param args.config.distribution - The distribution type (e.g., 'main',
67
- * 'flask').
68
- * @param args.config.environment - The environment type (e.g., 'prod', 'rc',
69
- * 'dev').
70
- */
71
- constructor(args: {
72
- fetch: typeof fetch;
73
- retries?: number;
74
- maximumConsecutiveFailures?: number;
75
- circuitBreakDuration?: number;
76
- onBreak?: () => void;
77
- onDegraded?: () => void;
78
- config: {
79
- client: ClientType;
80
- distribution: DistributionType;
81
- environment: EnvironmentType;
82
- };
83
- });
84
- /**
85
- * Listens for when the request to the API fails too many times in a row.
86
- *
87
- * @param args - The same arguments that {@link ServicePolicy.onBreak}
88
- * takes.
89
- * @returns What {@link ServicePolicy.onBreak} returns.
90
- */
91
- onBreak(...args: Parameters<ServicePolicy['onBreak']>): IDisposable;
92
- /**
93
- * Listens for when the API is degraded.
94
- *
95
- * @param args - The same arguments that {@link ServicePolicy.onDegraded}
96
- * takes.
97
- * @returns What {@link ServicePolicy.onDegraded} returns.
98
- */
99
- onDegraded(...args: Parameters<ServicePolicy['onDegraded']>): IDisposable;
100
- /**
101
- * Fetches feature flags from the API with specific client, distribution, and environment parameters.
102
- * Provides structured error handling, including fallback to cached data if available.
87
+ * Fetches feature flags from the API with specific client, distribution, and
88
+ * environment parameters. Always requests fresh data from the network, and
89
+ * concurrent calls share a single request. Throws if the request fails or
90
+ * the response is not an array of flag objects.
103
91
  *
104
- * @returns An object of feature flags and their boolean values or a structured error object.
92
+ * @returns The feature flags merged into a single object, and the time they
93
+ * were fetched.
105
94
  */
106
95
  fetchRemoteFeatureFlags(): Promise<ServiceResponse>;
107
96
  }
97
+ export {};
108
98
  //# sourceMappingURL=client-config-api-service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client-config-api-service.d.ts","sourceRoot":"","sources":["../../src/client-config-api-service/client-config-api-service.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7C,OAAO,KAAK,EAEV,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,eAAe,EAEhB,MAAM,4CAA4C,CAAC;AACpD,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,yCAAyC,CAAC;AAE9F;;GAEG;AACH,qBAAa,sBAAuB,YAAW,8BAA8B;;IAW3E;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,YAAY,IAAI,EAAE;QAChB,KAAK,EAAE,OAAO,KAAK,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,0BAA0B,CAAC,EAAE,MAAM,CAAC;QACpC,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAC9B,MAAM,EAAE;YACN,MAAM,EAAE,UAAU,CAAC;YACnB,YAAY,EAAE,gBAAgB,CAAC;YAC/B,WAAW,EAAE,eAAe,CAAC;SAC9B,CAAC;KACH,EAAE;IAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IAEH,YAAY,IAAI,EAAE;QAChB,KAAK,EAAE,OAAO,KAAK,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,0BAA0B,CAAC,EAAE,MAAM,CAAC;QACpC,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAC9B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;QACxB,MAAM,EAAE;YACN,MAAM,EAAE,UAAU,CAAC;YACnB,YAAY,EAAE,gBAAgB,CAAC;YAC/B,WAAW,EAAE,eAAe,CAAC;SAC9B,CAAC;KACH,EAAE;IAyCH;;;;;;OAMG;IACH,OAAO,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,GAAG,WAAW,CAElE;IAED;;;;;;OAMG;IACH,UAAU,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,GAAG,WAAW,CAExE;IAED;;;;;OAKG;IACU,uBAAuB,IAAI,OAAO,CAAC,eAAe,CAAC,CAyB/D;CAgBF"}
1
+ {"version":3,"file":"client-config-api-service.d.ts","sourceRoot":"","sources":["../../src/client-config-api-service/client-config-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EACV,0BAA0B,EAC1B,4BAA4B,EAC5B,oCAAoC,EACpC,kCAAkC,EACnC,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAKrD,OAAO,KAAK,EAEV,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,eAAe,EAChB,MAAM,4CAA4C,CAAC;AACpD,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,oDAAoD,CAAC;AAI9G;;;GAGG;AACH,eAAO,MAAM,WAAW,2BAA2B,CAAC;AAYpD;;GAEG;AACH,MAAM,MAAM,6CAA6C,GACvD,kCAAkC,CAAC,OAAO,WAAW,CAAC,CAAC;AAEzD;;GAEG;AACH,MAAM,MAAM,6BAA6B,GACrC,mCAAmC,GACnC,6CAA6C,CAAC;AAElD;;;GAGG;AACH,KAAK,cAAc,GAAG,KAAK,CAAC;AAE5B;;GAEG;AACH,MAAM,MAAM,uCAAuC,GACjD,4BAA4B,CAAC,OAAO,WAAW,CAAC,CAAC;AAEnD;;;GAGG;AACH,MAAM,MAAM,+CAA+C,GACzD,oCAAoC,CAAC,OAAO,WAAW,CAAC,CAAC;AAE3D;;GAEG;AACH,MAAM,MAAM,4BAA4B,GACpC,uCAAuC,GACvC,+CAA+C,CAAC;AAEpD;;;GAGG;AACH,KAAK,aAAa,GAAG,KAAK,CAAC;AAE3B;;;GAGG;AACH,MAAM,MAAM,+BAA+B,GAAG,SAAS,CACrD,OAAO,WAAW,EAClB,6BAA6B,GAAG,cAAc,EAC9C,4BAA4B,GAAG,aAAa,CAC7C,CAAC;AAIF;;;;;;;GAOG;AACH,qBAAa,sBAAuB,SAAQ,eAAe,CACzD,OAAO,WAAW,EAClB,+BAA+B,CAChC;;IASC;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,EACV,SAAS,EACT,KAAK,EAAE,aAAa,EACpB,MAAM,EACN,aAAkB,GACnB,EAAE;QACD,SAAS,EAAE,+BAA+B,CAAC;QAC3C,KAAK,EAAE,OAAO,KAAK,CAAC;QACpB,MAAM,EAAE;YACN,MAAM,EAAE,UAAU,CAAC;YACnB,YAAY,EAAE,gBAAgB,CAAC;YAC/B,WAAW,EAAE,eAAe,CAAC;SAC9B,CAAC;QACF,aAAa,CAAC,EAAE,0BAA0B,CAAC;KAC5C,EAYA;IAED;;;;;;;;OAQG;IACG,uBAAuB,IAAI,OAAO,CAAC,eAAe,CAAC,CA4BxD;CAgBF"}
@@ -1,70 +1,88 @@
1
- import { createServicePolicy, DEFAULT_CIRCUIT_BREAK_DURATION, DEFAULT_MAX_CONSECUTIVE_FAILURES, DEFAULT_MAX_RETRIES, } from '@metamask/controller-utils';
1
+ import { BaseDataService } from '@metamask/base-data-service';
2
+ import { HttpError } from '@metamask/controller-utils';
3
+ import { array, record, string } from '@metamask/superstruct';
4
+ import { JsonStruct } from '@metamask/utils';
2
5
  import { BASE_URL } from '../constants.js';
6
+ // === GENERAL ===
3
7
  /**
4
- * This service is responsible for fetching feature flags from the ClientConfig API.
8
+ * The name of the {@link ClientConfigApiService}, used to namespace the
9
+ * service's actions and events.
5
10
  */
6
- export class ClientConfigApiService {
11
+ export const serviceName = 'ClientConfigApiService';
12
+ /**
13
+ * Describes the shape of the ClientConfig API response: an array of objects,
14
+ * each holding feature flag key value pairs.
15
+ */
16
+ const FeatureFlagsResponseStruct = array(record(string(), JsonStruct));
17
+ // === MESSENGER ===
18
+ const MESSENGER_EXPOSED_METHODS = ['fetchRemoteFeatureFlags'];
19
+ // === SERVICE DEFINITION ===
20
+ /**
21
+ * This service is responsible for fetching feature flags from the ClientConfig
22
+ * API.
23
+ *
24
+ * Its {@link ClientConfigApiService.fetchRemoteFeatureFlags} method is
25
+ * automatically registered on the messenger so that
26
+ * `RemoteFeatureFlagController` can call it.
27
+ */
28
+ export class ClientConfigApiService extends BaseDataService {
7
29
  #fetch;
8
- #policy;
9
30
  #client;
10
31
  #distribution;
11
32
  #environment;
12
- constructor({ fetch: fetchFunction, retries = DEFAULT_MAX_RETRIES, maximumConsecutiveFailures = DEFAULT_MAX_CONSECUTIVE_FAILURES, circuitBreakDuration = DEFAULT_CIRCUIT_BREAK_DURATION, onBreak, onDegraded, config, }) {
33
+ /**
34
+ * Constructs a new ClientConfigApiService object.
35
+ *
36
+ * @param args - The arguments.
37
+ * @param args.messenger - The messenger suited for this service.
38
+ * @param args.fetch - A function that can be used to make an HTTP request.
39
+ * If your JavaScript environment supports `fetch` natively, you'll probably
40
+ * want to pass that; otherwise you can pass an equivalent (such as `fetch`
41
+ * via `node-fetch`).
42
+ * @param args.config - The configuration object, includes client,
43
+ * distribution, and environment.
44
+ * @param args.config.client - The client type (e.g., 'extension', 'mobile').
45
+ * @param args.config.distribution - The distribution type (e.g., 'main',
46
+ * 'flask').
47
+ * @param args.config.environment - The environment type (e.g., 'prod', 'rc',
48
+ * 'dev').
49
+ * @param args.policyOptions - Options to pass to `createServicePolicy`, which
50
+ * is used to wrap each request. See {@link CreateServicePolicyOptions}.
51
+ */
52
+ constructor({ messenger, fetch: fetchFunction, config, policyOptions = {}, }) {
53
+ super({ name: serviceName, messenger, policyOptions });
13
54
  this.#fetch = fetchFunction;
14
55
  this.#client = config.client;
15
56
  this.#distribution = config.distribution;
16
57
  this.#environment = config.environment;
17
- this.#policy = createServicePolicy({
18
- maxRetries: retries,
19
- maxConsecutiveFailures: maximumConsecutiveFailures,
20
- circuitBreakDuration,
21
- });
22
- if (onBreak) {
23
- this.#policy.onBreak(onBreak);
24
- }
25
- if (onDegraded) {
26
- this.#policy.onDegraded(onDegraded);
27
- }
58
+ this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
28
59
  }
29
60
  /**
30
- * Listens for when the request to the API fails too many times in a row.
61
+ * Fetches feature flags from the API with specific client, distribution, and
62
+ * environment parameters. Always requests fresh data from the network, and
63
+ * concurrent calls share a single request. Throws if the request fails or
64
+ * the response is not an array of flag objects.
31
65
  *
32
- * @param args - The same arguments that {@link ServicePolicy.onBreak}
33
- * takes.
34
- * @returns What {@link ServicePolicy.onBreak} returns.
35
- */
36
- onBreak(...args) {
37
- return this.#policy.onBreak(...args);
38
- }
39
- /**
40
- * Listens for when the API is degraded.
41
- *
42
- * @param args - The same arguments that {@link ServicePolicy.onDegraded}
43
- * takes.
44
- * @returns What {@link ServicePolicy.onDegraded} returns.
45
- */
46
- onDegraded(...args) {
47
- return this.#policy.onDegraded(...args);
48
- }
49
- /**
50
- * Fetches feature flags from the API with specific client, distribution, and environment parameters.
51
- * Provides structured error handling, including fallback to cached data if available.
52
- *
53
- * @returns An object of feature flags and their boolean values or a structured error object.
66
+ * @returns The feature flags merged into a single object, and the time they
67
+ * were fetched.
54
68
  */
55
69
  async fetchRemoteFeatureFlags() {
56
70
  const url = `${BASE_URL}/flags?client=${this.#client}&distribution=${this.#distribution}&environment=${this.#environment}`;
57
- const response = await this.#policy.execute(() => this.#fetch(url, { cache: 'no-cache' }));
58
- if (!response.ok) {
59
- throw new Error('Failed to fetch remote feature flags');
60
- }
61
- const data = await response.json();
62
- if (!Array.isArray(data)) {
63
- throw new Error('Feature flags api did not return an array');
64
- }
65
- const remoteFeatureFlags = this.#flattenFeatureFlags(data);
71
+ const data = await this.fetchQuery({
72
+ queryKey: [`${this.name}:fetchRemoteFeatureFlags`],
73
+ staleTime: 0,
74
+ gcTime: 0,
75
+ responseStruct: FeatureFlagsResponseStruct,
76
+ queryFn: async () => {
77
+ const response = await this.#fetch(url, { cache: 'no-cache' });
78
+ if (!response.ok) {
79
+ throw new HttpError(response.status, `Failed to fetch remote feature flags: ${response.status}`);
80
+ }
81
+ return (await response.json());
82
+ },
83
+ });
66
84
  return {
67
- remoteFeatureFlags,
85
+ remoteFeatureFlags: this.#flattenFeatureFlags(data),
68
86
  cacheTimestamp: Date.now(),
69
87
  };
70
88
  }
@@ -1 +1 @@
1
- {"version":3,"file":"client-config-api-service.js","sourceRoot":"","sources":["../../src/client-config-api-service/client-config-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,8BAA8B,EAC9B,gCAAgC,EAChC,mBAAmB,GACpB,MAAM,4BAA4B,CAAC;AAIpC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAW3C;;GAEG;AACH,MAAM,OAAO,sBAAsB;IACxB,MAAM,CAAe;IAErB,OAAO,CAAgB;IAEvB,OAAO,CAAa;IAEpB,aAAa,CAAmB;IAEhC,YAAY,CAAkB;IA+EvC,YAAY,EACV,KAAK,EAAE,aAAa,EACpB,OAAO,GAAG,mBAAmB,EAC7B,0BAA0B,GAAG,gCAAgC,EAC7D,oBAAoB,GAAG,8BAA8B,EACrD,OAAO,EACP,UAAU,EACV,MAAM,GAaP;QACC,IAAI,CAAC,MAAM,GAAG,aAAa,CAAC;QAC5B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC;QACzC,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC;QAEvC,IAAI,CAAC,OAAO,GAAG,mBAAmB,CAAC;YACjC,UAAU,EAAE,OAAO;YACnB,sBAAsB,EAAE,0BAA0B;YAClD,oBAAoB;SACrB,CAAC,CAAC;QACH,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QACD,IAAI,UAAU,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,GAAG,IAA0C;QACnD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IACvC,CAAC;IAED;;;;;;OAMG;IACH,UAAU,CAAC,GAAG,IAA6C;QACzD,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,uBAAuB;QAClC,MAAM,GAAG,GAAG,GAAG,QAAQ,iBAAiB,IAAI,CAAC,OAAO,iBAClD,IAAI,CAAC,aACP,gBAAgB,IAAI,CAAC,YAAY,EAAE,CAAC;QAEpC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAC/C,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CACxC,CAAC;QAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC1D,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAEnC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAC/D,CAAC;QAED,MAAM,kBAAkB,GAAG,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;QAE3D,OAAO;YACL,kBAAkB;YAClB,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE;SAC3B,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACH,oBAAoB,CAAC,YAA6B;QAChD,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;YACvC,OAAO,EAAE,GAAG,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;QAC7B,CAAC,EAAE,EAAE,CAAC,CAAC;IACT,CAAC;CACF","sourcesContent":["import {\n createServicePolicy,\n DEFAULT_CIRCUIT_BREAK_DURATION,\n DEFAULT_MAX_CONSECUTIVE_FAILURES,\n DEFAULT_MAX_RETRIES,\n} from '@metamask/controller-utils';\nimport type { ServicePolicy } from '@metamask/controller-utils';\nimport type { IDisposable } from 'cockatiel';\n\nimport { BASE_URL } from '../constants.js';\nimport type {\n FeatureFlags,\n ClientType,\n DistributionType,\n EnvironmentType,\n ServiceResponse,\n ApiDataResponse,\n} from '../remote-feature-flag-controller-types.js';\nimport type { AbstractClientConfigApiService } from './abstract-client-config-api-service.js';\n\n/**\n * This service is responsible for fetching feature flags from the ClientConfig API.\n */\nexport class ClientConfigApiService implements AbstractClientConfigApiService {\n readonly #fetch: typeof fetch;\n\n readonly #policy: ServicePolicy;\n\n readonly #client: ClientType;\n\n readonly #distribution: DistributionType;\n\n readonly #environment: EnvironmentType;\n\n /**\n * Constructs a new ClientConfigApiService object.\n *\n * @param args - The arguments.\n * @param args.fetch - A function that can be used to make an HTTP request.\n * If your JavaScript environment supports `fetch` natively, you'll probably\n * want to pass that; otherwise you can pass an equivalent (such as `fetch`\n * via `node-fetch`).\n * @param args.retries - Number of retry attempts for each fetch request.\n * @param args.maximumConsecutiveFailures - The maximum number of consecutive\n * failures allowed before breaking the circuit and pausing further fetch\n * attempts.\n * @param args.circuitBreakDuration - The amount of time to wait when the\n * circuit breaks from too many consecutive failures.\n * @param args.config - The configuration object, includes client,\n * distribution, and environment.\n * @param args.config.client - The client type (e.g., 'extension', 'mobile').\n * @param args.config.distribution - The distribution type (e.g., 'main',\n * 'flask').\n * @param args.config.environment - The environment type (e.g., 'prod', 'rc',\n * 'dev').\n */\n constructor(args: {\n fetch: typeof fetch;\n retries?: number;\n maximumConsecutiveFailures?: number;\n circuitBreakDuration?: number;\n config: {\n client: ClientType;\n distribution: DistributionType;\n environment: EnvironmentType;\n };\n });\n\n /**\n * Constructs a new ClientConfigApiService object.\n *\n * @deprecated This signature is deprecated; please use the `onBreak` and\n * `onDegraded` methods instead.\n * @param args - The arguments.\n * @param args.fetch - A function that can be used to make an HTTP request.\n * If your JavaScript environment supports `fetch` natively, you'll probably\n * want to pass that; otherwise you can pass an equivalent (such as `fetch`\n * via `node-fetch`).\n * @param args.retries - Number of retry attempts for each fetch request.\n * @param args.maximumConsecutiveFailures - The maximum number of consecutive\n * failures allowed before breaking the circuit and pausing further fetch\n * attempts.\n * @param args.circuitBreakDuration - The amount of time to wait when the\n * circuit breaks from too many consecutive failures.\n * @param args.onBreak - Callback for when the circuit breaks, useful\n * for capturing metrics about network failures.\n * @param args.onDegraded - Callback for when the API responds successfully\n * but takes too long to respond (5 seconds or more).\n * @param args.config - The configuration object, includes client,\n * distribution, and environment.\n * @param args.config.client - The client type (e.g., 'extension', 'mobile').\n * @param args.config.distribution - The distribution type (e.g., 'main',\n * 'flask').\n * @param args.config.environment - The environment type (e.g., 'prod', 'rc',\n * 'dev').\n */\n // eslint-disable-next-line @typescript-eslint/unified-signatures\n constructor(args: {\n fetch: typeof fetch;\n retries?: number;\n maximumConsecutiveFailures?: number;\n circuitBreakDuration?: number;\n onBreak?: () => void;\n onDegraded?: () => void;\n config: {\n client: ClientType;\n distribution: DistributionType;\n environment: EnvironmentType;\n };\n });\n\n constructor({\n fetch: fetchFunction,\n retries = DEFAULT_MAX_RETRIES,\n maximumConsecutiveFailures = DEFAULT_MAX_CONSECUTIVE_FAILURES,\n circuitBreakDuration = DEFAULT_CIRCUIT_BREAK_DURATION,\n onBreak,\n onDegraded,\n config,\n }: {\n fetch: typeof fetch;\n retries?: number;\n maximumConsecutiveFailures?: number;\n circuitBreakDuration?: number;\n onBreak?: () => void;\n onDegraded?: () => void;\n config: {\n client: ClientType;\n distribution: DistributionType;\n environment: EnvironmentType;\n };\n }) {\n this.#fetch = fetchFunction;\n this.#client = config.client;\n this.#distribution = config.distribution;\n this.#environment = config.environment;\n\n this.#policy = createServicePolicy({\n maxRetries: retries,\n maxConsecutiveFailures: maximumConsecutiveFailures,\n circuitBreakDuration,\n });\n if (onBreak) {\n this.#policy.onBreak(onBreak);\n }\n if (onDegraded) {\n this.#policy.onDegraded(onDegraded);\n }\n }\n\n /**\n * Listens for when the request to the API fails too many times in a row.\n *\n * @param args - The same arguments that {@link ServicePolicy.onBreak}\n * takes.\n * @returns What {@link ServicePolicy.onBreak} returns.\n */\n onBreak(...args: Parameters<ServicePolicy['onBreak']>): IDisposable {\n return this.#policy.onBreak(...args);\n }\n\n /**\n * Listens for when the API is degraded.\n *\n * @param args - The same arguments that {@link ServicePolicy.onDegraded}\n * takes.\n * @returns What {@link ServicePolicy.onDegraded} returns.\n */\n onDegraded(...args: Parameters<ServicePolicy['onDegraded']>): IDisposable {\n return this.#policy.onDegraded(...args);\n }\n\n /**\n * Fetches feature flags from the API with specific client, distribution, and environment parameters.\n * Provides structured error handling, including fallback to cached data if available.\n *\n * @returns An object of feature flags and their boolean values or a structured error object.\n */\n public async fetchRemoteFeatureFlags(): Promise<ServiceResponse> {\n const url = `${BASE_URL}/flags?client=${this.#client}&distribution=${\n this.#distribution\n }&environment=${this.#environment}`;\n\n const response = await this.#policy.execute(() =>\n this.#fetch(url, { cache: 'no-cache' }),\n );\n\n if (!response.ok) {\n throw new Error('Failed to fetch remote feature flags');\n }\n\n const data = await response.json();\n\n if (!Array.isArray(data)) {\n throw new Error('Feature flags api did not return an array');\n }\n\n const remoteFeatureFlags = this.#flattenFeatureFlags(data);\n\n return {\n remoteFeatureFlags,\n cacheTimestamp: Date.now(),\n };\n }\n\n /**\n * Flattens an array of feature flag objects into a single feature flags object.\n *\n * @param responseData - Array of objects containing feature flag key-value pairs\n * @returns A single object containing all feature flags merged together\n * @example\n * // Input: [{ flag1: true }, { flag2: [] }]\n * // Output: { flag1: true, flag2: [] }\n */\n #flattenFeatureFlags(responseData: ApiDataResponse): FeatureFlags {\n return responseData.reduce((acc, curr) => {\n return { ...acc, ...curr };\n }, {});\n }\n}\n"]}
1
+ {"version":3,"file":"client-config-api-service.js","sourceRoot":"","sources":["../../src/client-config-api-service/client-config-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAO9D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAEvD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAU3C,kBAAkB;AAElB;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,wBAAwB,CAAC;AAEpD;;;GAGG;AACH,MAAM,0BAA0B,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC;AAEvE,oBAAoB;AAEpB,MAAM,yBAAyB,GAAG,CAAC,yBAAyB,CAAU,CAAC;AAyDvE,6BAA6B;AAE7B;;;;;;;GAOG;AACH,MAAM,OAAO,sBAAuB,SAAQ,eAG3C;IACU,MAAM,CAAe;IAErB,OAAO,CAAa;IAEpB,aAAa,CAAmB;IAEhC,YAAY,CAAkB;IAEvC;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,EACV,SAAS,EACT,KAAK,EAAE,aAAa,EACpB,MAAM,EACN,aAAa,GAAG,EAAE,GAUnB;QACC,KAAK,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC,CAAC;QAEvD,IAAI,CAAC,MAAM,GAAG,aAAa,CAAC;QAC5B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC;QACzC,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC;QAEvC,IAAI,CAAC,SAAS,CAAC,4BAA4B,CACzC,IAAI,EACJ,yBAAyB,CAC1B,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,uBAAuB;QAC3B,MAAM,GAAG,GAAG,GAAG,QAAQ,iBAAiB,IAAI,CAAC,OAAO,iBAClD,IAAI,CAAC,aACP,gBAAgB,IAAI,CAAC,YAAY,EAAE,CAAC;QAEpC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC;YACjC,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,0BAA0B,CAAC;YAClD,SAAS,EAAE,CAAC;YACZ,MAAM,EAAE,CAAC;YACT,cAAc,EAAE,0BAA0B;YAC1C,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;gBAE/D,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;oBACjB,MAAM,IAAI,SAAS,CACjB,QAAQ,CAAC,MAAM,EACf,yCAAyC,QAAQ,CAAC,MAAM,EAAE,CAC3D,CAAC;gBACJ,CAAC;gBAED,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAmB,CAAC;YACnD,CAAC;SACF,CAAC,CAAC;QAEH,OAAO;YACL,kBAAkB,EAAE,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC;YACnD,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE;SAC3B,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACH,oBAAoB,CAAC,YAA4B;QAC/C,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;YACvC,OAAO,EAAE,GAAG,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;QAC7B,CAAC,EAAE,EAAE,CAAC,CAAC;IACT,CAAC;CACF","sourcesContent":["import { BaseDataService } from '@metamask/base-data-service';\nimport type {\n CreateServicePolicyOptions,\n DataServiceCacheUpdatedEvent,\n DataServiceGranularCacheUpdatedEvent,\n DataServiceInvalidateQueriesAction,\n} from '@metamask/base-data-service';\nimport { HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport { array, record, string } from '@metamask/superstruct';\nimport { JsonStruct } from '@metamask/utils';\n\nimport { BASE_URL } from '../constants.js';\nimport type {\n FeatureFlags,\n ClientType,\n DistributionType,\n EnvironmentType,\n ServiceResponse,\n} from '../remote-feature-flag-controller-types.js';\nimport type { ClientConfigApiServiceMethodActions } from './client-config-api-service-method-action-types.js';\n\n// === GENERAL ===\n\n/**\n * The name of the {@link ClientConfigApiService}, used to namespace the\n * service's actions and events.\n */\nexport const serviceName = 'ClientConfigApiService';\n\n/**\n * Describes the shape of the ClientConfig API response: an array of objects,\n * each holding feature flag key value pairs.\n */\nconst FeatureFlagsResponseStruct = array(record(string(), JsonStruct));\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = ['fetchRemoteFeatureFlags'] as const;\n\n/**\n * Invalidates cached queries for {@link ClientConfigApiService}.\n */\nexport type ClientConfigApiServiceInvalidateQueriesAction =\n DataServiceInvalidateQueriesAction<typeof serviceName>;\n\n/**\n * Actions that {@link ClientConfigApiService} exposes to other consumers.\n */\nexport type ClientConfigApiServiceActions =\n | ClientConfigApiServiceMethodActions\n | ClientConfigApiServiceInvalidateQueriesAction;\n\n/**\n * Actions from other messengers that {@link ClientConfigApiServiceMessenger}\n * calls.\n */\ntype AllowedActions = never;\n\n/**\n * Published when {@link ClientConfigApiService}'s cache is updated.\n */\nexport type ClientConfigApiServiceCacheUpdatedEvent =\n DataServiceCacheUpdatedEvent<typeof serviceName>;\n\n/**\n * Published when a key within {@link ClientConfigApiService}'s cache is\n * updated.\n */\nexport type ClientConfigApiServiceGranularCacheUpdatedEvent =\n DataServiceGranularCacheUpdatedEvent<typeof serviceName>;\n\n/**\n * Events that {@link ClientConfigApiService} exposes to other consumers.\n */\nexport type ClientConfigApiServiceEvents =\n | ClientConfigApiServiceCacheUpdatedEvent\n | ClientConfigApiServiceGranularCacheUpdatedEvent;\n\n/**\n * Events from other messengers that {@link ClientConfigApiService} subscribes\n * to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger restricted to actions and events accessed by\n * {@link ClientConfigApiService}.\n */\nexport type ClientConfigApiServiceMessenger = Messenger<\n typeof serviceName,\n ClientConfigApiServiceActions | AllowedActions,\n ClientConfigApiServiceEvents | AllowedEvents\n>;\n\n// === SERVICE DEFINITION ===\n\n/**\n * This service is responsible for fetching feature flags from the ClientConfig\n * API.\n *\n * Its {@link ClientConfigApiService.fetchRemoteFeatureFlags} method is\n * automatically registered on the messenger so that\n * `RemoteFeatureFlagController` can call it.\n */\nexport class ClientConfigApiService extends BaseDataService<\n typeof serviceName,\n ClientConfigApiServiceMessenger\n> {\n readonly #fetch: typeof fetch;\n\n readonly #client: ClientType;\n\n readonly #distribution: DistributionType;\n\n readonly #environment: EnvironmentType;\n\n /**\n * Constructs a new ClientConfigApiService object.\n *\n * @param args - The arguments.\n * @param args.messenger - The messenger suited for this service.\n * @param args.fetch - A function that can be used to make an HTTP request.\n * If your JavaScript environment supports `fetch` natively, you'll probably\n * want to pass that; otherwise you can pass an equivalent (such as `fetch`\n * via `node-fetch`).\n * @param args.config - The configuration object, includes client,\n * distribution, and environment.\n * @param args.config.client - The client type (e.g., 'extension', 'mobile').\n * @param args.config.distribution - The distribution type (e.g., 'main',\n * 'flask').\n * @param args.config.environment - The environment type (e.g., 'prod', 'rc',\n * 'dev').\n * @param args.policyOptions - Options to pass to `createServicePolicy`, which\n * is used to wrap each request. See {@link CreateServicePolicyOptions}.\n */\n constructor({\n messenger,\n fetch: fetchFunction,\n config,\n policyOptions = {},\n }: {\n messenger: ClientConfigApiServiceMessenger;\n fetch: typeof fetch;\n config: {\n client: ClientType;\n distribution: DistributionType;\n environment: EnvironmentType;\n };\n policyOptions?: CreateServicePolicyOptions;\n }) {\n super({ name: serviceName, messenger, policyOptions });\n\n this.#fetch = fetchFunction;\n this.#client = config.client;\n this.#distribution = config.distribution;\n this.#environment = config.environment;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Fetches feature flags from the API with specific client, distribution, and\n * environment parameters. Always requests fresh data from the network, and\n * concurrent calls share a single request. Throws if the request fails or\n * the response is not an array of flag objects.\n *\n * @returns The feature flags merged into a single object, and the time they\n * were fetched.\n */\n async fetchRemoteFeatureFlags(): Promise<ServiceResponse> {\n const url = `${BASE_URL}/flags?client=${this.#client}&distribution=${\n this.#distribution\n }&environment=${this.#environment}`;\n\n const data = await this.fetchQuery({\n queryKey: [`${this.name}:fetchRemoteFeatureFlags`],\n staleTime: 0,\n gcTime: 0,\n responseStruct: FeatureFlagsResponseStruct,\n queryFn: async () => {\n const response = await this.#fetch(url, { cache: 'no-cache' });\n\n if (!response.ok) {\n throw new HttpError(\n response.status,\n `Failed to fetch remote feature flags: ${response.status}`,\n );\n }\n\n return (await response.json()) as FeatureFlags[];\n },\n });\n\n return {\n remoteFeatureFlags: this.#flattenFeatureFlags(data),\n cacheTimestamp: Date.now(),\n };\n }\n\n /**\n * Flattens an array of feature flag objects into a single feature flags object.\n *\n * @param responseData - Array of objects containing feature flag key-value pairs\n * @returns A single object containing all feature flags merged together\n * @example\n * // Input: [{ flag1: true }, { flag2: [] }]\n * // Output: { flag1: true, flag2: [] }\n */\n #flattenFeatureFlags(responseData: FeatureFlags[]): FeatureFlags {\n return responseData.reduce((acc, curr) => {\n return { ...acc, ...curr };\n }, {});\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -4,5 +4,7 @@ export type { RemoteFeatureFlagControllerClearAllFlagOverridesAction, RemoteFeat
4
4
  export { ClientType, DistributionType, EnvironmentType, ThresholdVersion, } from './remote-feature-flag-controller-types.js';
5
5
  export type { FeatureFlags } from './remote-feature-flag-controller-types.js';
6
6
  export { ClientConfigApiService } from './client-config-api-service/client-config-api-service.js';
7
+ export type { ClientConfigApiServiceActions, ClientConfigApiServiceCacheUpdatedEvent, ClientConfigApiServiceEvents, ClientConfigApiServiceGranularCacheUpdatedEvent, ClientConfigApiServiceInvalidateQueriesAction, ClientConfigApiServiceMessenger, } from './client-config-api-service/client-config-api-service.js';
8
+ export type { ClientConfigApiServiceFetchRemoteFeatureFlagsAction, ClientConfigApiServiceMethodActions, } from './client-config-api-service/client-config-api-service-method-action-types.js';
7
9
  export { generateDeterministicRandomNumber } from './utils/user-segmentation-utils.js';
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,YAAY,EACV,gCAAgC,EAChC,oCAAoC,EACpC,kCAAkC,EAClC,yCAAyC,EACzC,iCAAiC,EACjC,2CAA2C,GAC5C,MAAM,qCAAqC,CAAC;AAC7C,YAAY,EACV,sDAAsD,EACtD,wCAAwC,EACxC,uCAAuC,EACvC,wCAAwC,EACxC,mDAAmD,EACnD,gDAAgD,EAChD,yDAAyD,GAC1D,MAAM,yDAAyD,CAAC;AACjE,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACjB,MAAM,2CAA2C,CAAC;AAEnD,YAAY,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAC9E,OAAO,EAAE,sBAAsB,EAAE,MAAM,0DAA0D,CAAC;AAClG,OAAO,EAAE,iCAAiC,EAAE,MAAM,oCAAoC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,YAAY,EACV,gCAAgC,EAChC,oCAAoC,EACpC,kCAAkC,EAClC,yCAAyC,EACzC,iCAAiC,EACjC,2CAA2C,GAC5C,MAAM,qCAAqC,CAAC;AAC7C,YAAY,EACV,sDAAsD,EACtD,wCAAwC,EACxC,uCAAuC,EACvC,wCAAwC,EACxC,mDAAmD,EACnD,gDAAgD,EAChD,yDAAyD,GAC1D,MAAM,yDAAyD,CAAC;AACjE,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACjB,MAAM,2CAA2C,CAAC;AAEnD,YAAY,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAC9E,OAAO,EAAE,sBAAsB,EAAE,MAAM,0DAA0D,CAAC;AAClG,YAAY,EACV,6BAA6B,EAC7B,uCAAuC,EACvC,4BAA4B,EAC5B,+CAA+C,EAC/C,6CAA6C,EAC7C,+BAA+B,GAChC,MAAM,0DAA0D,CAAC;AAClE,YAAY,EACV,mDAAmD,EACnD,mCAAmC,GACpC,MAAM,8EAA8E,CAAC;AACtF,OAAO,EAAE,iCAAiC,EAAE,MAAM,oCAAoC,CAAC"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAkBlF,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACjB,MAAM,2CAA2C,CAAC;AAGnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0DAA0D,CAAC;AAClG,OAAO,EAAE,iCAAiC,EAAE,MAAM,oCAAoC,CAAC","sourcesContent":["export { RemoteFeatureFlagController } from './remote-feature-flag-controller.js';\nexport type {\n RemoteFeatureFlagControllerState,\n RemoteFeatureFlagControllerMessenger,\n RemoteFeatureFlagControllerActions,\n RemoteFeatureFlagControllerGetStateAction,\n RemoteFeatureFlagControllerEvents,\n RemoteFeatureFlagControllerStateChangeEvent,\n} from './remote-feature-flag-controller.js';\nexport type {\n RemoteFeatureFlagControllerClearAllFlagOverridesAction,\n RemoteFeatureFlagControllerDisableAction,\n RemoteFeatureFlagControllerEnableAction,\n RemoteFeatureFlagControllerMethodActions,\n RemoteFeatureFlagControllerRemoveFlagOverrideAction,\n RemoteFeatureFlagControllerSetFlagOverrideAction,\n RemoteFeatureFlagControllerUpdateRemoteFeatureFlagsAction,\n} from './remote-feature-flag-controller-method-action-types.js';\nexport {\n ClientType,\n DistributionType,\n EnvironmentType,\n ThresholdVersion,\n} from './remote-feature-flag-controller-types.js';\n\nexport type { FeatureFlags } from './remote-feature-flag-controller-types.js';\nexport { ClientConfigApiService } from './client-config-api-service/client-config-api-service.js';\nexport { generateDeterministicRandomNumber } from './utils/user-segmentation-utils.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAkBlF,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACjB,MAAM,2CAA2C,CAAC;AAGnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0DAA0D,CAAC;AAalG,OAAO,EAAE,iCAAiC,EAAE,MAAM,oCAAoC,CAAC","sourcesContent":["export { RemoteFeatureFlagController } from './remote-feature-flag-controller.js';\nexport type {\n RemoteFeatureFlagControllerState,\n RemoteFeatureFlagControllerMessenger,\n RemoteFeatureFlagControllerActions,\n RemoteFeatureFlagControllerGetStateAction,\n RemoteFeatureFlagControllerEvents,\n RemoteFeatureFlagControllerStateChangeEvent,\n} from './remote-feature-flag-controller.js';\nexport type {\n RemoteFeatureFlagControllerClearAllFlagOverridesAction,\n RemoteFeatureFlagControllerDisableAction,\n RemoteFeatureFlagControllerEnableAction,\n RemoteFeatureFlagControllerMethodActions,\n RemoteFeatureFlagControllerRemoveFlagOverrideAction,\n RemoteFeatureFlagControllerSetFlagOverrideAction,\n RemoteFeatureFlagControllerUpdateRemoteFeatureFlagsAction,\n} from './remote-feature-flag-controller-method-action-types.js';\nexport {\n ClientType,\n DistributionType,\n EnvironmentType,\n ThresholdVersion,\n} from './remote-feature-flag-controller-types.js';\n\nexport type { FeatureFlags } from './remote-feature-flag-controller-types.js';\nexport { ClientConfigApiService } from './client-config-api-service/client-config-api-service.js';\nexport type {\n ClientConfigApiServiceActions,\n ClientConfigApiServiceCacheUpdatedEvent,\n ClientConfigApiServiceEvents,\n ClientConfigApiServiceGranularCacheUpdatedEvent,\n ClientConfigApiServiceInvalidateQueriesAction,\n ClientConfigApiServiceMessenger,\n} from './client-config-api-service/client-config-api-service.js';\nexport type {\n ClientConfigApiServiceFetchRemoteFeatureFlagsAction,\n ClientConfigApiServiceMethodActions,\n} from './client-config-api-service/client-config-api-service-method-action-types.js';\nexport { generateDeterministicRandomNumber } from './utils/user-segmentation-utils.js';\n"]}
@@ -2,7 +2,7 @@ import { BaseController, ControllerGetStateAction } from '@metamask/base-control
2
2
  import type { ControllerStateChangeEvent } from '@metamask/base-controller';
3
3
  import type { Messenger } from '@metamask/messenger';
4
4
  import type { Json } from '@metamask/utils';
5
- import type { AbstractClientConfigApiService } from './client-config-api-service/abstract-client-config-api-service.js';
5
+ import type { ClientConfigApiServiceFetchRemoteFeatureFlagsAction } from './client-config-api-service/client-config-api-service-method-action-types.js';
6
6
  import type { RemoteFeatureFlagControllerMethodActions } from './remote-feature-flag-controller-method-action-types.js';
7
7
  import type { FeatureFlags } from './remote-feature-flag-controller-types.js';
8
8
  export declare const controllerName = "RemoteFeatureFlagController";
@@ -19,7 +19,12 @@ export type RemoteFeatureFlagControllerGetStateAction = ControllerGetStateAction
19
19
  export type RemoteFeatureFlagControllerActions = RemoteFeatureFlagControllerGetStateAction | RemoteFeatureFlagControllerMethodActions;
20
20
  export type RemoteFeatureFlagControllerStateChangeEvent = ControllerStateChangeEvent<typeof controllerName, RemoteFeatureFlagControllerState>;
21
21
  export type RemoteFeatureFlagControllerEvents = RemoteFeatureFlagControllerStateChangeEvent;
22
- export type RemoteFeatureFlagControllerMessenger = Messenger<typeof controllerName, RemoteFeatureFlagControllerActions, RemoteFeatureFlagControllerEvents>;
22
+ /**
23
+ * Actions from other messengers that {@link RemoteFeatureFlagControllerMessenger}
24
+ * calls.
25
+ */
26
+ type AllowedActions = ClientConfigApiServiceFetchRemoteFeatureFlagsAction;
27
+ export type RemoteFeatureFlagControllerMessenger = Messenger<typeof controllerName, RemoteFeatureFlagControllerActions | AllowedActions, RemoteFeatureFlagControllerEvents>;
23
28
  /**
24
29
  * Returns the default state for the RemoteFeatureFlagController.
25
30
  *
@@ -40,7 +45,6 @@ export declare class RemoteFeatureFlagController extends BaseController<typeof c
40
45
  * @param options - The controller options.
41
46
  * @param options.messenger - The messenger used for communication.
42
47
  * @param options.state - The initial state of the controller.
43
- * @param options.clientConfigApiService - The service instance to fetch remote feature flags.
44
48
  * @param options.fetchInterval - The interval in milliseconds before cached flags expire. Defaults to 1 day.
45
49
  * @param options.disabled - Determines if the controller should be disabled initially. Defaults to false.
46
50
  * @param options.getMetaMetricsId - Returns metaMetricsId.
@@ -50,10 +54,9 @@ export declare class RemoteFeatureFlagController extends BaseController<typeof c
50
54
  * @param options.prevClientVersion - The previous client version for feature flag cache invalidation.
51
55
  * @param options.defaultFeatureFlags - Client-side default feature flags used as the lowest-precedence layer under processed remote flags and local overrides. Not persisted.
52
56
  */
53
- constructor({ messenger, state, clientConfigApiService, fetchInterval, disabled, getMetaMetricsId, getCanonicalProfileId, metaMetricsFlags, clientVersion, prevClientVersion, defaultFeatureFlags, }: {
57
+ constructor({ messenger, state, fetchInterval, disabled, getMetaMetricsId, getCanonicalProfileId, metaMetricsFlags, clientVersion, prevClientVersion, defaultFeatureFlags, }: {
54
58
  messenger: RemoteFeatureFlagControllerMessenger;
55
59
  state?: Partial<RemoteFeatureFlagControllerState>;
56
- clientConfigApiService: AbstractClientConfigApiService;
57
60
  getMetaMetricsId: () => string;
58
61
  getCanonicalProfileId: () => string;
59
62
  metaMetricsFlags?: readonly string[];
@@ -110,4 +113,5 @@ export declare class RemoteFeatureFlagController extends BaseController<typeof c
110
113
  */
111
114
  clearAllFlagOverrides(): void;
112
115
  }
116
+ export {};
113
117
  //# sourceMappingURL=remote-feature-flag-controller.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"remote-feature-flag-controller.d.ts","sourceRoot":"","sources":["../src/remote-feature-flag-controller.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"remote-feature-flag-controller.d.ts","sourceRoot":"","sources":["../src/remote-feature-flag-controller.ts"],"names":[],"mappings":"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"}
@@ -101,7 +101,6 @@ function findExplicitIdMatch(entries, normalizedId) {
101
101
  export class RemoteFeatureFlagController extends BaseController {
102
102
  #fetchInterval;
103
103
  #disabled;
104
- #clientConfigApiService;
105
104
  #inProgressFlagUpdate;
106
105
  #getMetaMetricsId;
107
106
  #getCanonicalProfileId;
@@ -115,7 +114,6 @@ export class RemoteFeatureFlagController extends BaseController {
115
114
  * @param options - The controller options.
116
115
  * @param options.messenger - The messenger used for communication.
117
116
  * @param options.state - The initial state of the controller.
118
- * @param options.clientConfigApiService - The service instance to fetch remote feature flags.
119
117
  * @param options.fetchInterval - The interval in milliseconds before cached flags expire. Defaults to 1 day.
120
118
  * @param options.disabled - Determines if the controller should be disabled initially. Defaults to false.
121
119
  * @param options.getMetaMetricsId - Returns metaMetricsId.
@@ -125,7 +123,7 @@ export class RemoteFeatureFlagController extends BaseController {
125
123
  * @param options.prevClientVersion - The previous client version for feature flag cache invalidation.
126
124
  * @param options.defaultFeatureFlags - Client-side default feature flags used as the lowest-precedence layer under processed remote flags and local overrides. Not persisted.
127
125
  */
128
- constructor({ messenger, state, clientConfigApiService, fetchInterval = DEFAULT_CACHE_DURATION, disabled = false, getMetaMetricsId, getCanonicalProfileId, metaMetricsFlags = [], clientVersion, prevClientVersion, defaultFeatureFlags = {}, }) {
126
+ constructor({ messenger, state, fetchInterval = DEFAULT_CACHE_DURATION, disabled = false, getMetaMetricsId, getCanonicalProfileId, metaMetricsFlags = [], clientVersion, prevClientVersion, defaultFeatureFlags = {}, }) {
129
127
  if (!isValidSemVerVersion(clientVersion)) {
130
128
  throw new Error(`Invalid clientVersion: "${clientVersion}". Must be a valid 3-part SemVer version string`);
131
129
  }
@@ -154,7 +152,6 @@ export class RemoteFeatureFlagController extends BaseController {
154
152
  this.#processedRemoteFeatureFlags = initialState.remoteFeatureFlags;
155
153
  this.#fetchInterval = fetchInterval;
156
154
  this.#disabled = disabled;
157
- this.#clientConfigApiService = clientConfigApiService;
158
155
  this.#getMetaMetricsId = getMetaMetricsId;
159
156
  this.#getCanonicalProfileId = getCanonicalProfileId;
160
157
  this.#metaMetricsFlags = new Set(metaMetricsFlags);
@@ -205,8 +202,7 @@ export class RemoteFeatureFlagController extends BaseController {
205
202
  return;
206
203
  }
207
204
  try {
208
- this.#inProgressFlagUpdate =
209
- this.#clientConfigApiService.fetchRemoteFeatureFlags();
205
+ this.#inProgressFlagUpdate = this.messenger.call('ClientConfigApiService:fetchRemoteFeatureFlags');
210
206
  serverData = await this.#inProgressFlagUpdate;
211
207
  }
212
208
  finally {
@@ -1 +1 @@
1
- {"version":3,"file":"remote-feature-flag-controller.js","sourceRoot":"","sources":["../src/remote-feature-flag-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BaseController,\n ControllerGetStateAction,\n} from '@metamask/base-controller';\nimport type { ControllerStateChangeEvent } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport { hasProperty, isValidSemVerVersion } from '@metamask/utils';\nimport type { Json, SemVerVersion } from '@metamask/utils';\n\nimport type { AbstractClientConfigApiService } from './client-config-api-service/abstract-client-config-api-service.js';\nimport type { RemoteFeatureFlagControllerMethodActions } from './remote-feature-flag-controller-method-action-types.js';\nimport type {\n FeatureFlags,\n ServiceResponse,\n FeatureFlagScopeValue,\n} from './remote-feature-flag-controller-types.js';\nimport {\n calculateThresholdForFlag,\n isFeatureFlagWithScopeValue,\n} from './utils/user-segmentation-utils.js';\nimport { isVersionFeatureFlag, getVersionData } from './utils/version.js';\n\n// === GENERAL ===\n\nexport const controllerName = 'RemoteFeatureFlagController';\nexport const DEFAULT_CACHE_DURATION = 24 * 60 * 60 * 1000; // 1 day\n\n// === STATE ===\n\nexport type RemoteFeatureFlagControllerState = {\n remoteFeatureFlags: FeatureFlags;\n localOverrides?: FeatureFlags;\n rawRemoteFeatureFlags?: FeatureFlags;\n cacheTimestamp: number;\n thresholdCache?: Record<string, number>;\n featureFlagThresholdGroups?: Record<string, string>;\n};\n\nconst remoteFeatureFlagControllerMetadata = {\n remoteFeatureFlags: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n localOverrides: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n rawRemoteFeatureFlags: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: false,\n },\n cacheTimestamp: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: false,\n },\n thresholdCache: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: false,\n },\n featureFlagThresholdGroups: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: false,\n },\n};\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'clearAllFlagOverrides',\n 'disable',\n 'enable',\n 'removeFlagOverride',\n 'setFlagOverride',\n 'updateRemoteFeatureFlags',\n] as const;\n\nexport type RemoteFeatureFlagControllerGetStateAction =\n ControllerGetStateAction<\n typeof controllerName,\n RemoteFeatureFlagControllerState\n >;\n\nexport type RemoteFeatureFlagControllerActions =\n | RemoteFeatureFlagControllerGetStateAction\n | RemoteFeatureFlagControllerMethodActions;\n\nexport type RemoteFeatureFlagControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n RemoteFeatureFlagControllerState\n >;\n\nexport type RemoteFeatureFlagControllerEvents =\n RemoteFeatureFlagControllerStateChangeEvent;\n\nexport type RemoteFeatureFlagControllerMessenger = Messenger<\n typeof controllerName,\n RemoteFeatureFlagControllerActions,\n RemoteFeatureFlagControllerEvents\n>;\n\n/**\n * Returns the default state for the RemoteFeatureFlagController.\n *\n * @returns The default controller state.\n */\nexport function getDefaultRemoteFeatureFlagControllerState(): RemoteFeatureFlagControllerState {\n return {\n remoteFeatureFlags: {},\n localOverrides: {},\n rawRemoteFeatureFlags: {},\n cacheTimestamp: 0,\n };\n}\n\n/**\n * Searches threshold entries for an explicit MetaMetrics ID match.\n * Returns the first entry whose `metaMetricsIds` list contains the given\n * normalized ID. Entries with malformed `metaMetricsIds` (not an array) are\n * skipped without throwing.\n *\n * @param entries - The array of raw threshold entries for a feature flag.\n * @param normalizedId - The current user's MetaMetrics ID, already trimmed and\n * lower-cased.\n * @returns The first matching entry, or `undefined` if none match.\n */\nfunction findExplicitIdMatch(\n entries: Json[],\n normalizedId: string,\n): FeatureFlagScopeValue | undefined {\n for (const entry of entries) {\n if (!isFeatureFlagWithScopeValue(entry)) {\n continue;\n }\n const { metaMetricsIds } = entry;\n if (!Array.isArray(metaMetricsIds)) {\n continue;\n }\n const hasMatch = metaMetricsIds.some(\n (id) =>\n typeof id === 'string' && id.trim().toLowerCase() === normalizedId,\n );\n if (hasMatch) {\n return entry;\n }\n }\n return undefined;\n}\n\n/**\n * The RemoteFeatureFlagController manages the retrieval and caching of remote feature flags.\n * It fetches feature flags from a remote API, caches them, and provides methods to access\n * and manage these flags. The controller ensures that feature flags are refreshed based on\n * a specified interval and handles cases where the controller is disabled or the network is unavailable.\n */\nexport class RemoteFeatureFlagController extends BaseController<\n typeof controllerName,\n RemoteFeatureFlagControllerState,\n RemoteFeatureFlagControllerMessenger\n> {\n readonly #fetchInterval: number;\n\n #disabled: boolean;\n\n readonly #clientConfigApiService: AbstractClientConfigApiService;\n\n #inProgressFlagUpdate?: Promise<ServiceResponse>;\n\n readonly #getMetaMetricsId: () => string;\n\n readonly #getCanonicalProfileId: () => string;\n\n readonly #metaMetricsFlags: ReadonlySet<string>;\n\n readonly #clientVersion: SemVerVersion;\n\n readonly #defaultFeatureFlags: FeatureFlags;\n\n #processedRemoteFeatureFlags: FeatureFlags;\n\n /**\n * Constructs a new RemoteFeatureFlagController instance.\n *\n * @param options - The controller options.\n * @param options.messenger - The messenger used for communication.\n * @param options.state - The initial state of the controller.\n * @param options.clientConfigApiService - The service instance to fetch remote feature flags.\n * @param options.fetchInterval - The interval in milliseconds before cached flags expire. Defaults to 1 day.\n * @param options.disabled - Determines if the controller should be disabled initially. Defaults to false.\n * @param options.getMetaMetricsId - Returns metaMetricsId.\n * @param options.getCanonicalProfileId - Returns the canonical profile identifier used for threshold flags by default. Must return an empty string, rather than throwing, when the identifier is unavailable.\n * @param options.metaMetricsFlags - Names of feature flags that should use MetaMetrics ID for threshold assignment.\n * @param options.clientVersion - The current client version for version-based feature flag filtering. Must be a valid 3-part SemVer version string.\n * @param options.prevClientVersion - The previous client version for feature flag cache invalidation.\n * @param options.defaultFeatureFlags - Client-side default feature flags used as the lowest-precedence layer under processed remote flags and local overrides. Not persisted.\n */\n constructor({\n messenger,\n state,\n clientConfigApiService,\n fetchInterval = DEFAULT_CACHE_DURATION,\n disabled = false,\n getMetaMetricsId,\n getCanonicalProfileId,\n metaMetricsFlags = [],\n clientVersion,\n prevClientVersion,\n defaultFeatureFlags = {},\n }: {\n messenger: RemoteFeatureFlagControllerMessenger;\n state?: Partial<RemoteFeatureFlagControllerState>;\n clientConfigApiService: AbstractClientConfigApiService;\n getMetaMetricsId: () => string;\n getCanonicalProfileId: () => string;\n metaMetricsFlags?: readonly string[];\n fetchInterval?: number;\n disabled?: boolean;\n clientVersion: string;\n prevClientVersion?: string;\n defaultFeatureFlags?: FeatureFlags;\n }) {\n if (!isValidSemVerVersion(clientVersion)) {\n throw new Error(\n `Invalid clientVersion: \"${clientVersion}\". Must be a valid 3-part SemVer version string`,\n );\n }\n\n const initialState: RemoteFeatureFlagControllerState = {\n ...getDefaultRemoteFeatureFlagControllerState(),\n ...state,\n };\n\n const hasClientVersionChanged =\n isValidSemVerVersion(prevClientVersion) &&\n prevClientVersion !== clientVersion;\n\n super({\n name: controllerName,\n metadata: remoteFeatureFlagControllerMetadata,\n messenger,\n state: {\n ...initialState,\n cacheTimestamp: hasClientVersionChanged\n ? 0\n : initialState.cacheTimestamp,\n },\n });\n\n this.#defaultFeatureFlags = defaultFeatureFlags;\n // Last session's effective flags stand in for the remote layer until\n // `init` re-derives it from the persisted raw flags, or a fetch replaces\n // it. Overrides are layered on top rather than subtracted out, so a remote\n // flag that happens to share an override's value is not lost.\n this.#processedRemoteFeatureFlags = initialState.remoteFeatureFlags;\n this.#fetchInterval = fetchInterval;\n this.#disabled = disabled;\n this.#clientConfigApiService = clientConfigApiService;\n this.#getMetaMetricsId = getMetaMetricsId;\n this.#getCanonicalProfileId = getCanonicalProfileId;\n this.#metaMetricsFlags = new Set(metaMetricsFlags);\n this.#clientVersion = clientVersion;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Computes effective feature flags with precedence:\n * defaults < processed remote < local overrides.\n *\n * @param options - The layers to merge. Each defaults to the current layer.\n * @param options.processedRemoteFeatureFlags - The processed remote feature\n * flags. Defaults to the currently resolved remote layer.\n * @param options.localOverrides - Local overrides. Defaults to current state\n * overrides.\n * @returns The effective feature flags.\n */\n #getEffectiveFeatureFlags({\n processedRemoteFeatureFlags = this.#processedRemoteFeatureFlags,\n localOverrides = this.state.localOverrides,\n }: {\n processedRemoteFeatureFlags?: FeatureFlags;\n localOverrides?: FeatureFlags;\n } = {}): FeatureFlags {\n return {\n ...this.#defaultFeatureFlags,\n ...processedRemoteFeatureFlags,\n ...localOverrides,\n };\n }\n\n /**\n * Checks if the cached feature flags are expired based on the fetch interval.\n *\n * @returns Whether the cache is expired (`true`) or still valid (`false`).\n */\n #isCacheExpired(): boolean {\n return Date.now() - this.state.cacheTimestamp > this.#fetchInterval;\n }\n\n /**\n * Retrieves the remote feature flags, fetching from the API if necessary.\n * Uses caching to prevent redundant API calls and handles concurrent fetches.\n *\n * @param force - When `true`, fetch even if the cache has not expired.\n * Defaults to `false`. Has no effect when the controller is disabled.\n * @returns A promise that resolves to the current set of feature flags.\n */\n async updateRemoteFeatureFlags(force = false): Promise<void> {\n if (this.#disabled || (!force && !this.#isCacheExpired())) {\n return;\n }\n\n let serverData;\n\n if (this.#inProgressFlagUpdate) {\n await this.#inProgressFlagUpdate;\n return;\n }\n\n try {\n this.#inProgressFlagUpdate =\n this.#clientConfigApiService.fetchRemoteFeatureFlags();\n\n serverData = await this.#inProgressFlagUpdate;\n } finally {\n this.#inProgressFlagUpdate = undefined;\n }\n\n await this.#updateCache(serverData.remoteFeatureFlags);\n }\n\n /**\n * Computes the effective feature flags, re-deriving the remote layer from the\n * raw flags already in state. Threshold selection needs to await a hash and\n * so cannot run in the constructor, which is why this cannot be part of\n * construction. Clients must call this once after constructing the\n * controller.\n *\n * When there are no persisted raw flags, as on a fresh install or for state\n * persisted before raw flags were stored, the previous session's flags stand\n * in for the remote layer so that nothing is lost.\n */\n async init(): Promise<void> {\n const { rawRemoteFeatureFlags } = this.state;\n const hasRawRemoteFeatureFlags =\n rawRemoteFeatureFlags && Object.keys(rawRemoteFeatureFlags).length > 0;\n\n const resolved = hasRawRemoteFeatureFlags\n ? await this.#processRemoteFeatureFlags(rawRemoteFeatureFlags)\n : undefined;\n\n this.#processedRemoteFeatureFlags =\n resolved?.processedFlags ?? this.state.remoteFeatureFlags;\n\n this.update(() => {\n return {\n ...this.state,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags(),\n ...(resolved && {\n thresholdCache: resolved.thresholdCache,\n featureFlagThresholdGroups: resolved.featureFlagThresholdGroups,\n }),\n };\n });\n }\n\n /**\n * Updates the controller's state with new feature flags and resets the cache timestamp.\n *\n * @param remoteFeatureFlags - The new feature flags to cache.\n */\n async #updateCache(remoteFeatureFlags: FeatureFlags): Promise<void> {\n const resolved = await this.#processRemoteFeatureFlags(remoteFeatureFlags);\n\n this.#processedRemoteFeatureFlags = resolved.processedFlags;\n\n // Single state update with all changes batched together\n this.update(() => {\n return {\n ...this.state,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({\n processedRemoteFeatureFlags: resolved.processedFlags,\n }),\n rawRemoteFeatureFlags: remoteFeatureFlags,\n cacheTimestamp: Date.now(),\n thresholdCache: resolved.thresholdCache,\n featureFlagThresholdGroups: resolved.featureFlagThresholdGroups,\n };\n });\n }\n\n /**\n * Processes a version-based feature flag to get the appropriate value for the current client version.\n *\n * @param flagValue - The feature flag value to process\n * @returns The processed value, or null if no version qualifies (skip this flag)\n */\n #processVersionBasedFlag(flagValue: Json): Json | null {\n if (!isVersionFeatureFlag(flagValue)) {\n return flagValue;\n }\n\n return getVersionData(flagValue, this.#clientVersion);\n }\n\n /**\n * Selects the identifier used to bucket a threshold flag. Flags named in\n * `metaMetricsFlags` segment by MetaMetrics ID; all others segment by\n * canonical profile ID.\n *\n * @param featureFlagName - The name of the feature flag being processed.\n * @returns The segmentation identifier, which may be empty when unavailable.\n */\n #getSegmentationId(featureFlagName: string): string {\n if (this.#metaMetricsFlags.has(featureFlagName)) {\n return this.#getMetaMetricsId();\n }\n return this.#getCanonicalProfileId();\n }\n\n /**\n * Resolves raw feature flags into the values that apply to this client and\n * user, selecting version and threshold entries and reconciling the\n * threshold cache against the flags the server currently serves.\n *\n * @param remoteFeatureFlags - The unprocessed feature flags.\n * @returns The processed flags, the updated threshold cache, and the\n * selected threshold group names.\n */\n async #processRemoteFeatureFlags(remoteFeatureFlags: FeatureFlags): Promise<{\n processedFlags: FeatureFlags;\n thresholdCache: Record<string, number>;\n featureFlagThresholdGroups: Record<string, string>;\n }> {\n const processedFlags: FeatureFlags = {};\n const metaMetricsId = this.#getMetaMetricsId();\n const thresholdCacheUpdates: Record<string, number> = {};\n const featureFlagThresholdGroups: Record<string, string> = {};\n\n for (const [\n remoteFeatureFlagName,\n remoteFeatureFlagValue,\n ] of Object.entries(remoteFeatureFlags)) {\n let processedValue = this.#processVersionBasedFlag(\n remoteFeatureFlagValue,\n );\n if (processedValue === null) {\n continue;\n }\n\n if (Array.isArray(processedValue)) {\n // Validate array has valid threshold items before doing expensive crypto operation\n const hasValidThresholds = processedValue.some(\n isFeatureFlagWithScopeValue,\n );\n\n if (!hasValidThresholds) {\n // Not a threshold array - preserve as-is\n processedFlags[remoteFeatureFlagName] = processedValue;\n continue;\n }\n\n // Explicit-ID matching: check before hash-based threshold, bypasses cache\n const normalizedMetaMetricsId = metaMetricsId.trim().toLowerCase();\n const explicitMatch = normalizedMetaMetricsId\n ? findExplicitIdMatch(processedValue, normalizedMetaMetricsId)\n : undefined;\n\n if (explicitMatch) {\n processedValue = explicitMatch.value;\n if (explicitMatch.name) {\n featureFlagThresholdGroups[remoteFeatureFlagName] =\n explicitMatch.name;\n }\n } else {\n const segmentationId = this.#getSegmentationId(remoteFeatureFlagName);\n\n if (!segmentationId) {\n // The identifier needed to group this flag is unavailable, as it\n // is when `init` runs before whatever backs these getters is\n // ready. Exposing the unresolved threshold array would\n // flip the flag for the end user until the next fetch.\n if (\n hasProperty(\n this.#processedRemoteFeatureFlags,\n remoteFeatureFlagName,\n )\n ) {\n processedFlags[remoteFeatureFlagName] =\n this.#processedRemoteFeatureFlags[remoteFeatureFlagName];\n // Keep the group name alongside the value it was selected with,\n // so consumers segmenting on it do not see a value with no group.\n const previousGroup =\n this.state.featureFlagThresholdGroups?.[remoteFeatureFlagName];\n if (previousGroup) {\n featureFlagThresholdGroups[remoteFeatureFlagName] =\n previousGroup;\n }\n continue;\n }\n\n // No previous value to preserve, as on a fresh install.\n processedFlags[remoteFeatureFlagName] = processedValue;\n continue;\n }\n\n // Fall back to hash-based threshold selection with cache\n const cacheKey =\n `${segmentationId}:${remoteFeatureFlagName}` as const;\n let thresholdValue = this.state.thresholdCache?.[cacheKey];\n\n if (thresholdValue === undefined) {\n thresholdValue = await calculateThresholdForFlag(\n segmentationId,\n remoteFeatureFlagName,\n );\n\n // Collect new threshold for batched state update\n thresholdCacheUpdates[cacheKey] = thresholdValue;\n }\n\n const threshold = thresholdValue;\n const selectedGroup = processedValue.find(\n (featureFlag): featureFlag is FeatureFlagScopeValue => {\n if (!isFeatureFlagWithScopeValue(featureFlag)) {\n return false;\n }\n\n return threshold <= featureFlag.scope.value;\n },\n );\n\n if (selectedGroup) {\n processedValue = selectedGroup.value;\n if (selectedGroup.name) {\n featureFlagThresholdGroups[remoteFeatureFlagName] =\n selectedGroup.name;\n }\n }\n }\n }\n\n processedFlags[remoteFeatureFlagName] = processedValue;\n }\n\n const thresholdCache = {\n ...this.state.thresholdCache,\n ...thresholdCacheUpdates,\n };\n\n // Drop cached thresholds for flags this user is no longer served, under\n // either identifier they may have been bucketed by.\n const canonicalProfileId = this.#getCanonicalProfileId();\n const currentFlagNames = Object.keys(remoteFeatureFlags);\n for (const cacheKey of Object.keys(thresholdCache)) {\n const [cachedSegmentationId, ...cachedFlagNameParts] =\n cacheKey.split(':');\n const cachedFlagName = cachedFlagNameParts.join(':');\n if (\n (cachedSegmentationId === metaMetricsId ||\n cachedSegmentationId === canonicalProfileId) &&\n !currentFlagNames.includes(cachedFlagName)\n ) {\n delete thresholdCache[cacheKey];\n }\n }\n\n return {\n processedFlags,\n thresholdCache,\n featureFlagThresholdGroups,\n };\n }\n\n /**\n * Enables the controller, allowing it to make network requests.\n */\n enable(): void {\n this.#disabled = false;\n }\n\n /**\n * Disables the controller, preventing it from making network requests.\n */\n disable(): void {\n this.#disabled = true;\n }\n\n /**\n * Sets a local override for a specific feature flag.\n *\n * @param flagName - The name of the feature flag to override.\n * @param value - The override value for the feature flag.\n */\n setFlagOverride(flagName: string, value: Json): void {\n this.update(() => {\n const localOverrides = {\n ...this.state.localOverrides,\n [flagName]: value,\n };\n\n return {\n ...this.state,\n localOverrides,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({ localOverrides }),\n };\n });\n }\n\n /**\n * Clears the local override for a specific feature flag.\n *\n * @param flagName - The name of the feature flag to clear.\n */\n removeFlagOverride(flagName: string): void {\n const newLocalOverrides = { ...this.state.localOverrides };\n delete newLocalOverrides[flagName];\n\n this.update(() => {\n return {\n ...this.state,\n localOverrides: newLocalOverrides,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({\n localOverrides: newLocalOverrides,\n }),\n };\n });\n }\n\n /**\n * Clears all local feature flag overrides.\n */\n clearAllFlagOverrides(): void {\n this.update(() => {\n return {\n ...this.state,\n localOverrides: {},\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({\n localOverrides: {},\n }),\n };\n });\n }\n}\n"]}
1
+ {"version":3,"file":"remote-feature-flag-controller.js","sourceRoot":"","sources":["../src/remote-feature-flag-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BaseController,\n ControllerGetStateAction,\n} from '@metamask/base-controller';\nimport type { ControllerStateChangeEvent } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport { hasProperty, isValidSemVerVersion } from '@metamask/utils';\nimport type { Json, SemVerVersion } from '@metamask/utils';\n\nimport type { ClientConfigApiServiceFetchRemoteFeatureFlagsAction } from './client-config-api-service/client-config-api-service-method-action-types.js';\nimport type { RemoteFeatureFlagControllerMethodActions } from './remote-feature-flag-controller-method-action-types.js';\nimport type {\n FeatureFlags,\n ServiceResponse,\n FeatureFlagScopeValue,\n} from './remote-feature-flag-controller-types.js';\nimport {\n calculateThresholdForFlag,\n isFeatureFlagWithScopeValue,\n} from './utils/user-segmentation-utils.js';\nimport { isVersionFeatureFlag, getVersionData } from './utils/version.js';\n\n// === GENERAL ===\n\nexport const controllerName = 'RemoteFeatureFlagController';\nexport const DEFAULT_CACHE_DURATION = 24 * 60 * 60 * 1000; // 1 day\n\n// === STATE ===\n\nexport type RemoteFeatureFlagControllerState = {\n remoteFeatureFlags: FeatureFlags;\n localOverrides?: FeatureFlags;\n rawRemoteFeatureFlags?: FeatureFlags;\n cacheTimestamp: number;\n thresholdCache?: Record<string, number>;\n featureFlagThresholdGroups?: Record<string, string>;\n};\n\nconst remoteFeatureFlagControllerMetadata = {\n remoteFeatureFlags: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n localOverrides: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n rawRemoteFeatureFlags: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: false,\n },\n cacheTimestamp: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: false,\n },\n thresholdCache: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: false,\n },\n featureFlagThresholdGroups: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: false,\n },\n};\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'clearAllFlagOverrides',\n 'disable',\n 'enable',\n 'removeFlagOverride',\n 'setFlagOverride',\n 'updateRemoteFeatureFlags',\n] as const;\n\nexport type RemoteFeatureFlagControllerGetStateAction =\n ControllerGetStateAction<\n typeof controllerName,\n RemoteFeatureFlagControllerState\n >;\n\nexport type RemoteFeatureFlagControllerActions =\n | RemoteFeatureFlagControllerGetStateAction\n | RemoteFeatureFlagControllerMethodActions;\n\nexport type RemoteFeatureFlagControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n RemoteFeatureFlagControllerState\n >;\n\nexport type RemoteFeatureFlagControllerEvents =\n RemoteFeatureFlagControllerStateChangeEvent;\n\n/**\n * Actions from other messengers that {@link RemoteFeatureFlagControllerMessenger}\n * calls.\n */\ntype AllowedActions = ClientConfigApiServiceFetchRemoteFeatureFlagsAction;\n\nexport type RemoteFeatureFlagControllerMessenger = Messenger<\n typeof controllerName,\n RemoteFeatureFlagControllerActions | AllowedActions,\n RemoteFeatureFlagControllerEvents\n>;\n\n/**\n * Returns the default state for the RemoteFeatureFlagController.\n *\n * @returns The default controller state.\n */\nexport function getDefaultRemoteFeatureFlagControllerState(): RemoteFeatureFlagControllerState {\n return {\n remoteFeatureFlags: {},\n localOverrides: {},\n rawRemoteFeatureFlags: {},\n cacheTimestamp: 0,\n };\n}\n\n/**\n * Searches threshold entries for an explicit MetaMetrics ID match.\n * Returns the first entry whose `metaMetricsIds` list contains the given\n * normalized ID. Entries with malformed `metaMetricsIds` (not an array) are\n * skipped without throwing.\n *\n * @param entries - The array of raw threshold entries for a feature flag.\n * @param normalizedId - The current user's MetaMetrics ID, already trimmed and\n * lower-cased.\n * @returns The first matching entry, or `undefined` if none match.\n */\nfunction findExplicitIdMatch(\n entries: Json[],\n normalizedId: string,\n): FeatureFlagScopeValue | undefined {\n for (const entry of entries) {\n if (!isFeatureFlagWithScopeValue(entry)) {\n continue;\n }\n const { metaMetricsIds } = entry;\n if (!Array.isArray(metaMetricsIds)) {\n continue;\n }\n const hasMatch = metaMetricsIds.some(\n (id) =>\n typeof id === 'string' && id.trim().toLowerCase() === normalizedId,\n );\n if (hasMatch) {\n return entry;\n }\n }\n return undefined;\n}\n\n/**\n * The RemoteFeatureFlagController manages the retrieval and caching of remote feature flags.\n * It fetches feature flags from a remote API, caches them, and provides methods to access\n * and manage these flags. The controller ensures that feature flags are refreshed based on\n * a specified interval and handles cases where the controller is disabled or the network is unavailable.\n */\nexport class RemoteFeatureFlagController extends BaseController<\n typeof controllerName,\n RemoteFeatureFlagControllerState,\n RemoteFeatureFlagControllerMessenger\n> {\n readonly #fetchInterval: number;\n\n #disabled: boolean;\n\n #inProgressFlagUpdate?: Promise<ServiceResponse>;\n\n readonly #getMetaMetricsId: () => string;\n\n readonly #getCanonicalProfileId: () => string;\n\n readonly #metaMetricsFlags: ReadonlySet<string>;\n\n readonly #clientVersion: SemVerVersion;\n\n readonly #defaultFeatureFlags: FeatureFlags;\n\n #processedRemoteFeatureFlags: FeatureFlags;\n\n /**\n * Constructs a new RemoteFeatureFlagController instance.\n *\n * @param options - The controller options.\n * @param options.messenger - The messenger used for communication.\n * @param options.state - The initial state of the controller.\n * @param options.fetchInterval - The interval in milliseconds before cached flags expire. Defaults to 1 day.\n * @param options.disabled - Determines if the controller should be disabled initially. Defaults to false.\n * @param options.getMetaMetricsId - Returns metaMetricsId.\n * @param options.getCanonicalProfileId - Returns the canonical profile identifier used for threshold flags by default. Must return an empty string, rather than throwing, when the identifier is unavailable.\n * @param options.metaMetricsFlags - Names of feature flags that should use MetaMetrics ID for threshold assignment.\n * @param options.clientVersion - The current client version for version-based feature flag filtering. Must be a valid 3-part SemVer version string.\n * @param options.prevClientVersion - The previous client version for feature flag cache invalidation.\n * @param options.defaultFeatureFlags - Client-side default feature flags used as the lowest-precedence layer under processed remote flags and local overrides. Not persisted.\n */\n constructor({\n messenger,\n state,\n fetchInterval = DEFAULT_CACHE_DURATION,\n disabled = false,\n getMetaMetricsId,\n getCanonicalProfileId,\n metaMetricsFlags = [],\n clientVersion,\n prevClientVersion,\n defaultFeatureFlags = {},\n }: {\n messenger: RemoteFeatureFlagControllerMessenger;\n state?: Partial<RemoteFeatureFlagControllerState>;\n getMetaMetricsId: () => string;\n getCanonicalProfileId: () => string;\n metaMetricsFlags?: readonly string[];\n fetchInterval?: number;\n disabled?: boolean;\n clientVersion: string;\n prevClientVersion?: string;\n defaultFeatureFlags?: FeatureFlags;\n }) {\n if (!isValidSemVerVersion(clientVersion)) {\n throw new Error(\n `Invalid clientVersion: \"${clientVersion}\". Must be a valid 3-part SemVer version string`,\n );\n }\n\n const initialState: RemoteFeatureFlagControllerState = {\n ...getDefaultRemoteFeatureFlagControllerState(),\n ...state,\n };\n\n const hasClientVersionChanged =\n isValidSemVerVersion(prevClientVersion) &&\n prevClientVersion !== clientVersion;\n\n super({\n name: controllerName,\n metadata: remoteFeatureFlagControllerMetadata,\n messenger,\n state: {\n ...initialState,\n cacheTimestamp: hasClientVersionChanged\n ? 0\n : initialState.cacheTimestamp,\n },\n });\n\n this.#defaultFeatureFlags = defaultFeatureFlags;\n // Last session's effective flags stand in for the remote layer until\n // `init` re-derives it from the persisted raw flags, or a fetch replaces\n // it. Overrides are layered on top rather than subtracted out, so a remote\n // flag that happens to share an override's value is not lost.\n this.#processedRemoteFeatureFlags = initialState.remoteFeatureFlags;\n this.#fetchInterval = fetchInterval;\n this.#disabled = disabled;\n this.#getMetaMetricsId = getMetaMetricsId;\n this.#getCanonicalProfileId = getCanonicalProfileId;\n this.#metaMetricsFlags = new Set(metaMetricsFlags);\n this.#clientVersion = clientVersion;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Computes effective feature flags with precedence:\n * defaults < processed remote < local overrides.\n *\n * @param options - The layers to merge. Each defaults to the current layer.\n * @param options.processedRemoteFeatureFlags - The processed remote feature\n * flags. Defaults to the currently resolved remote layer.\n * @param options.localOverrides - Local overrides. Defaults to current state\n * overrides.\n * @returns The effective feature flags.\n */\n #getEffectiveFeatureFlags({\n processedRemoteFeatureFlags = this.#processedRemoteFeatureFlags,\n localOverrides = this.state.localOverrides,\n }: {\n processedRemoteFeatureFlags?: FeatureFlags;\n localOverrides?: FeatureFlags;\n } = {}): FeatureFlags {\n return {\n ...this.#defaultFeatureFlags,\n ...processedRemoteFeatureFlags,\n ...localOverrides,\n };\n }\n\n /**\n * Checks if the cached feature flags are expired based on the fetch interval.\n *\n * @returns Whether the cache is expired (`true`) or still valid (`false`).\n */\n #isCacheExpired(): boolean {\n return Date.now() - this.state.cacheTimestamp > this.#fetchInterval;\n }\n\n /**\n * Retrieves the remote feature flags, fetching from the API if necessary.\n * Uses caching to prevent redundant API calls and handles concurrent fetches.\n *\n * @param force - When `true`, fetch even if the cache has not expired.\n * Defaults to `false`. Has no effect when the controller is disabled.\n * @returns A promise that resolves to the current set of feature flags.\n */\n async updateRemoteFeatureFlags(force = false): Promise<void> {\n if (this.#disabled || (!force && !this.#isCacheExpired())) {\n return;\n }\n\n let serverData;\n\n if (this.#inProgressFlagUpdate) {\n await this.#inProgressFlagUpdate;\n return;\n }\n\n try {\n this.#inProgressFlagUpdate = this.messenger.call(\n 'ClientConfigApiService:fetchRemoteFeatureFlags',\n );\n\n serverData = await this.#inProgressFlagUpdate;\n } finally {\n this.#inProgressFlagUpdate = undefined;\n }\n\n await this.#updateCache(serverData.remoteFeatureFlags);\n }\n\n /**\n * Computes the effective feature flags, re-deriving the remote layer from the\n * raw flags already in state. Threshold selection needs to await a hash and\n * so cannot run in the constructor, which is why this cannot be part of\n * construction. Clients must call this once after constructing the\n * controller.\n *\n * When there are no persisted raw flags, as on a fresh install or for state\n * persisted before raw flags were stored, the previous session's flags stand\n * in for the remote layer so that nothing is lost.\n */\n async init(): Promise<void> {\n const { rawRemoteFeatureFlags } = this.state;\n const hasRawRemoteFeatureFlags =\n rawRemoteFeatureFlags && Object.keys(rawRemoteFeatureFlags).length > 0;\n\n const resolved = hasRawRemoteFeatureFlags\n ? await this.#processRemoteFeatureFlags(rawRemoteFeatureFlags)\n : undefined;\n\n this.#processedRemoteFeatureFlags =\n resolved?.processedFlags ?? this.state.remoteFeatureFlags;\n\n this.update(() => {\n return {\n ...this.state,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags(),\n ...(resolved && {\n thresholdCache: resolved.thresholdCache,\n featureFlagThresholdGroups: resolved.featureFlagThresholdGroups,\n }),\n };\n });\n }\n\n /**\n * Updates the controller's state with new feature flags and resets the cache timestamp.\n *\n * @param remoteFeatureFlags - The new feature flags to cache.\n */\n async #updateCache(remoteFeatureFlags: FeatureFlags): Promise<void> {\n const resolved = await this.#processRemoteFeatureFlags(remoteFeatureFlags);\n\n this.#processedRemoteFeatureFlags = resolved.processedFlags;\n\n // Single state update with all changes batched together\n this.update(() => {\n return {\n ...this.state,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({\n processedRemoteFeatureFlags: resolved.processedFlags,\n }),\n rawRemoteFeatureFlags: remoteFeatureFlags,\n cacheTimestamp: Date.now(),\n thresholdCache: resolved.thresholdCache,\n featureFlagThresholdGroups: resolved.featureFlagThresholdGroups,\n };\n });\n }\n\n /**\n * Processes a version-based feature flag to get the appropriate value for the current client version.\n *\n * @param flagValue - The feature flag value to process\n * @returns The processed value, or null if no version qualifies (skip this flag)\n */\n #processVersionBasedFlag(flagValue: Json): Json | null {\n if (!isVersionFeatureFlag(flagValue)) {\n return flagValue;\n }\n\n return getVersionData(flagValue, this.#clientVersion);\n }\n\n /**\n * Selects the identifier used to bucket a threshold flag. Flags named in\n * `metaMetricsFlags` segment by MetaMetrics ID; all others segment by\n * canonical profile ID.\n *\n * @param featureFlagName - The name of the feature flag being processed.\n * @returns The segmentation identifier, which may be empty when unavailable.\n */\n #getSegmentationId(featureFlagName: string): string {\n if (this.#metaMetricsFlags.has(featureFlagName)) {\n return this.#getMetaMetricsId();\n }\n return this.#getCanonicalProfileId();\n }\n\n /**\n * Resolves raw feature flags into the values that apply to this client and\n * user, selecting version and threshold entries and reconciling the\n * threshold cache against the flags the server currently serves.\n *\n * @param remoteFeatureFlags - The unprocessed feature flags.\n * @returns The processed flags, the updated threshold cache, and the\n * selected threshold group names.\n */\n async #processRemoteFeatureFlags(remoteFeatureFlags: FeatureFlags): Promise<{\n processedFlags: FeatureFlags;\n thresholdCache: Record<string, number>;\n featureFlagThresholdGroups: Record<string, string>;\n }> {\n const processedFlags: FeatureFlags = {};\n const metaMetricsId = this.#getMetaMetricsId();\n const thresholdCacheUpdates: Record<string, number> = {};\n const featureFlagThresholdGroups: Record<string, string> = {};\n\n for (const [\n remoteFeatureFlagName,\n remoteFeatureFlagValue,\n ] of Object.entries(remoteFeatureFlags)) {\n let processedValue = this.#processVersionBasedFlag(\n remoteFeatureFlagValue,\n );\n if (processedValue === null) {\n continue;\n }\n\n if (Array.isArray(processedValue)) {\n // Validate array has valid threshold items before doing expensive crypto operation\n const hasValidThresholds = processedValue.some(\n isFeatureFlagWithScopeValue,\n );\n\n if (!hasValidThresholds) {\n // Not a threshold array - preserve as-is\n processedFlags[remoteFeatureFlagName] = processedValue;\n continue;\n }\n\n // Explicit-ID matching: check before hash-based threshold, bypasses cache\n const normalizedMetaMetricsId = metaMetricsId.trim().toLowerCase();\n const explicitMatch = normalizedMetaMetricsId\n ? findExplicitIdMatch(processedValue, normalizedMetaMetricsId)\n : undefined;\n\n if (explicitMatch) {\n processedValue = explicitMatch.value;\n if (explicitMatch.name) {\n featureFlagThresholdGroups[remoteFeatureFlagName] =\n explicitMatch.name;\n }\n } else {\n const segmentationId = this.#getSegmentationId(remoteFeatureFlagName);\n\n if (!segmentationId) {\n // The identifier needed to group this flag is unavailable, as it\n // is when `init` runs before whatever backs these getters is\n // ready. Exposing the unresolved threshold array would\n // flip the flag for the end user until the next fetch.\n if (\n hasProperty(\n this.#processedRemoteFeatureFlags,\n remoteFeatureFlagName,\n )\n ) {\n processedFlags[remoteFeatureFlagName] =\n this.#processedRemoteFeatureFlags[remoteFeatureFlagName];\n // Keep the group name alongside the value it was selected with,\n // so consumers segmenting on it do not see a value with no group.\n const previousGroup =\n this.state.featureFlagThresholdGroups?.[remoteFeatureFlagName];\n if (previousGroup) {\n featureFlagThresholdGroups[remoteFeatureFlagName] =\n previousGroup;\n }\n continue;\n }\n\n // No previous value to preserve, as on a fresh install.\n processedFlags[remoteFeatureFlagName] = processedValue;\n continue;\n }\n\n // Fall back to hash-based threshold selection with cache\n const cacheKey =\n `${segmentationId}:${remoteFeatureFlagName}` as const;\n let thresholdValue = this.state.thresholdCache?.[cacheKey];\n\n if (thresholdValue === undefined) {\n thresholdValue = await calculateThresholdForFlag(\n segmentationId,\n remoteFeatureFlagName,\n );\n\n // Collect new threshold for batched state update\n thresholdCacheUpdates[cacheKey] = thresholdValue;\n }\n\n const threshold = thresholdValue;\n const selectedGroup = processedValue.find(\n (featureFlag): featureFlag is FeatureFlagScopeValue => {\n if (!isFeatureFlagWithScopeValue(featureFlag)) {\n return false;\n }\n\n return threshold <= featureFlag.scope.value;\n },\n );\n\n if (selectedGroup) {\n processedValue = selectedGroup.value;\n if (selectedGroup.name) {\n featureFlagThresholdGroups[remoteFeatureFlagName] =\n selectedGroup.name;\n }\n }\n }\n }\n\n processedFlags[remoteFeatureFlagName] = processedValue;\n }\n\n const thresholdCache = {\n ...this.state.thresholdCache,\n ...thresholdCacheUpdates,\n };\n\n // Drop cached thresholds for flags this user is no longer served, under\n // either identifier they may have been bucketed by.\n const canonicalProfileId = this.#getCanonicalProfileId();\n const currentFlagNames = Object.keys(remoteFeatureFlags);\n for (const cacheKey of Object.keys(thresholdCache)) {\n const [cachedSegmentationId, ...cachedFlagNameParts] =\n cacheKey.split(':');\n const cachedFlagName = cachedFlagNameParts.join(':');\n if (\n (cachedSegmentationId === metaMetricsId ||\n cachedSegmentationId === canonicalProfileId) &&\n !currentFlagNames.includes(cachedFlagName)\n ) {\n delete thresholdCache[cacheKey];\n }\n }\n\n return {\n processedFlags,\n thresholdCache,\n featureFlagThresholdGroups,\n };\n }\n\n /**\n * Enables the controller, allowing it to make network requests.\n */\n enable(): void {\n this.#disabled = false;\n }\n\n /**\n * Disables the controller, preventing it from making network requests.\n */\n disable(): void {\n this.#disabled = true;\n }\n\n /**\n * Sets a local override for a specific feature flag.\n *\n * @param flagName - The name of the feature flag to override.\n * @param value - The override value for the feature flag.\n */\n setFlagOverride(flagName: string, value: Json): void {\n this.update(() => {\n const localOverrides = {\n ...this.state.localOverrides,\n [flagName]: value,\n };\n\n return {\n ...this.state,\n localOverrides,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({ localOverrides }),\n };\n });\n }\n\n /**\n * Clears the local override for a specific feature flag.\n *\n * @param flagName - The name of the feature flag to clear.\n */\n removeFlagOverride(flagName: string): void {\n const newLocalOverrides = { ...this.state.localOverrides };\n delete newLocalOverrides[flagName];\n\n this.update(() => {\n return {\n ...this.state,\n localOverrides: newLocalOverrides,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({\n localOverrides: newLocalOverrides,\n }),\n };\n });\n }\n\n /**\n * Clears all local feature flag overrides.\n */\n clearAllFlagOverrides(): void {\n this.update(() => {\n return {\n ...this.state,\n localOverrides: {},\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({\n localOverrides: {},\n }),\n };\n });\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/remote-feature-flag-controller",
3
- "version": "7.0.0-preview-c1f9a0f",
3
+ "version": "7.0.0-preview-01e391e",
4
4
  "description": "The RemoteFeatureFlagController manages the retrieval and caching of remote feature flags",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -51,8 +51,10 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@metamask/base-controller": "^10.1.0",
54
+ "@metamask/base-data-service": "^2.1.0",
54
55
  "@metamask/controller-utils": "^13.0.0",
55
56
  "@metamask/messenger": "^3.0.0",
57
+ "@metamask/superstruct": "^3.4.1",
56
58
  "@metamask/utils": "^12.0.1",
57
59
  "uuid": "^14.0.0"
58
60
  },
@@ -1,17 +0,0 @@
1
- import type { ServicePolicy } from '@metamask/controller-utils';
2
- import type { ServiceResponse } from '../remote-feature-flag-controller-types.js';
3
- /**
4
- * A service object responsible for fetching feature flags.
5
- */
6
- export type AbstractClientConfigApiService = Partial<Pick<ServicePolicy, 'onBreak' | 'onDegraded'>> & {
7
- /**
8
- * Fetches feature flags from the API with specific client, distribution, and
9
- * environment parameters. Provides structured error handling, including
10
- * fallback to cached data if available.
11
- *
12
- * @returns An object of feature flags and their boolean values or a
13
- * structured error object.
14
- */
15
- fetchRemoteFeatureFlags(): Promise<ServiceResponse>;
16
- };
17
- //# sourceMappingURL=abstract-client-config-api-service.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"abstract-client-config-api-service.d.ts","sourceRoot":"","sources":["../../src/client-config-api-service/abstract-client-config-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4CAA4C,CAAC;AAElF;;GAEG;AACH,MAAM,MAAM,8BAA8B,GAAG,OAAO,CAClD,IAAI,CAAC,aAAa,EAAE,SAAS,GAAG,YAAY,CAAC,CAC9C,GAAG;IACF;;;;;;;OAOG;IACH,uBAAuB,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;CACrD,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=abstract-client-config-api-service.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"abstract-client-config-api-service.js","sourceRoot":"","sources":["../../src/client-config-api-service/abstract-client-config-api-service.ts"],"names":[],"mappings":"","sourcesContent":["import type { ServicePolicy } from '@metamask/controller-utils';\n\nimport type { ServiceResponse } from '../remote-feature-flag-controller-types.js';\n\n/**\n * A service object responsible for fetching feature flags.\n */\nexport type AbstractClientConfigApiService = Partial<\n Pick<ServicePolicy, 'onBreak' | 'onDegraded'>\n> & {\n /**\n * Fetches feature flags from the API with specific client, distribution, and\n * environment parameters. Provides structured error handling, including\n * fallback to cached data if available.\n *\n * @returns An object of feature flags and their boolean values or a\n * structured error object.\n */\n fetchRemoteFeatureFlags(): Promise<ServiceResponse>;\n};\n"]}