@metamask-previews/remote-feature-flag-controller 4.2.2-preview-40d5fb1e2 → 4.2.2-preview-a3e31582e
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/dist/client-config-api-service/abstract-client-config-api-service.cjs.map +1 -1
- package/dist/client-config-api-service/abstract-client-config-api-service.d.cts.map +1 -1
- package/dist/client-config-api-service/abstract-client-config-api-service.d.mts.map +1 -1
- package/dist/client-config-api-service/abstract-client-config-api-service.mjs.map +1 -1
- package/dist/client-config-api-service/client-config-api-service.cjs +2 -2
- package/dist/client-config-api-service/client-config-api-service.cjs.map +1 -1
- package/dist/client-config-api-service/client-config-api-service.d.cts.map +1 -1
- package/dist/client-config-api-service/client-config-api-service.d.mts.map +1 -1
- package/dist/client-config-api-service/client-config-api-service.mjs.map +1 -1
- package/dist/index.cjs +11 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/remote-feature-flag-controller-method-action-types.cjs.map +1 -1
- package/dist/remote-feature-flag-controller-method-action-types.d.cts.map +1 -1
- package/dist/remote-feature-flag-controller-method-action-types.d.mts.map +1 -1
- package/dist/remote-feature-flag-controller-method-action-types.mjs.map +1 -1
- package/dist/remote-feature-flag-controller.cjs +8 -8
- package/dist/remote-feature-flag-controller.cjs.map +1 -1
- package/dist/remote-feature-flag-controller.d.cts.map +1 -1
- package/dist/remote-feature-flag-controller.d.mts.map +1 -1
- package/dist/remote-feature-flag-controller.mjs.map +1 -1
- package/dist/utils/user-segmentation-utils.cjs.map +1 -1
- package/dist/utils/user-segmentation-utils.d.cts.map +1 -1
- package/dist/utils/user-segmentation-utils.d.mts.map +1 -1
- package/dist/utils/user-segmentation-utils.mjs.map +1 -1
- package/dist/utils/version.cjs.map +1 -1
- package/dist/utils/version.d.cts.map +1 -1
- package/dist/utils/version.d.mts.map +1 -1
- package/dist/utils/version.mjs.map +1 -1
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"abstract-client-config-api-service.cjs","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';\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"]}
|
|
1
|
+
{"version":3,"file":"abstract-client-config-api-service.cjs","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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"abstract-client-config-api-service.d.cts","sourceRoot":"","sources":["../../src/client-config-api-service/abstract-client-config-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,mCAAmC;AAEhE,OAAO,KAAK,EAAE,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"abstract-client-config-api-service.d.cts","sourceRoot":"","sources":["../../src/client-config-api-service/abstract-client-config-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,mCAAmC;AAEhE,OAAO,KAAK,EAAE,eAAe,EAAE,oDAAmD;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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"abstract-client-config-api-service.d.mts","sourceRoot":"","sources":["../../src/client-config-api-service/abstract-client-config-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,mCAAmC;AAEhE,OAAO,KAAK,EAAE,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"abstract-client-config-api-service.d.mts","sourceRoot":"","sources":["../../src/client-config-api-service/abstract-client-config-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,mCAAmC;AAEhE,OAAO,KAAK,EAAE,eAAe,EAAE,oDAAmD;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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"abstract-client-config-api-service.mjs","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';\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"]}
|
|
1
|
+
{"version":3,"file":"abstract-client-config-api-service.mjs","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"]}
|
|
@@ -14,7 +14,7 @@ var _ClientConfigApiService_instances, _ClientConfigApiService_fetch, _ClientCon
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.ClientConfigApiService = void 0;
|
|
16
16
|
const controller_utils_1 = require("@metamask/controller-utils");
|
|
17
|
-
const
|
|
17
|
+
const constants_js_1 = require("../constants.cjs");
|
|
18
18
|
/**
|
|
19
19
|
* This service is responsible for fetching feature flags from the ClientConfig API.
|
|
20
20
|
*/
|
|
@@ -69,7 +69,7 @@ class ClientConfigApiService {
|
|
|
69
69
|
* @returns An object of feature flags and their boolean values or a structured error object.
|
|
70
70
|
*/
|
|
71
71
|
async fetchRemoteFeatureFlags() {
|
|
72
|
-
const url = `${
|
|
72
|
+
const url = `${constants_js_1.BASE_URL}/flags?client=${__classPrivateFieldGet(this, _ClientConfigApiService_client, "f")}&distribution=${__classPrivateFieldGet(this, _ClientConfigApiService_distribution, "f")}&environment=${__classPrivateFieldGet(this, _ClientConfigApiService_environment, "f")}`;
|
|
73
73
|
const response = await __classPrivateFieldGet(this, _ClientConfigApiService_policy, "f").execute(() => __classPrivateFieldGet(this, _ClientConfigApiService_fetch, "f").call(this, url, { cache: 'no-cache' }));
|
|
74
74
|
if (!response.ok) {
|
|
75
75
|
throw new Error('Failed to fetch remote feature flags');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-config-api-service.cjs","sourceRoot":"","sources":["../../src/client-config-api-service/client-config-api-service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,iEAKoC;AAIpC,
|
|
1
|
+
{"version":3,"file":"client-config-api-service.cjs","sourceRoot":"","sources":["../../src/client-config-api-service/client-config-api-service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,iEAKoC;AAIpC,mDAA2C;AAW3C;;GAEG;AACH,MAAa,sBAAsB;IAwFjC,YAAY,EACV,KAAK,EAAE,aAAa,EACpB,OAAO,GAAG,sCAAmB,EAC7B,0BAA0B,GAAG,mDAAgC,EAC7D,oBAAoB,GAAG,iDAA8B,EACrD,OAAO,EACP,UAAU,EACV,MAAM,GAaP;;QA3GQ,gDAAqB;QAErB,iDAAuB;QAEvB,iDAAoB;QAEpB,uDAAgC;QAEhC,sDAA8B;QAoGrC,uBAAA,IAAI,iCAAU,aAAa,MAAA,CAAC;QAC5B,uBAAA,IAAI,kCAAW,MAAM,CAAC,MAAM,MAAA,CAAC;QAC7B,uBAAA,IAAI,wCAAiB,MAAM,CAAC,YAAY,MAAA,CAAC;QACzC,uBAAA,IAAI,uCAAgB,MAAM,CAAC,WAAW,MAAA,CAAC;QAEvC,uBAAA,IAAI,kCAAW,IAAA,sCAAmB,EAAC;YACjC,UAAU,EAAE,OAAO;YACnB,sBAAsB,EAAE,0BAA0B;YAClD,oBAAoB;SACrB,CAAC,MAAA,CAAC;QACH,IAAI,OAAO,EAAE,CAAC;YACZ,uBAAA,IAAI,sCAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QACD,IAAI,UAAU,EAAE,CAAC;YACf,uBAAA,IAAI,sCAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,GAAG,IAA0C;QACnD,OAAO,uBAAA,IAAI,sCAAQ,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IACvC,CAAC;IAED;;;;;;OAMG;IACH,UAAU,CAAC,GAAG,IAA6C;QACzD,OAAO,uBAAA,IAAI,sCAAQ,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,uBAAuB;QAClC,MAAM,GAAG,GAAG,GAAG,uBAAQ,iBAAiB,uBAAA,IAAI,sCAAQ,iBAClD,uBAAA,IAAI,4CACN,gBAAgB,uBAAA,IAAI,2CAAa,EAAE,CAAC;QAEpC,MAAM,QAAQ,GAAG,MAAM,uBAAA,IAAI,sCAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,CAC/C,uBAAA,IAAI,qCAAO,MAAX,IAAI,EAAQ,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,uBAAA,IAAI,sFAAqB,MAAzB,IAAI,EAAsB,IAAI,CAAC,CAAC;QAE3D,OAAO;YACL,kBAAkB;YAClB,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE;SAC3B,CAAC;IACJ,CAAC;CAgBF;AApMD,wDAoMC;gZALsB,YAA6B;IAChD,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;QACvC,OAAO,EAAE,GAAG,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAC7B,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC","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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-config-api-service.d.cts","sourceRoot":"","sources":["../../src/client-config-api-service/client-config-api-service.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,mCAAmC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB;AAG7C,OAAO,KAAK,EAEV,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,eAAe,EAEhB,
|
|
1
|
+
{"version":3,"file":"client-config-api-service.d.cts","sourceRoot":"","sources":["../../src/client-config-api-service/client-config-api-service.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,mCAAmC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB;AAG7C,OAAO,KAAK,EAEV,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,eAAe,EAEhB,oDAAmD;AACpD,OAAO,KAAK,EAAE,8BAA8B,EAAE,iDAAgD;AAE9F;;GAEG;AACH,qBAAa,sBAAuB,YAAW,8BAA8B;;IAW3E;;;;;;;;;;;;;;;;;;;;;OAqBG;gBACS,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;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;gBAES,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;IAyCD;;;;;;OAMG;IACH,OAAO,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,GAAG,WAAW;IAInE;;;;;;OAMG;IACH,UAAU,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,GAAG,WAAW;IAIzE;;;;;OAKG;IACU,uBAAuB,IAAI,OAAO,CAAC,eAAe,CAAC;CAyCjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-config-api-service.d.mts","sourceRoot":"","sources":["../../src/client-config-api-service/client-config-api-service.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,mCAAmC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB;AAG7C,OAAO,KAAK,EAEV,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,eAAe,EAEhB,
|
|
1
|
+
{"version":3,"file":"client-config-api-service.d.mts","sourceRoot":"","sources":["../../src/client-config-api-service/client-config-api-service.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,mCAAmC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB;AAG7C,OAAO,KAAK,EAEV,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,eAAe,EAEhB,oDAAmD;AACpD,OAAO,KAAK,EAAE,8BAA8B,EAAE,iDAAgD;AAE9F;;GAEG;AACH,qBAAa,sBAAuB,YAAW,8BAA8B;;IAW3E;;;;;;;;;;;;;;;;;;;;;OAqBG;gBACS,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;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;gBAES,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;IAyCD;;;;;;OAMG;IACH,OAAO,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,GAAG,WAAW;IAInE;;;;;;OAMG;IACH,UAAU,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,GAAG,WAAW;IAIzE;;;;;OAKG;IACU,uBAAuB,IAAI,OAAO,CAAC,eAAe,CAAC;CAyCjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-config-api-service.mjs","sourceRoot":"","sources":["../../src/client-config-api-service/client-config-api-service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EACL,mBAAmB,EACnB,8BAA8B,EAC9B,gCAAgC,EAChC,mBAAmB,EACpB,mCAAmC;AAIpC,OAAO,EAAE,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"client-config-api-service.mjs","sourceRoot":"","sources":["../../src/client-config-api-service/client-config-api-service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EACL,mBAAmB,EACnB,8BAA8B,EAC9B,gCAAgC,EAChC,mBAAmB,EACpB,mCAAmC;AAIpC,OAAO,EAAE,QAAQ,EAAE,yBAAwB;AAW3C;;GAEG;AACH,MAAM,OAAO,sBAAsB;IAwFjC,YAAY,EACV,KAAK,EAAE,aAAa,EACpB,OAAO,GAAG,mBAAmB,EAC7B,0BAA0B,GAAG,gCAAgC,EAC7D,oBAAoB,GAAG,8BAA8B,EACrD,OAAO,EACP,UAAU,EACV,MAAM,GAaP;;QA3GQ,gDAAqB;QAErB,iDAAuB;QAEvB,iDAAoB;QAEpB,uDAAgC;QAEhC,sDAA8B;QAoGrC,uBAAA,IAAI,iCAAU,aAAa,MAAA,CAAC;QAC5B,uBAAA,IAAI,kCAAW,MAAM,CAAC,MAAM,MAAA,CAAC;QAC7B,uBAAA,IAAI,wCAAiB,MAAM,CAAC,YAAY,MAAA,CAAC;QACzC,uBAAA,IAAI,uCAAgB,MAAM,CAAC,WAAW,MAAA,CAAC;QAEvC,uBAAA,IAAI,kCAAW,mBAAmB,CAAC;YACjC,UAAU,EAAE,OAAO;YACnB,sBAAsB,EAAE,0BAA0B;YAClD,oBAAoB;SACrB,CAAC,MAAA,CAAC;QACH,IAAI,OAAO,EAAE,CAAC;YACZ,uBAAA,IAAI,sCAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QACD,IAAI,UAAU,EAAE,CAAC;YACf,uBAAA,IAAI,sCAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,GAAG,IAA0C;QACnD,OAAO,uBAAA,IAAI,sCAAQ,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IACvC,CAAC;IAED;;;;;;OAMG;IACH,UAAU,CAAC,GAAG,IAA6C;QACzD,OAAO,uBAAA,IAAI,sCAAQ,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,uBAAuB;QAClC,MAAM,GAAG,GAAG,GAAG,QAAQ,iBAAiB,uBAAA,IAAI,sCAAQ,iBAClD,uBAAA,IAAI,4CACN,gBAAgB,uBAAA,IAAI,2CAAa,EAAE,CAAC;QAEpC,MAAM,QAAQ,GAAG,MAAM,uBAAA,IAAI,sCAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,CAC/C,uBAAA,IAAI,qCAAO,MAAX,IAAI,EAAQ,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,uBAAA,IAAI,sFAAqB,MAAzB,IAAI,EAAsB,IAAI,CAAC,CAAC;QAE3D,OAAO;YACL,kBAAkB;YAClB,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE;SAC3B,CAAC;IACJ,CAAC;CAgBF;gZALsB,YAA6B;IAChD,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;QACvC,OAAO,EAAE,GAAG,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAC7B,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC","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"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateDeterministicRandomNumber = exports.ClientConfigApiService = exports.ThresholdVersion = exports.EnvironmentType = exports.DistributionType = exports.ClientType = exports.RemoteFeatureFlagController = void 0;
|
|
4
|
-
var
|
|
5
|
-
Object.defineProperty(exports, "RemoteFeatureFlagController", { enumerable: true, get: function () { return
|
|
6
|
-
var
|
|
7
|
-
Object.defineProperty(exports, "ClientType", { enumerable: true, get: function () { return
|
|
8
|
-
Object.defineProperty(exports, "DistributionType", { enumerable: true, get: function () { return
|
|
9
|
-
Object.defineProperty(exports, "EnvironmentType", { enumerable: true, get: function () { return
|
|
10
|
-
Object.defineProperty(exports, "ThresholdVersion", { enumerable: true, get: function () { return
|
|
11
|
-
var
|
|
12
|
-
Object.defineProperty(exports, "ClientConfigApiService", { enumerable: true, get: function () { return
|
|
13
|
-
var
|
|
14
|
-
Object.defineProperty(exports, "generateDeterministicRandomNumber", { enumerable: true, get: function () { return
|
|
4
|
+
var remote_feature_flag_controller_js_1 = require("./remote-feature-flag-controller.cjs");
|
|
5
|
+
Object.defineProperty(exports, "RemoteFeatureFlagController", { enumerable: true, get: function () { return remote_feature_flag_controller_js_1.RemoteFeatureFlagController; } });
|
|
6
|
+
var remote_feature_flag_controller_types_js_1 = require("./remote-feature-flag-controller-types.cjs");
|
|
7
|
+
Object.defineProperty(exports, "ClientType", { enumerable: true, get: function () { return remote_feature_flag_controller_types_js_1.ClientType; } });
|
|
8
|
+
Object.defineProperty(exports, "DistributionType", { enumerable: true, get: function () { return remote_feature_flag_controller_types_js_1.DistributionType; } });
|
|
9
|
+
Object.defineProperty(exports, "EnvironmentType", { enumerable: true, get: function () { return remote_feature_flag_controller_types_js_1.EnvironmentType; } });
|
|
10
|
+
Object.defineProperty(exports, "ThresholdVersion", { enumerable: true, get: function () { return remote_feature_flag_controller_types_js_1.ThresholdVersion; } });
|
|
11
|
+
var client_config_api_service_js_1 = require("./client-config-api-service/client-config-api-service.cjs");
|
|
12
|
+
Object.defineProperty(exports, "ClientConfigApiService", { enumerable: true, get: function () { return client_config_api_service_js_1.ClientConfigApiService; } });
|
|
13
|
+
var user_segmentation_utils_js_1 = require("./utils/user-segmentation-utils.cjs");
|
|
14
|
+
Object.defineProperty(exports, "generateDeterministicRandomNumber", { enumerable: true, get: function () { return user_segmentation_utils_js_1.generateDeterministicRandomNumber; } });
|
|
15
15
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,0FAAkF;AAAzE,gJAAA,2BAA2B,OAAA;AAkBpC,sGAKmD;AAJjD,qIAAA,UAAU,OAAA;AACV,2IAAA,gBAAgB,OAAA;AAChB,0IAAA,eAAe,OAAA;AACf,2IAAA,gBAAgB,OAAA;AAIlB,0GAAkG;AAAzF,sIAAA,sBAAsB,OAAA;AAC/B,kFAAuF;AAA9E,+IAAA,iCAAiC,OAAA","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"]}
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,6CAA4C;AAClF,YAAY,EACV,gCAAgC,EAChC,oCAAoC,EACpC,kCAAkC,EAClC,yCAAyC,EACzC,iCAAiC,EACjC,2CAA2C,GAC5C,6CAA4C;AAC7C,YAAY,EACV,sDAAsD,EACtD,wCAAwC,EACxC,uCAAuC,EACvC,wCAAwC,EACxC,mDAAmD,EACnD,gDAAgD,EAChD,yDAAyD,GAC1D,iEAAgE;AACjE,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACjB,mDAAkD;AAEnD,YAAY,EAAE,YAAY,EAAE,mDAAkD;AAC9E,OAAO,EAAE,sBAAsB,EAAE,kEAAiE;AAClG,OAAO,EAAE,iCAAiC,EAAE,4CAA2C"}
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,6CAA4C;AAClF,YAAY,EACV,gCAAgC,EAChC,oCAAoC,EACpC,kCAAkC,EAClC,yCAAyC,EACzC,iCAAiC,EACjC,2CAA2C,GAC5C,6CAA4C;AAC7C,YAAY,EACV,sDAAsD,EACtD,wCAAwC,EACxC,uCAAuC,EACvC,wCAAwC,EACxC,mDAAmD,EACnD,gDAAgD,EAChD,yDAAyD,GAC1D,iEAAgE;AACjE,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACjB,mDAAkD;AAEnD,YAAY,EAAE,YAAY,EAAE,mDAAkD;AAC9E,OAAO,EAAE,sBAAsB,EAAE,kEAAiE;AAClG,OAAO,EAAE,iCAAiC,EAAE,4CAA2C"}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,
|
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,6CAA4C;AAkBlF,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EACjB,mDAAkD;AAGnD,OAAO,EAAE,sBAAsB,EAAE,kEAAiE;AAClG,OAAO,EAAE,iCAAiC,EAAE,4CAA2C","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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-feature-flag-controller-method-action-types.cjs","sourceRoot":"","sources":["../src/remote-feature-flag-controller-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { RemoteFeatureFlagController } from './remote-feature-flag-controller';\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 * @returns A promise that resolves to the current set of feature flags.\n */\nexport type RemoteFeatureFlagControllerUpdateRemoteFeatureFlagsAction = {\n type: `RemoteFeatureFlagController:updateRemoteFeatureFlags`;\n handler: RemoteFeatureFlagController['updateRemoteFeatureFlags'];\n};\n\n/**\n * Enables the controller, allowing it to make network requests.\n */\nexport type RemoteFeatureFlagControllerEnableAction = {\n type: `RemoteFeatureFlagController:enable`;\n handler: RemoteFeatureFlagController['enable'];\n};\n\n/**\n * Disables the controller, preventing it from making network requests.\n */\nexport type RemoteFeatureFlagControllerDisableAction = {\n type: `RemoteFeatureFlagController:disable`;\n handler: RemoteFeatureFlagController['disable'];\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 */\nexport type RemoteFeatureFlagControllerSetFlagOverrideAction = {\n type: `RemoteFeatureFlagController:setFlagOverride`;\n handler: RemoteFeatureFlagController['setFlagOverride'];\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 */\nexport type RemoteFeatureFlagControllerRemoveFlagOverrideAction = {\n type: `RemoteFeatureFlagController:removeFlagOverride`;\n handler: RemoteFeatureFlagController['removeFlagOverride'];\n};\n\n/**\n * Clears all local feature flag overrides.\n */\nexport type RemoteFeatureFlagControllerClearAllFlagOverridesAction = {\n type: `RemoteFeatureFlagController:clearAllFlagOverrides`;\n handler: RemoteFeatureFlagController['clearAllFlagOverrides'];\n};\n\n/**\n * Union of all RemoteFeatureFlagController action types.\n */\nexport type RemoteFeatureFlagControllerMethodActions =\n | RemoteFeatureFlagControllerUpdateRemoteFeatureFlagsAction\n | RemoteFeatureFlagControllerEnableAction\n | RemoteFeatureFlagControllerDisableAction\n | RemoteFeatureFlagControllerSetFlagOverrideAction\n | RemoteFeatureFlagControllerRemoveFlagOverrideAction\n | RemoteFeatureFlagControllerClearAllFlagOverridesAction;\n"]}
|
|
1
|
+
{"version":3,"file":"remote-feature-flag-controller-method-action-types.cjs","sourceRoot":"","sources":["../src/remote-feature-flag-controller-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { RemoteFeatureFlagController } from './remote-feature-flag-controller.js';\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 * @returns A promise that resolves to the current set of feature flags.\n */\nexport type RemoteFeatureFlagControllerUpdateRemoteFeatureFlagsAction = {\n type: `RemoteFeatureFlagController:updateRemoteFeatureFlags`;\n handler: RemoteFeatureFlagController['updateRemoteFeatureFlags'];\n};\n\n/**\n * Enables the controller, allowing it to make network requests.\n */\nexport type RemoteFeatureFlagControllerEnableAction = {\n type: `RemoteFeatureFlagController:enable`;\n handler: RemoteFeatureFlagController['enable'];\n};\n\n/**\n * Disables the controller, preventing it from making network requests.\n */\nexport type RemoteFeatureFlagControllerDisableAction = {\n type: `RemoteFeatureFlagController:disable`;\n handler: RemoteFeatureFlagController['disable'];\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 */\nexport type RemoteFeatureFlagControllerSetFlagOverrideAction = {\n type: `RemoteFeatureFlagController:setFlagOverride`;\n handler: RemoteFeatureFlagController['setFlagOverride'];\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 */\nexport type RemoteFeatureFlagControllerRemoveFlagOverrideAction = {\n type: `RemoteFeatureFlagController:removeFlagOverride`;\n handler: RemoteFeatureFlagController['removeFlagOverride'];\n};\n\n/**\n * Clears all local feature flag overrides.\n */\nexport type RemoteFeatureFlagControllerClearAllFlagOverridesAction = {\n type: `RemoteFeatureFlagController:clearAllFlagOverrides`;\n handler: RemoteFeatureFlagController['clearAllFlagOverrides'];\n};\n\n/**\n * Union of all RemoteFeatureFlagController action types.\n */\nexport type RemoteFeatureFlagControllerMethodActions =\n | RemoteFeatureFlagControllerUpdateRemoteFeatureFlagsAction\n | RemoteFeatureFlagControllerEnableAction\n | RemoteFeatureFlagControllerDisableAction\n | RemoteFeatureFlagControllerSetFlagOverrideAction\n | RemoteFeatureFlagControllerRemoveFlagOverrideAction\n | RemoteFeatureFlagControllerClearAllFlagOverridesAction;\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-feature-flag-controller-method-action-types.d.cts","sourceRoot":"","sources":["../src/remote-feature-flag-controller-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,2BAA2B,EAAE,
|
|
1
|
+
{"version":3,"file":"remote-feature-flag-controller-method-action-types.d.cts","sourceRoot":"","sources":["../src/remote-feature-flag-controller-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,2BAA2B,EAAE,6CAA4C;AAEvF;;;;;GAKG;AACH,MAAM,MAAM,yDAAyD,GAAG;IACtE,IAAI,EAAE,sDAAsD,CAAC;IAC7D,OAAO,EAAE,2BAA2B,CAAC,0BAA0B,CAAC,CAAC;CAClE,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uCAAuC,GAAG;IACpD,IAAI,EAAE,oCAAoC,CAAC;IAC3C,OAAO,EAAE,2BAA2B,CAAC,QAAQ,CAAC,CAAC;CAChD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,wCAAwC,GAAG;IACrD,IAAI,EAAE,qCAAqC,CAAC;IAC5C,OAAO,EAAE,2BAA2B,CAAC,SAAS,CAAC,CAAC;CACjD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,gDAAgD,GAAG;IAC7D,IAAI,EAAE,6CAA6C,CAAC;IACpD,OAAO,EAAE,2BAA2B,CAAC,iBAAiB,CAAC,CAAC;CACzD,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,mDAAmD,GAAG;IAChE,IAAI,EAAE,gDAAgD,CAAC;IACvD,OAAO,EAAE,2BAA2B,CAAC,oBAAoB,CAAC,CAAC;CAC5D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,sDAAsD,GAAG;IACnE,IAAI,EAAE,mDAAmD,CAAC;IAC1D,OAAO,EAAE,2BAA2B,CAAC,uBAAuB,CAAC,CAAC;CAC/D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,wCAAwC,GAChD,yDAAyD,GACzD,uCAAuC,GACvC,wCAAwC,GACxC,gDAAgD,GAChD,mDAAmD,GACnD,sDAAsD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-feature-flag-controller-method-action-types.d.mts","sourceRoot":"","sources":["../src/remote-feature-flag-controller-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,2BAA2B,EAAE,
|
|
1
|
+
{"version":3,"file":"remote-feature-flag-controller-method-action-types.d.mts","sourceRoot":"","sources":["../src/remote-feature-flag-controller-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,2BAA2B,EAAE,6CAA4C;AAEvF;;;;;GAKG;AACH,MAAM,MAAM,yDAAyD,GAAG;IACtE,IAAI,EAAE,sDAAsD,CAAC;IAC7D,OAAO,EAAE,2BAA2B,CAAC,0BAA0B,CAAC,CAAC;CAClE,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uCAAuC,GAAG;IACpD,IAAI,EAAE,oCAAoC,CAAC;IAC3C,OAAO,EAAE,2BAA2B,CAAC,QAAQ,CAAC,CAAC;CAChD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,wCAAwC,GAAG;IACrD,IAAI,EAAE,qCAAqC,CAAC;IAC5C,OAAO,EAAE,2BAA2B,CAAC,SAAS,CAAC,CAAC;CACjD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,gDAAgD,GAAG;IAC7D,IAAI,EAAE,6CAA6C,CAAC;IACpD,OAAO,EAAE,2BAA2B,CAAC,iBAAiB,CAAC,CAAC;CACzD,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,mDAAmD,GAAG;IAChE,IAAI,EAAE,gDAAgD,CAAC;IACvD,OAAO,EAAE,2BAA2B,CAAC,oBAAoB,CAAC,CAAC;CAC5D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,sDAAsD,GAAG;IACnE,IAAI,EAAE,mDAAmD,CAAC;IAC1D,OAAO,EAAE,2BAA2B,CAAC,uBAAuB,CAAC,CAAC;CAC/D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,wCAAwC,GAChD,yDAAyD,GACzD,uCAAuC,GACvC,wCAAwC,GACxC,gDAAgD,GAChD,mDAAmD,GACnD,sDAAsD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-feature-flag-controller-method-action-types.mjs","sourceRoot":"","sources":["../src/remote-feature-flag-controller-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { RemoteFeatureFlagController } from './remote-feature-flag-controller';\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 * @returns A promise that resolves to the current set of feature flags.\n */\nexport type RemoteFeatureFlagControllerUpdateRemoteFeatureFlagsAction = {\n type: `RemoteFeatureFlagController:updateRemoteFeatureFlags`;\n handler: RemoteFeatureFlagController['updateRemoteFeatureFlags'];\n};\n\n/**\n * Enables the controller, allowing it to make network requests.\n */\nexport type RemoteFeatureFlagControllerEnableAction = {\n type: `RemoteFeatureFlagController:enable`;\n handler: RemoteFeatureFlagController['enable'];\n};\n\n/**\n * Disables the controller, preventing it from making network requests.\n */\nexport type RemoteFeatureFlagControllerDisableAction = {\n type: `RemoteFeatureFlagController:disable`;\n handler: RemoteFeatureFlagController['disable'];\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 */\nexport type RemoteFeatureFlagControllerSetFlagOverrideAction = {\n type: `RemoteFeatureFlagController:setFlagOverride`;\n handler: RemoteFeatureFlagController['setFlagOverride'];\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 */\nexport type RemoteFeatureFlagControllerRemoveFlagOverrideAction = {\n type: `RemoteFeatureFlagController:removeFlagOverride`;\n handler: RemoteFeatureFlagController['removeFlagOverride'];\n};\n\n/**\n * Clears all local feature flag overrides.\n */\nexport type RemoteFeatureFlagControllerClearAllFlagOverridesAction = {\n type: `RemoteFeatureFlagController:clearAllFlagOverrides`;\n handler: RemoteFeatureFlagController['clearAllFlagOverrides'];\n};\n\n/**\n * Union of all RemoteFeatureFlagController action types.\n */\nexport type RemoteFeatureFlagControllerMethodActions =\n | RemoteFeatureFlagControllerUpdateRemoteFeatureFlagsAction\n | RemoteFeatureFlagControllerEnableAction\n | RemoteFeatureFlagControllerDisableAction\n | RemoteFeatureFlagControllerSetFlagOverrideAction\n | RemoteFeatureFlagControllerRemoveFlagOverrideAction\n | RemoteFeatureFlagControllerClearAllFlagOverridesAction;\n"]}
|
|
1
|
+
{"version":3,"file":"remote-feature-flag-controller-method-action-types.mjs","sourceRoot":"","sources":["../src/remote-feature-flag-controller-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { RemoteFeatureFlagController } from './remote-feature-flag-controller.js';\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 * @returns A promise that resolves to the current set of feature flags.\n */\nexport type RemoteFeatureFlagControllerUpdateRemoteFeatureFlagsAction = {\n type: `RemoteFeatureFlagController:updateRemoteFeatureFlags`;\n handler: RemoteFeatureFlagController['updateRemoteFeatureFlags'];\n};\n\n/**\n * Enables the controller, allowing it to make network requests.\n */\nexport type RemoteFeatureFlagControllerEnableAction = {\n type: `RemoteFeatureFlagController:enable`;\n handler: RemoteFeatureFlagController['enable'];\n};\n\n/**\n * Disables the controller, preventing it from making network requests.\n */\nexport type RemoteFeatureFlagControllerDisableAction = {\n type: `RemoteFeatureFlagController:disable`;\n handler: RemoteFeatureFlagController['disable'];\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 */\nexport type RemoteFeatureFlagControllerSetFlagOverrideAction = {\n type: `RemoteFeatureFlagController:setFlagOverride`;\n handler: RemoteFeatureFlagController['setFlagOverride'];\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 */\nexport type RemoteFeatureFlagControllerRemoveFlagOverrideAction = {\n type: `RemoteFeatureFlagController:removeFlagOverride`;\n handler: RemoteFeatureFlagController['removeFlagOverride'];\n};\n\n/**\n * Clears all local feature flag overrides.\n */\nexport type RemoteFeatureFlagControllerClearAllFlagOverridesAction = {\n type: `RemoteFeatureFlagController:clearAllFlagOverrides`;\n handler: RemoteFeatureFlagController['clearAllFlagOverrides'];\n};\n\n/**\n * Union of all RemoteFeatureFlagController action types.\n */\nexport type RemoteFeatureFlagControllerMethodActions =\n | RemoteFeatureFlagControllerUpdateRemoteFeatureFlagsAction\n | RemoteFeatureFlagControllerEnableAction\n | RemoteFeatureFlagControllerDisableAction\n | RemoteFeatureFlagControllerSetFlagOverrideAction\n | RemoteFeatureFlagControllerRemoveFlagOverrideAction\n | RemoteFeatureFlagControllerClearAllFlagOverridesAction;\n"]}
|
|
@@ -15,8 +15,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.RemoteFeatureFlagController = exports.getDefaultRemoteFeatureFlagControllerState = exports.DEFAULT_CACHE_DURATION = exports.controllerName = void 0;
|
|
16
16
|
const base_controller_1 = require("@metamask/base-controller");
|
|
17
17
|
const utils_1 = require("@metamask/utils");
|
|
18
|
-
const
|
|
19
|
-
const
|
|
18
|
+
const user_segmentation_utils_js_1 = require("./utils/user-segmentation-utils.cjs");
|
|
19
|
+
const version_js_1 = require("./utils/version.cjs");
|
|
20
20
|
// === GENERAL ===
|
|
21
21
|
exports.controllerName = 'RemoteFeatureFlagController';
|
|
22
22
|
exports.DEFAULT_CACHE_DURATION = 24 * 60 * 60 * 1000; // 1 day
|
|
@@ -94,7 +94,7 @@ exports.getDefaultRemoteFeatureFlagControllerState = getDefaultRemoteFeatureFlag
|
|
|
94
94
|
*/
|
|
95
95
|
function findExplicitIdMatch(entries, normalizedId) {
|
|
96
96
|
for (const entry of entries) {
|
|
97
|
-
if (!(0,
|
|
97
|
+
if (!(0, user_segmentation_utils_js_1.isFeatureFlagWithScopeValue)(entry)) {
|
|
98
98
|
continue;
|
|
99
99
|
}
|
|
100
100
|
const { metaMetricsIds } = entry;
|
|
@@ -352,10 +352,10 @@ async function _RemoteFeatureFlagController_updateCache(remoteFeatureFlags) {
|
|
|
352
352
|
};
|
|
353
353
|
});
|
|
354
354
|
}, _RemoteFeatureFlagController_processVersionBasedFlag = function _RemoteFeatureFlagController_processVersionBasedFlag(flagValue) {
|
|
355
|
-
if (!(0,
|
|
355
|
+
if (!(0, version_js_1.isVersionFeatureFlag)(flagValue)) {
|
|
356
356
|
return flagValue;
|
|
357
357
|
}
|
|
358
|
-
return (0,
|
|
358
|
+
return (0, version_js_1.getVersionData)(flagValue, __classPrivateFieldGet(this, _RemoteFeatureFlagController_clientVersion, "f"));
|
|
359
359
|
}, _RemoteFeatureFlagController_processRemoteFeatureFlags = async function _RemoteFeatureFlagController_processRemoteFeatureFlags(remoteFeatureFlags) {
|
|
360
360
|
const processedFlags = {};
|
|
361
361
|
const metaMetricsId = __classPrivateFieldGet(this, _RemoteFeatureFlagController_getMetaMetricsId, "f").call(this);
|
|
@@ -368,7 +368,7 @@ async function _RemoteFeatureFlagController_updateCache(remoteFeatureFlags) {
|
|
|
368
368
|
}
|
|
369
369
|
if (Array.isArray(processedValue)) {
|
|
370
370
|
// Validate array has valid threshold items before doing expensive crypto operation
|
|
371
|
-
const hasValidThresholds = processedValue.some(
|
|
371
|
+
const hasValidThresholds = processedValue.some(user_segmentation_utils_js_1.isFeatureFlagWithScopeValue);
|
|
372
372
|
if (!hasValidThresholds) {
|
|
373
373
|
// Not a threshold array - preserve as-is
|
|
374
374
|
processedFlags[remoteFeatureFlagName] = processedValue;
|
|
@@ -395,13 +395,13 @@ async function _RemoteFeatureFlagController_updateCache(remoteFeatureFlags) {
|
|
|
395
395
|
const cacheKey = `${metaMetricsId}:${remoteFeatureFlagName}`;
|
|
396
396
|
let thresholdValue = this.state.thresholdCache?.[cacheKey];
|
|
397
397
|
if (thresholdValue === undefined) {
|
|
398
|
-
thresholdValue = await (0,
|
|
398
|
+
thresholdValue = await (0, user_segmentation_utils_js_1.calculateThresholdForFlag)(metaMetricsId, remoteFeatureFlagName);
|
|
399
399
|
// Collect new threshold for batched state update
|
|
400
400
|
thresholdCacheUpdates[cacheKey] = thresholdValue;
|
|
401
401
|
}
|
|
402
402
|
const threshold = thresholdValue;
|
|
403
403
|
const selectedGroup = processedValue.find((featureFlag) => {
|
|
404
|
-
if (!(0,
|
|
404
|
+
if (!(0, user_segmentation_utils_js_1.isFeatureFlagWithScopeValue)(featureFlag)) {
|
|
405
405
|
return false;
|
|
406
406
|
}
|
|
407
407
|
return threshold <= featureFlag.scope.value;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-feature-flag-controller.cjs","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 { 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';\nimport type { RemoteFeatureFlagControllerMethodActions } from './remote-feature-flag-controller-method-action-types';\nimport type {\n FeatureFlags,\n ServiceResponse,\n FeatureFlagScopeValue,\n} from './remote-feature-flag-controller-types';\nimport {\n calculateThresholdForFlag,\n isFeatureFlagWithScopeValue,\n} from './utils/user-segmentation-utils';\nimport { isVersionFeatureFlag, getVersionData } from './utils/version';\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 * Returns a copy of `flags` with `metaMetricsIds` removed from every\n * threshold entry. Used before persisting raw flags to state so that\n * MetaMetrics IDs are never written to state logs or debug snapshots.\n *\n * @param flags - The raw feature flags object from the API.\n * @returns A new object with the same structure but without any\n * `metaMetricsIds` fields inside threshold entry arrays.\n */\nfunction redactMetaMetricsIds(flags: FeatureFlags): FeatureFlags {\n const result: FeatureFlags = {};\n for (const [name, value] of Object.entries(flags)) {\n if (!Array.isArray(value)) {\n result[name] = value;\n continue;\n }\n result[name] = value.map((entry) => {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n return entry;\n }\n const entryRecord = entry as Record<string, Json>;\n if (entryRecord.metaMetricsIds === undefined) {\n return entry;\n }\n const copy: Record<string, Json> = { ...entryRecord };\n delete copy.metaMetricsIds;\n return copy as Json;\n });\n }\n return result;\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 #clientVersion: SemVerVersion;\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.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 */\n constructor({\n messenger,\n state,\n clientConfigApiService,\n fetchInterval = DEFAULT_CACHE_DURATION,\n disabled = false,\n getMetaMetricsId,\n clientVersion,\n prevClientVersion,\n }: {\n messenger: RemoteFeatureFlagControllerMessenger;\n state?: Partial<RemoteFeatureFlagControllerState>;\n clientConfigApiService: AbstractClientConfigApiService;\n getMetaMetricsId: () => string;\n fetchInterval?: number;\n disabled?: boolean;\n clientVersion: string;\n prevClientVersion?: string;\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 const localOverrides = initialState.localOverrides ?? {};\n\n super({\n name: controllerName,\n metadata: remoteFeatureFlagControllerMetadata,\n messenger,\n state: {\n ...initialState,\n remoteFeatureFlags: {\n ...initialState.remoteFeatureFlags,\n ...localOverrides,\n },\n cacheTimestamp: hasClientVersionChanged\n ? 0\n : initialState.cacheTimestamp,\n },\n });\n\n this.#processedRemoteFeatureFlags = {\n ...initialState.remoteFeatureFlags,\n };\n for (const [flagName, overrideValue] of Object.entries(localOverrides)) {\n if (this.#processedRemoteFeatureFlags[flagName] === overrideValue) {\n delete this.#processedRemoteFeatureFlags[flagName];\n }\n }\n\n this.#fetchInterval = fetchInterval;\n this.#disabled = disabled;\n this.#clientConfigApiService = clientConfigApiService;\n this.#getMetaMetricsId = getMetaMetricsId;\n this.#clientVersion = clientVersion;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\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 * @returns A promise that resolves to the current set of feature flags.\n */\n async updateRemoteFeatureFlags(): Promise<void> {\n if (this.#disabled || !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 * 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 {\n processedFlags,\n thresholdCacheUpdates,\n featureFlagThresholdGroupUpdates,\n } = await this.#processRemoteFeatureFlags(remoteFeatureFlags);\n\n const metaMetricsId = this.#getMetaMetricsId();\n const currentFlagNames = Object.keys(remoteFeatureFlags);\n\n // Build updated threshold cache\n const updatedThresholdCache = { ...(this.state.thresholdCache ?? {}) };\n\n // Apply new thresholds\n for (const [cacheKey, threshold] of Object.entries(thresholdCacheUpdates)) {\n updatedThresholdCache[cacheKey] = threshold;\n }\n\n // Clean up stale entries\n for (const cacheKey of Object.keys(updatedThresholdCache)) {\n const [cachedMetaMetricsId, ...cachedFlagNameParts] = cacheKey.split(':');\n const cachedFlagName = cachedFlagNameParts.join(':');\n if (\n cachedMetaMetricsId === metaMetricsId &&\n !currentFlagNames.includes(cachedFlagName)\n ) {\n delete updatedThresholdCache[cacheKey];\n }\n }\n\n // Strip metaMetricsIds from processed flags so they never appear in\n // remoteFeatureFlags state or #processedRemoteFeatureFlags. Arrays that\n // were preserved as-is (e.g. when metaMetricsId is missing) would\n // otherwise leak explicit-targeting IDs into diagnostics.\n const redactedProcessedFlags = redactMetaMetricsIds(processedFlags);\n\n // Single state update with all changes batched together\n this.#processedRemoteFeatureFlags = redactedProcessedFlags;\n\n this.update(() => {\n return {\n ...this.state,\n remoteFeatureFlags: {\n ...redactedProcessedFlags,\n ...this.state.localOverrides,\n },\n rawRemoteFeatureFlags: redactMetaMetricsIds(remoteFeatureFlags),\n cacheTimestamp: Date.now(),\n thresholdCache: updatedThresholdCache,\n featureFlagThresholdGroups: featureFlagThresholdGroupUpdates,\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 async #processRemoteFeatureFlags(remoteFeatureFlags: FeatureFlags): Promise<{\n processedFlags: FeatureFlags;\n thresholdCacheUpdates: Record<string, number>;\n featureFlagThresholdGroupUpdates: Record<string, string>;\n }> {\n const processedFlags: FeatureFlags = {};\n const metaMetricsId = this.#getMetaMetricsId();\n const thresholdCacheUpdates: Record<string, number> = {};\n const featureFlagThresholdGroupUpdates: 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 // Skip threshold processing if metaMetricsId is not available\n if (!metaMetricsId) {\n // Preserve array as-is when user hasn't opted into MetaMetrics\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 = findExplicitIdMatch(\n processedValue,\n normalizedMetaMetricsId,\n );\n\n if (explicitMatch) {\n processedValue = explicitMatch.value;\n if (explicitMatch.name) {\n featureFlagThresholdGroupUpdates[remoteFeatureFlagName] =\n explicitMatch.name;\n }\n } else {\n // Fall back to hash-based threshold selection with cache\n const cacheKey = `${metaMetricsId}:${remoteFeatureFlagName}` as const;\n let thresholdValue = this.state.thresholdCache?.[cacheKey];\n\n if (thresholdValue === undefined) {\n thresholdValue = await calculateThresholdForFlag(\n metaMetricsId,\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 featureFlagThresholdGroupUpdates[remoteFeatureFlagName] =\n selectedGroup.name;\n }\n }\n }\n }\n\n processedFlags[remoteFeatureFlagName] = processedValue;\n }\n\n return {\n processedFlags,\n thresholdCacheUpdates,\n featureFlagThresholdGroupUpdates,\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: {\n ...this.state.remoteFeatureFlags,\n [flagName]: value,\n },\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 const remoteFeatureFlags = { ...this.state.remoteFeatureFlags };\n const processedValue = this.#processedRemoteFeatureFlags[flagName];\n\n if (processedValue === undefined) {\n delete remoteFeatureFlags[flagName];\n } else {\n remoteFeatureFlags[flagName] = processedValue;\n }\n\n this.update(() => {\n return {\n ...this.state,\n localOverrides: newLocalOverrides,\n remoteFeatureFlags,\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.#processedRemoteFeatureFlags },\n };\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"remote-feature-flag-controller.cjs","sourceRoot":"","sources":["../src/remote-feature-flag-controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,+DAGmC;AAGnC,2CAAuD;AAUvD,oFAG4C;AAC5C,oDAA0E;AAE1E,kBAAkB;AAEL,QAAA,cAAc,GAAG,6BAA6B,CAAC;AAC/C,QAAA,sBAAsB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,QAAQ;AAanE,MAAM,mCAAmC,GAAG;IAC1C,kBAAkB,EAAE;QAClB,kBAAkB,EAAE,IAAI;QACxB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,IAAI;QAC5B,QAAQ,EAAE,IAAI;KACf;IACD,cAAc,EAAE;QACd,kBAAkB,EAAE,IAAI;QACxB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,IAAI;QAC5B,QAAQ,EAAE,IAAI;KACf;IACD,qBAAqB,EAAE;QACrB,kBAAkB,EAAE,IAAI;QACxB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,IAAI;QAC5B,QAAQ,EAAE,KAAK;KAChB;IACD,cAAc,EAAE;QACd,kBAAkB,EAAE,IAAI;QACxB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,IAAI;QAC5B,QAAQ,EAAE,KAAK;KAChB;IACD,cAAc,EAAE;QACd,kBAAkB,EAAE,KAAK;QACzB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,KAAK;QAC7B,QAAQ,EAAE,KAAK;KAChB;IACD,0BAA0B,EAAE;QAC1B,kBAAkB,EAAE,IAAI;QACxB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,IAAI;QAC5B,QAAQ,EAAE,KAAK;KAChB;CACF,CAAC;AAEF,oBAAoB;AAEpB,MAAM,yBAAyB,GAAG;IAChC,uBAAuB;IACvB,SAAS;IACT,QAAQ;IACR,oBAAoB;IACpB,iBAAiB;IACjB,0BAA0B;CAClB,CAAC;AA2BX;;;;GAIG;AACH,SAAgB,0CAA0C;IACxD,OAAO;QACL,kBAAkB,EAAE,EAAE;QACtB,cAAc,EAAE,EAAE;QAClB,qBAAqB,EAAE,EAAE;QACzB,cAAc,EAAE,CAAC;KAClB,CAAC;AACJ,CAAC;AAPD,gGAOC;AAED;;;;;;;;;;GAUG;AACH,SAAS,mBAAmB,CAC1B,OAAe,EACf,YAAoB;IAEpB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAA,wDAA2B,EAAC,KAAK,CAAC,EAAE,CAAC;YACxC,SAAS;QACX,CAAC;QACD,MAAM,EAAE,cAAc,EAAE,GAAG,KAAK,CAAC;QACjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;YACnC,SAAS;QACX,CAAC;QACD,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAClC,CAAC,EAAE,EAAE,EAAE,CACL,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,YAAY,CACrE,CAAC;QACF,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,oBAAoB,CAAC,KAAmB;IAC/C,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAClD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;YACrB,SAAS;QACX,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACjC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBACxE,OAAO,KAAK,CAAC;YACf,CAAC;YACD,MAAM,WAAW,GAAG,KAA6B,CAAC;YAClD,IAAI,WAAW,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;gBAC7C,OAAO,KAAK,CAAC;YACf,CAAC;YACD,MAAM,IAAI,GAAyB,EAAE,GAAG,WAAW,EAAE,CAAC;YACtD,OAAO,IAAI,CAAC,cAAc,CAAC;YAC3B,OAAO,IAAY,CAAC;QACtB,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAa,2BAA4B,SAAQ,gCAIhD;IAeC;;;;;;;;;;;;OAYG;IACH,YAAY,EACV,SAAS,EACT,KAAK,EACL,sBAAsB,EACtB,aAAa,GAAG,8BAAsB,EACtC,QAAQ,GAAG,KAAK,EAChB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAUlB;QACC,IAAI,CAAC,IAAA,4BAAoB,EAAC,aAAa,CAAC,EAAE,CAAC;YACzC,MAAM,IAAI,KAAK,CACb,2BAA2B,aAAa,iDAAiD,CAC1F,CAAC;QACJ,CAAC;QAED,MAAM,YAAY,GAAqC;YACrD,GAAG,0CAA0C,EAAE;YAC/C,GAAG,KAAK;SACT,CAAC;QAEF,MAAM,uBAAuB,GAC3B,IAAA,4BAAoB,EAAC,iBAAiB,CAAC;YACvC,iBAAiB,KAAK,aAAa,CAAC;QAEtC,MAAM,cAAc,GAAG,YAAY,CAAC,cAAc,IAAI,EAAE,CAAC;QAEzD,KAAK,CAAC;YACJ,IAAI,EAAE,sBAAc;YACpB,QAAQ,EAAE,mCAAmC;YAC7C,SAAS;YACT,KAAK,EAAE;gBACL,GAAG,YAAY;gBACf,kBAAkB,EAAE;oBAClB,GAAG,YAAY,CAAC,kBAAkB;oBAClC,GAAG,cAAc;iBAClB;gBACD,cAAc,EAAE,uBAAuB;oBACrC,CAAC,CAAC,CAAC;oBACH,CAAC,CAAC,YAAY,CAAC,cAAc;aAChC;SACF,CAAC,CAAC;;QA7EI,6DAAuB;QAEhC,wDAAmB;QAEV,sEAAwD;QAEjE,oEAAiD;QAExC,gEAAgC;QAEhC,6DAA8B;QAEvC,mEAA6C,EAAE,EAAC;QAmE9C,uBAAA,IAAI,4DAAgC;YAClC,GAAG,YAAY,CAAC,kBAAkB;SACnC,MAAA,CAAC;QACF,KAAK,MAAM,CAAC,QAAQ,EAAE,aAAa,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;YACvE,IAAI,uBAAA,IAAI,gEAA6B,CAAC,QAAQ,CAAC,KAAK,aAAa,EAAE,CAAC;gBAClE,OAAO,uBAAA,IAAI,gEAA6B,CAAC,QAAQ,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;QAED,uBAAA,IAAI,8CAAkB,aAAa,MAAA,CAAC;QACpC,uBAAA,IAAI,yCAAa,QAAQ,MAAA,CAAC;QAC1B,uBAAA,IAAI,uDAA2B,sBAAsB,MAAA,CAAC;QACtD,uBAAA,IAAI,iDAAqB,gBAAgB,MAAA,CAAC;QAC1C,uBAAA,IAAI,8CAAkB,aAAa,MAAA,CAAC;QAEpC,IAAI,CAAC,SAAS,CAAC,4BAA4B,CACzC,IAAI,EACJ,yBAAyB,CAC1B,CAAC;IACJ,CAAC;IAWD;;;;;OAKG;IACH,KAAK,CAAC,wBAAwB;QAC5B,IAAI,uBAAA,IAAI,6CAAU,IAAI,CAAC,uBAAA,IAAI,2FAAgB,MAApB,IAAI,CAAkB,EAAE,CAAC;YAC9C,OAAO;QACT,CAAC;QAED,IAAI,UAAU,CAAC;QAEf,IAAI,uBAAA,IAAI,yDAAsB,EAAE,CAAC;YAC/B,MAAM,uBAAA,IAAI,yDAAsB,CAAC;YACjC,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,uBAAA,IAAI,qDACF,uBAAA,IAAI,2DAAwB,CAAC,uBAAuB,EAAE,MAAA,CAAC;YAEzD,UAAU,GAAG,MAAM,uBAAA,IAAI,yDAAsB,CAAC;QAChD,CAAC;gBAAS,CAAC;YACT,uBAAA,IAAI,qDAAyB,SAAS,MAAA,CAAC;QACzC,CAAC;QAED,MAAM,uBAAA,IAAI,wFAAa,MAAjB,IAAI,EAAc,UAAU,CAAC,kBAAkB,CAAC,CAAC;IACzD,CAAC;IA8KD;;OAEG;IACH,MAAM;QACJ,uBAAA,IAAI,yCAAa,KAAK,MAAA,CAAC;IACzB,CAAC;IAED;;OAEG;IACH,OAAO;QACL,uBAAA,IAAI,yCAAa,IAAI,MAAA,CAAC;IACxB,CAAC;IAED;;;;;OAKG;IACH,eAAe,CAAC,QAAgB,EAAE,KAAW;QAC3C,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACf,MAAM,cAAc,GAAG;gBACrB,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc;gBAC5B,CAAC,QAAQ,CAAC,EAAE,KAAK;aAClB,CAAC;YAEF,OAAO;gBACL,GAAG,IAAI,CAAC,KAAK;gBACb,cAAc;gBACd,kBAAkB,EAAE;oBAClB,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB;oBAChC,CAAC,QAAQ,CAAC,EAAE,KAAK;iBAClB;aACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,kBAAkB,CAAC,QAAgB;QACjC,MAAM,iBAAiB,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;QAC3D,OAAO,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAEnC,MAAM,kBAAkB,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,EAAE,CAAC;QAChE,MAAM,cAAc,GAAG,uBAAA,IAAI,gEAA6B,CAAC,QAAQ,CAAC,CAAC;QAEnE,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;YACjC,OAAO,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QACtC,CAAC;aAAM,CAAC;YACN,kBAAkB,CAAC,QAAQ,CAAC,GAAG,cAAc,CAAC;QAChD,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACf,OAAO;gBACL,GAAG,IAAI,CAAC,KAAK;gBACb,cAAc,EAAE,iBAAiB;gBACjC,kBAAkB;aACnB,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,qBAAqB;QACnB,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACf,OAAO;gBACL,GAAG,IAAI,CAAC,KAAK;gBACb,cAAc,EAAE,EAAE;gBAClB,kBAAkB,EAAE,EAAE,GAAG,uBAAA,IAAI,gEAA6B,EAAE;aAC7D,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAzYD,kEAyYC;;IA1RG,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,uBAAA,IAAI,kDAAe,CAAC;AACtE,CAAC;AAgCD;;;;GAIG;AACH,KAAK,mDAAc,kBAAgC;IACjD,MAAM,EACJ,cAAc,EACd,qBAAqB,EACrB,gCAAgC,GACjC,GAAG,MAAM,uBAAA,IAAI,sGAA2B,MAA/B,IAAI,EAA4B,kBAAkB,CAAC,CAAC;IAE9D,MAAM,aAAa,GAAG,uBAAA,IAAI,qDAAkB,MAAtB,IAAI,CAAoB,CAAC;IAC/C,MAAM,gBAAgB,GAAG,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAEzD,gCAAgC;IAChC,MAAM,qBAAqB,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,IAAI,EAAE,CAAC,EAAE,CAAC;IAEvE,uBAAuB;IACvB,KAAK,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,qBAAqB,CAAC,EAAE,CAAC;QAC1E,qBAAqB,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC;IAC9C,CAAC;IAED,yBAAyB;IACzB,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,EAAE,CAAC;QAC1D,MAAM,CAAC,mBAAmB,EAAE,GAAG,mBAAmB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1E,MAAM,cAAc,GAAG,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrD,IACE,mBAAmB,KAAK,aAAa;YACrC,CAAC,gBAAgB,CAAC,QAAQ,CAAC,cAAc,CAAC,EAC1C,CAAC;YACD,OAAO,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,oEAAoE;IACpE,yEAAyE;IACzE,kEAAkE;IAClE,0DAA0D;IAC1D,MAAM,sBAAsB,GAAG,oBAAoB,CAAC,cAAc,CAAC,CAAC;IAEpE,wDAAwD;IACxD,uBAAA,IAAI,4DAAgC,sBAAsB,MAAA,CAAC;IAE3D,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;QACf,OAAO;YACL,GAAG,IAAI,CAAC,KAAK;YACb,kBAAkB,EAAE;gBAClB,GAAG,sBAAsB;gBACzB,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc;aAC7B;YACD,qBAAqB,EAAE,oBAAoB,CAAC,kBAAkB,CAAC;YAC/D,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE;YAC1B,cAAc,EAAE,qBAAqB;YACrC,0BAA0B,EAAE,gCAAgC;SAC7D,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,uHAQwB,SAAe;IACtC,IAAI,CAAC,IAAA,iCAAoB,EAAC,SAAS,CAAC,EAAE,CAAC;QACrC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,IAAA,2BAAc,EAAC,SAAS,EAAE,uBAAA,IAAI,kDAAe,CAAC,CAAC;AACxD,CAAC,2DAED,KAAK,iEAA4B,kBAAgC;IAK/D,MAAM,cAAc,GAAiB,EAAE,CAAC;IACxC,MAAM,aAAa,GAAG,uBAAA,IAAI,qDAAkB,MAAtB,IAAI,CAAoB,CAAC;IAC/C,MAAM,qBAAqB,GAA2B,EAAE,CAAC;IACzD,MAAM,gCAAgC,GAA2B,EAAE,CAAC;IAEpE,KAAK,MAAM,CACT,qBAAqB,EACrB,sBAAsB,EACvB,IAAI,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACxC,IAAI,cAAc,GAAG,uBAAA,IAAI,oGAAyB,MAA7B,IAAI,EACvB,sBAAsB,CACvB,CAAC;QACF,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;YAC5B,SAAS;QACX,CAAC;QAED,IAAI,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;YAClC,mFAAmF;YACnF,MAAM,kBAAkB,GAAG,cAAc,CAAC,IAAI,CAC5C,wDAA2B,CAC5B,CAAC;YAEF,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBACxB,yCAAyC;gBACzC,cAAc,CAAC,qBAAqB,CAAC,GAAG,cAAc,CAAC;gBACvD,SAAS;YACX,CAAC;YAED,8DAA8D;YAC9D,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,+DAA+D;gBAC/D,cAAc,CAAC,qBAAqB,CAAC,GAAG,cAAc,CAAC;gBACvD,SAAS;YACX,CAAC;YAED,0EAA0E;YAC1E,MAAM,uBAAuB,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YACnE,MAAM,aAAa,GAAG,mBAAmB,CACvC,cAAc,EACd,uBAAuB,CACxB,CAAC;YAEF,IAAI,aAAa,EAAE,CAAC;gBAClB,cAAc,GAAG,aAAa,CAAC,KAAK,CAAC;gBACrC,IAAI,aAAa,CAAC,IAAI,EAAE,CAAC;oBACvB,gCAAgC,CAAC,qBAAqB,CAAC;wBACrD,aAAa,CAAC,IAAI,CAAC;gBACvB,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,yDAAyD;gBACzD,MAAM,QAAQ,GAAG,GAAG,aAAa,IAAI,qBAAqB,EAAW,CAAC;gBACtE,IAAI,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC,QAAQ,CAAC,CAAC;gBAE3D,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;oBACjC,cAAc,GAAG,MAAM,IAAA,sDAAyB,EAC9C,aAAa,EACb,qBAAqB,CACtB,CAAC;oBAEF,iDAAiD;oBACjD,qBAAqB,CAAC,QAAQ,CAAC,GAAG,cAAc,CAAC;gBACnD,CAAC;gBAED,MAAM,SAAS,GAAG,cAAc,CAAC;gBACjC,MAAM,aAAa,GAAG,cAAc,CAAC,IAAI,CACvC,CAAC,WAAW,EAAwC,EAAE;oBACpD,IAAI,CAAC,IAAA,wDAA2B,EAAC,WAAW,CAAC,EAAE,CAAC;wBAC9C,OAAO,KAAK,CAAC;oBACf,CAAC;oBAED,OAAO,SAAS,IAAI,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC;gBAC9C,CAAC,CACF,CAAC;gBAEF,IAAI,aAAa,EAAE,CAAC;oBAClB,cAAc,GAAG,aAAa,CAAC,KAAK,CAAC;oBACrC,IAAI,aAAa,CAAC,IAAI,EAAE,CAAC;wBACvB,gCAAgC,CAAC,qBAAqB,CAAC;4BACrD,aAAa,CAAC,IAAI,CAAC;oBACvB,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,cAAc,CAAC,qBAAqB,CAAC,GAAG,cAAc,CAAC;IACzD,CAAC;IAED,OAAO;QACL,cAAc;QACd,qBAAqB;QACrB,gCAAgC;KACjC,CAAC;AACJ,CAAC","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 { 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 * Returns a copy of `flags` with `metaMetricsIds` removed from every\n * threshold entry. Used before persisting raw flags to state so that\n * MetaMetrics IDs are never written to state logs or debug snapshots.\n *\n * @param flags - The raw feature flags object from the API.\n * @returns A new object with the same structure but without any\n * `metaMetricsIds` fields inside threshold entry arrays.\n */\nfunction redactMetaMetricsIds(flags: FeatureFlags): FeatureFlags {\n const result: FeatureFlags = {};\n for (const [name, value] of Object.entries(flags)) {\n if (!Array.isArray(value)) {\n result[name] = value;\n continue;\n }\n result[name] = value.map((entry) => {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n return entry;\n }\n const entryRecord = entry as Record<string, Json>;\n if (entryRecord.metaMetricsIds === undefined) {\n return entry;\n }\n const copy: Record<string, Json> = { ...entryRecord };\n delete copy.metaMetricsIds;\n return copy as Json;\n });\n }\n return result;\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 #clientVersion: SemVerVersion;\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.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 */\n constructor({\n messenger,\n state,\n clientConfigApiService,\n fetchInterval = DEFAULT_CACHE_DURATION,\n disabled = false,\n getMetaMetricsId,\n clientVersion,\n prevClientVersion,\n }: {\n messenger: RemoteFeatureFlagControllerMessenger;\n state?: Partial<RemoteFeatureFlagControllerState>;\n clientConfigApiService: AbstractClientConfigApiService;\n getMetaMetricsId: () => string;\n fetchInterval?: number;\n disabled?: boolean;\n clientVersion: string;\n prevClientVersion?: string;\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 const localOverrides = initialState.localOverrides ?? {};\n\n super({\n name: controllerName,\n metadata: remoteFeatureFlagControllerMetadata,\n messenger,\n state: {\n ...initialState,\n remoteFeatureFlags: {\n ...initialState.remoteFeatureFlags,\n ...localOverrides,\n },\n cacheTimestamp: hasClientVersionChanged\n ? 0\n : initialState.cacheTimestamp,\n },\n });\n\n this.#processedRemoteFeatureFlags = {\n ...initialState.remoteFeatureFlags,\n };\n for (const [flagName, overrideValue] of Object.entries(localOverrides)) {\n if (this.#processedRemoteFeatureFlags[flagName] === overrideValue) {\n delete this.#processedRemoteFeatureFlags[flagName];\n }\n }\n\n this.#fetchInterval = fetchInterval;\n this.#disabled = disabled;\n this.#clientConfigApiService = clientConfigApiService;\n this.#getMetaMetricsId = getMetaMetricsId;\n this.#clientVersion = clientVersion;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\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 * @returns A promise that resolves to the current set of feature flags.\n */\n async updateRemoteFeatureFlags(): Promise<void> {\n if (this.#disabled || !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 * 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 {\n processedFlags,\n thresholdCacheUpdates,\n featureFlagThresholdGroupUpdates,\n } = await this.#processRemoteFeatureFlags(remoteFeatureFlags);\n\n const metaMetricsId = this.#getMetaMetricsId();\n const currentFlagNames = Object.keys(remoteFeatureFlags);\n\n // Build updated threshold cache\n const updatedThresholdCache = { ...(this.state.thresholdCache ?? {}) };\n\n // Apply new thresholds\n for (const [cacheKey, threshold] of Object.entries(thresholdCacheUpdates)) {\n updatedThresholdCache[cacheKey] = threshold;\n }\n\n // Clean up stale entries\n for (const cacheKey of Object.keys(updatedThresholdCache)) {\n const [cachedMetaMetricsId, ...cachedFlagNameParts] = cacheKey.split(':');\n const cachedFlagName = cachedFlagNameParts.join(':');\n if (\n cachedMetaMetricsId === metaMetricsId &&\n !currentFlagNames.includes(cachedFlagName)\n ) {\n delete updatedThresholdCache[cacheKey];\n }\n }\n\n // Strip metaMetricsIds from processed flags so they never appear in\n // remoteFeatureFlags state or #processedRemoteFeatureFlags. Arrays that\n // were preserved as-is (e.g. when metaMetricsId is missing) would\n // otherwise leak explicit-targeting IDs into diagnostics.\n const redactedProcessedFlags = redactMetaMetricsIds(processedFlags);\n\n // Single state update with all changes batched together\n this.#processedRemoteFeatureFlags = redactedProcessedFlags;\n\n this.update(() => {\n return {\n ...this.state,\n remoteFeatureFlags: {\n ...redactedProcessedFlags,\n ...this.state.localOverrides,\n },\n rawRemoteFeatureFlags: redactMetaMetricsIds(remoteFeatureFlags),\n cacheTimestamp: Date.now(),\n thresholdCache: updatedThresholdCache,\n featureFlagThresholdGroups: featureFlagThresholdGroupUpdates,\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 async #processRemoteFeatureFlags(remoteFeatureFlags: FeatureFlags): Promise<{\n processedFlags: FeatureFlags;\n thresholdCacheUpdates: Record<string, number>;\n featureFlagThresholdGroupUpdates: Record<string, string>;\n }> {\n const processedFlags: FeatureFlags = {};\n const metaMetricsId = this.#getMetaMetricsId();\n const thresholdCacheUpdates: Record<string, number> = {};\n const featureFlagThresholdGroupUpdates: 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 // Skip threshold processing if metaMetricsId is not available\n if (!metaMetricsId) {\n // Preserve array as-is when user hasn't opted into MetaMetrics\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 = findExplicitIdMatch(\n processedValue,\n normalizedMetaMetricsId,\n );\n\n if (explicitMatch) {\n processedValue = explicitMatch.value;\n if (explicitMatch.name) {\n featureFlagThresholdGroupUpdates[remoteFeatureFlagName] =\n explicitMatch.name;\n }\n } else {\n // Fall back to hash-based threshold selection with cache\n const cacheKey = `${metaMetricsId}:${remoteFeatureFlagName}` as const;\n let thresholdValue = this.state.thresholdCache?.[cacheKey];\n\n if (thresholdValue === undefined) {\n thresholdValue = await calculateThresholdForFlag(\n metaMetricsId,\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 featureFlagThresholdGroupUpdates[remoteFeatureFlagName] =\n selectedGroup.name;\n }\n }\n }\n }\n\n processedFlags[remoteFeatureFlagName] = processedValue;\n }\n\n return {\n processedFlags,\n thresholdCacheUpdates,\n featureFlagThresholdGroupUpdates,\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: {\n ...this.state.remoteFeatureFlags,\n [flagName]: value,\n },\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 const remoteFeatureFlags = { ...this.state.remoteFeatureFlags };\n const processedValue = this.#processedRemoteFeatureFlags[flagName];\n\n if (processedValue === undefined) {\n delete remoteFeatureFlags[flagName];\n } else {\n remoteFeatureFlags[flagName] = processedValue;\n }\n\n this.update(() => {\n return {\n ...this.state,\n localOverrides: newLocalOverrides,\n remoteFeatureFlags,\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.#processedRemoteFeatureFlags },\n };\n });\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-feature-flag-controller.d.cts","sourceRoot":"","sources":["../src/remote-feature-flag-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,wBAAwB,EACzB,kCAAkC;AACnC,OAAO,KAAK,EAAE,0BAA0B,EAAE,kCAAkC;AAC5E,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAErD,OAAO,KAAK,EAAE,IAAI,EAAiB,wBAAwB;AAE3D,OAAO,KAAK,EAAE,8BAA8B,EAAE,
|
|
1
|
+
{"version":3,"file":"remote-feature-flag-controller.d.cts","sourceRoot":"","sources":["../src/remote-feature-flag-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,wBAAwB,EACzB,kCAAkC;AACnC,OAAO,KAAK,EAAE,0BAA0B,EAAE,kCAAkC;AAC5E,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAErD,OAAO,KAAK,EAAE,IAAI,EAAiB,wBAAwB;AAE3D,OAAO,KAAK,EAAE,8BAA8B,EAAE,2EAA0E;AACxH,OAAO,KAAK,EAAE,wCAAwC,EAAE,iEAAgE;AACxH,OAAO,KAAK,EACV,YAAY,EAGb,mDAAkD;AASnD,eAAO,MAAM,cAAc,gCAAgC,CAAC;AAC5D,eAAO,MAAM,sBAAsB,QAAsB,CAAC;AAI1D,MAAM,MAAM,gCAAgC,GAAG;IAC7C,kBAAkB,EAAE,YAAY,CAAC;IACjC,cAAc,CAAC,EAAE,YAAY,CAAC;IAC9B,qBAAqB,CAAC,EAAE,YAAY,CAAC;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,0BAA0B,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrD,CAAC;AAoDF,MAAM,MAAM,yCAAyC,GACnD,wBAAwB,CACtB,OAAO,cAAc,EACrB,gCAAgC,CACjC,CAAC;AAEJ,MAAM,MAAM,kCAAkC,GAC1C,yCAAyC,GACzC,wCAAwC,CAAC;AAE7C,MAAM,MAAM,2CAA2C,GACrD,0BAA0B,CACxB,OAAO,cAAc,EACrB,gCAAgC,CACjC,CAAC;AAEJ,MAAM,MAAM,iCAAiC,GAC3C,2CAA2C,CAAC;AAE9C,MAAM,MAAM,oCAAoC,GAAG,SAAS,CAC1D,OAAO,cAAc,EACrB,kCAAkC,EAClC,iCAAiC,CAClC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,0CAA0C,IAAI,gCAAgC,CAO7F;AAoED;;;;;GAKG;AACH,qBAAa,2BAA4B,SAAQ,cAAc,CAC7D,OAAO,cAAc,EACrB,gCAAgC,EAChC,oCAAoC,CACrC;;IAeC;;;;;;;;;;;;OAYG;gBACS,EACV,SAAS,EACT,KAAK,EACL,sBAAsB,EACtB,aAAsC,EACtC,QAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,EAAE;QACD,SAAS,EAAE,oCAAoC,CAAC;QAChD,KAAK,CAAC,EAAE,OAAO,CAAC,gCAAgC,CAAC,CAAC;QAClD,sBAAsB,EAAE,8BAA8B,CAAC;QACvD,gBAAgB,EAAE,MAAM,MAAM,CAAC;QAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC5B;IAgED;;;;;OAKG;IACG,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAoM/C;;OAEG;IACH,MAAM,IAAI,IAAI;IAId;;OAEG;IACH,OAAO,IAAI,IAAI;IAIf;;;;;OAKG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,IAAI;IAkBpD;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAsB1C;;OAEG;IACH,qBAAqB,IAAI,IAAI;CAS9B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-feature-flag-controller.d.mts","sourceRoot":"","sources":["../src/remote-feature-flag-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,wBAAwB,EACzB,kCAAkC;AACnC,OAAO,KAAK,EAAE,0BAA0B,EAAE,kCAAkC;AAC5E,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAErD,OAAO,KAAK,EAAE,IAAI,EAAiB,wBAAwB;AAE3D,OAAO,KAAK,EAAE,8BAA8B,EAAE,
|
|
1
|
+
{"version":3,"file":"remote-feature-flag-controller.d.mts","sourceRoot":"","sources":["../src/remote-feature-flag-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,wBAAwB,EACzB,kCAAkC;AACnC,OAAO,KAAK,EAAE,0BAA0B,EAAE,kCAAkC;AAC5E,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAErD,OAAO,KAAK,EAAE,IAAI,EAAiB,wBAAwB;AAE3D,OAAO,KAAK,EAAE,8BAA8B,EAAE,2EAA0E;AACxH,OAAO,KAAK,EAAE,wCAAwC,EAAE,iEAAgE;AACxH,OAAO,KAAK,EACV,YAAY,EAGb,mDAAkD;AASnD,eAAO,MAAM,cAAc,gCAAgC,CAAC;AAC5D,eAAO,MAAM,sBAAsB,QAAsB,CAAC;AAI1D,MAAM,MAAM,gCAAgC,GAAG;IAC7C,kBAAkB,EAAE,YAAY,CAAC;IACjC,cAAc,CAAC,EAAE,YAAY,CAAC;IAC9B,qBAAqB,CAAC,EAAE,YAAY,CAAC;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,0BAA0B,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrD,CAAC;AAoDF,MAAM,MAAM,yCAAyC,GACnD,wBAAwB,CACtB,OAAO,cAAc,EACrB,gCAAgC,CACjC,CAAC;AAEJ,MAAM,MAAM,kCAAkC,GAC1C,yCAAyC,GACzC,wCAAwC,CAAC;AAE7C,MAAM,MAAM,2CAA2C,GACrD,0BAA0B,CACxB,OAAO,cAAc,EACrB,gCAAgC,CACjC,CAAC;AAEJ,MAAM,MAAM,iCAAiC,GAC3C,2CAA2C,CAAC;AAE9C,MAAM,MAAM,oCAAoC,GAAG,SAAS,CAC1D,OAAO,cAAc,EACrB,kCAAkC,EAClC,iCAAiC,CAClC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,0CAA0C,IAAI,gCAAgC,CAO7F;AAoED;;;;;GAKG;AACH,qBAAa,2BAA4B,SAAQ,cAAc,CAC7D,OAAO,cAAc,EACrB,gCAAgC,EAChC,oCAAoC,CACrC;;IAeC;;;;;;;;;;;;OAYG;gBACS,EACV,SAAS,EACT,KAAK,EACL,sBAAsB,EACtB,aAAsC,EACtC,QAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,EAAE;QACD,SAAS,EAAE,oCAAoC,CAAC;QAChD,KAAK,CAAC,EAAE,OAAO,CAAC,gCAAgC,CAAC,CAAC;QAClD,sBAAsB,EAAE,8BAA8B,CAAC;QACvD,gBAAgB,EAAE,MAAM,MAAM,CAAC;QAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC5B;IAgED;;;;;OAKG;IACG,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAoM/C;;OAEG;IACH,MAAM,IAAI,IAAI;IAId;;OAEG;IACH,OAAO,IAAI,IAAI;IAIf;;;;;OAKG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,IAAI;IAkBpD;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAsB1C;;OAEG;IACH,qBAAqB,IAAI,IAAI;CAS9B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-feature-flag-controller.mjs","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 { 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';\nimport type { RemoteFeatureFlagControllerMethodActions } from './remote-feature-flag-controller-method-action-types';\nimport type {\n FeatureFlags,\n ServiceResponse,\n FeatureFlagScopeValue,\n} from './remote-feature-flag-controller-types';\nimport {\n calculateThresholdForFlag,\n isFeatureFlagWithScopeValue,\n} from './utils/user-segmentation-utils';\nimport { isVersionFeatureFlag, getVersionData } from './utils/version';\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 * Returns a copy of `flags` with `metaMetricsIds` removed from every\n * threshold entry. Used before persisting raw flags to state so that\n * MetaMetrics IDs are never written to state logs or debug snapshots.\n *\n * @param flags - The raw feature flags object from the API.\n * @returns A new object with the same structure but without any\n * `metaMetricsIds` fields inside threshold entry arrays.\n */\nfunction redactMetaMetricsIds(flags: FeatureFlags): FeatureFlags {\n const result: FeatureFlags = {};\n for (const [name, value] of Object.entries(flags)) {\n if (!Array.isArray(value)) {\n result[name] = value;\n continue;\n }\n result[name] = value.map((entry) => {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n return entry;\n }\n const entryRecord = entry as Record<string, Json>;\n if (entryRecord.metaMetricsIds === undefined) {\n return entry;\n }\n const copy: Record<string, Json> = { ...entryRecord };\n delete copy.metaMetricsIds;\n return copy as Json;\n });\n }\n return result;\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 #clientVersion: SemVerVersion;\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.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 */\n constructor({\n messenger,\n state,\n clientConfigApiService,\n fetchInterval = DEFAULT_CACHE_DURATION,\n disabled = false,\n getMetaMetricsId,\n clientVersion,\n prevClientVersion,\n }: {\n messenger: RemoteFeatureFlagControllerMessenger;\n state?: Partial<RemoteFeatureFlagControllerState>;\n clientConfigApiService: AbstractClientConfigApiService;\n getMetaMetricsId: () => string;\n fetchInterval?: number;\n disabled?: boolean;\n clientVersion: string;\n prevClientVersion?: string;\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 const localOverrides = initialState.localOverrides ?? {};\n\n super({\n name: controllerName,\n metadata: remoteFeatureFlagControllerMetadata,\n messenger,\n state: {\n ...initialState,\n remoteFeatureFlags: {\n ...initialState.remoteFeatureFlags,\n ...localOverrides,\n },\n cacheTimestamp: hasClientVersionChanged\n ? 0\n : initialState.cacheTimestamp,\n },\n });\n\n this.#processedRemoteFeatureFlags = {\n ...initialState.remoteFeatureFlags,\n };\n for (const [flagName, overrideValue] of Object.entries(localOverrides)) {\n if (this.#processedRemoteFeatureFlags[flagName] === overrideValue) {\n delete this.#processedRemoteFeatureFlags[flagName];\n }\n }\n\n this.#fetchInterval = fetchInterval;\n this.#disabled = disabled;\n this.#clientConfigApiService = clientConfigApiService;\n this.#getMetaMetricsId = getMetaMetricsId;\n this.#clientVersion = clientVersion;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\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 * @returns A promise that resolves to the current set of feature flags.\n */\n async updateRemoteFeatureFlags(): Promise<void> {\n if (this.#disabled || !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 * 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 {\n processedFlags,\n thresholdCacheUpdates,\n featureFlagThresholdGroupUpdates,\n } = await this.#processRemoteFeatureFlags(remoteFeatureFlags);\n\n const metaMetricsId = this.#getMetaMetricsId();\n const currentFlagNames = Object.keys(remoteFeatureFlags);\n\n // Build updated threshold cache\n const updatedThresholdCache = { ...(this.state.thresholdCache ?? {}) };\n\n // Apply new thresholds\n for (const [cacheKey, threshold] of Object.entries(thresholdCacheUpdates)) {\n updatedThresholdCache[cacheKey] = threshold;\n }\n\n // Clean up stale entries\n for (const cacheKey of Object.keys(updatedThresholdCache)) {\n const [cachedMetaMetricsId, ...cachedFlagNameParts] = cacheKey.split(':');\n const cachedFlagName = cachedFlagNameParts.join(':');\n if (\n cachedMetaMetricsId === metaMetricsId &&\n !currentFlagNames.includes(cachedFlagName)\n ) {\n delete updatedThresholdCache[cacheKey];\n }\n }\n\n // Strip metaMetricsIds from processed flags so they never appear in\n // remoteFeatureFlags state or #processedRemoteFeatureFlags. Arrays that\n // were preserved as-is (e.g. when metaMetricsId is missing) would\n // otherwise leak explicit-targeting IDs into diagnostics.\n const redactedProcessedFlags = redactMetaMetricsIds(processedFlags);\n\n // Single state update with all changes batched together\n this.#processedRemoteFeatureFlags = redactedProcessedFlags;\n\n this.update(() => {\n return {\n ...this.state,\n remoteFeatureFlags: {\n ...redactedProcessedFlags,\n ...this.state.localOverrides,\n },\n rawRemoteFeatureFlags: redactMetaMetricsIds(remoteFeatureFlags),\n cacheTimestamp: Date.now(),\n thresholdCache: updatedThresholdCache,\n featureFlagThresholdGroups: featureFlagThresholdGroupUpdates,\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 async #processRemoteFeatureFlags(remoteFeatureFlags: FeatureFlags): Promise<{\n processedFlags: FeatureFlags;\n thresholdCacheUpdates: Record<string, number>;\n featureFlagThresholdGroupUpdates: Record<string, string>;\n }> {\n const processedFlags: FeatureFlags = {};\n const metaMetricsId = this.#getMetaMetricsId();\n const thresholdCacheUpdates: Record<string, number> = {};\n const featureFlagThresholdGroupUpdates: 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 // Skip threshold processing if metaMetricsId is not available\n if (!metaMetricsId) {\n // Preserve array as-is when user hasn't opted into MetaMetrics\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 = findExplicitIdMatch(\n processedValue,\n normalizedMetaMetricsId,\n );\n\n if (explicitMatch) {\n processedValue = explicitMatch.value;\n if (explicitMatch.name) {\n featureFlagThresholdGroupUpdates[remoteFeatureFlagName] =\n explicitMatch.name;\n }\n } else {\n // Fall back to hash-based threshold selection with cache\n const cacheKey = `${metaMetricsId}:${remoteFeatureFlagName}` as const;\n let thresholdValue = this.state.thresholdCache?.[cacheKey];\n\n if (thresholdValue === undefined) {\n thresholdValue = await calculateThresholdForFlag(\n metaMetricsId,\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 featureFlagThresholdGroupUpdates[remoteFeatureFlagName] =\n selectedGroup.name;\n }\n }\n }\n }\n\n processedFlags[remoteFeatureFlagName] = processedValue;\n }\n\n return {\n processedFlags,\n thresholdCacheUpdates,\n featureFlagThresholdGroupUpdates,\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: {\n ...this.state.remoteFeatureFlags,\n [flagName]: value,\n },\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 const remoteFeatureFlags = { ...this.state.remoteFeatureFlags };\n const processedValue = this.#processedRemoteFeatureFlags[flagName];\n\n if (processedValue === undefined) {\n delete remoteFeatureFlags[flagName];\n } else {\n remoteFeatureFlags[flagName] = processedValue;\n }\n\n this.update(() => {\n return {\n ...this.state,\n localOverrides: newLocalOverrides,\n remoteFeatureFlags,\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.#processedRemoteFeatureFlags },\n };\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"remote-feature-flag-controller.mjs","sourceRoot":"","sources":["../src/remote-feature-flag-controller.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EACL,cAAc,EAEf,kCAAkC;AAGnC,OAAO,EAAE,oBAAoB,EAAE,wBAAwB;AAUvD,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC5B,4CAA2C;AAC5C,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,4BAA2B;AAE1E,kBAAkB;AAElB,MAAM,CAAC,MAAM,cAAc,GAAG,6BAA6B,CAAC;AAC5D,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,QAAQ;AAanE,MAAM,mCAAmC,GAAG;IAC1C,kBAAkB,EAAE;QAClB,kBAAkB,EAAE,IAAI;QACxB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,IAAI;QAC5B,QAAQ,EAAE,IAAI;KACf;IACD,cAAc,EAAE;QACd,kBAAkB,EAAE,IAAI;QACxB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,IAAI;QAC5B,QAAQ,EAAE,IAAI;KACf;IACD,qBAAqB,EAAE;QACrB,kBAAkB,EAAE,IAAI;QACxB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,IAAI;QAC5B,QAAQ,EAAE,KAAK;KAChB;IACD,cAAc,EAAE;QACd,kBAAkB,EAAE,IAAI;QACxB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,IAAI;QAC5B,QAAQ,EAAE,KAAK;KAChB;IACD,cAAc,EAAE;QACd,kBAAkB,EAAE,KAAK;QACzB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,KAAK;QAC7B,QAAQ,EAAE,KAAK;KAChB;IACD,0BAA0B,EAAE;QAC1B,kBAAkB,EAAE,IAAI;QACxB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,IAAI;QAC5B,QAAQ,EAAE,KAAK;KAChB;CACF,CAAC;AAEF,oBAAoB;AAEpB,MAAM,yBAAyB,GAAG;IAChC,uBAAuB;IACvB,SAAS;IACT,QAAQ;IACR,oBAAoB;IACpB,iBAAiB;IACjB,0BAA0B;CAClB,CAAC;AA2BX;;;;GAIG;AACH,MAAM,UAAU,0CAA0C;IACxD,OAAO;QACL,kBAAkB,EAAE,EAAE;QACtB,cAAc,EAAE,EAAE;QAClB,qBAAqB,EAAE,EAAE;QACzB,cAAc,EAAE,CAAC;KAClB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,mBAAmB,CAC1B,OAAe,EACf,YAAoB;IAEpB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,2BAA2B,CAAC,KAAK,CAAC,EAAE,CAAC;YACxC,SAAS;QACX,CAAC;QACD,MAAM,EAAE,cAAc,EAAE,GAAG,KAAK,CAAC;QACjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;YACnC,SAAS;QACX,CAAC;QACD,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAClC,CAAC,EAAE,EAAE,EAAE,CACL,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,YAAY,CACrE,CAAC;QACF,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,oBAAoB,CAAC,KAAmB;IAC/C,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAClD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;YACrB,SAAS;QACX,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACjC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBACxE,OAAO,KAAK,CAAC;YACf,CAAC;YACD,MAAM,WAAW,GAAG,KAA6B,CAAC;YAClD,IAAI,WAAW,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;gBAC7C,OAAO,KAAK,CAAC;YACf,CAAC;YACD,MAAM,IAAI,GAAyB,EAAE,GAAG,WAAW,EAAE,CAAC;YACtD,OAAO,IAAI,CAAC,cAAc,CAAC;YAC3B,OAAO,IAAY,CAAC;QACtB,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,2BAA4B,SAAQ,cAIhD;IAeC;;;;;;;;;;;;OAYG;IACH,YAAY,EACV,SAAS,EACT,KAAK,EACL,sBAAsB,EACtB,aAAa,GAAG,sBAAsB,EACtC,QAAQ,GAAG,KAAK,EAChB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAUlB;QACC,IAAI,CAAC,oBAAoB,CAAC,aAAa,CAAC,EAAE,CAAC;YACzC,MAAM,IAAI,KAAK,CACb,2BAA2B,aAAa,iDAAiD,CAC1F,CAAC;QACJ,CAAC;QAED,MAAM,YAAY,GAAqC;YACrD,GAAG,0CAA0C,EAAE;YAC/C,GAAG,KAAK;SACT,CAAC;QAEF,MAAM,uBAAuB,GAC3B,oBAAoB,CAAC,iBAAiB,CAAC;YACvC,iBAAiB,KAAK,aAAa,CAAC;QAEtC,MAAM,cAAc,GAAG,YAAY,CAAC,cAAc,IAAI,EAAE,CAAC;QAEzD,KAAK,CAAC;YACJ,IAAI,EAAE,cAAc;YACpB,QAAQ,EAAE,mCAAmC;YAC7C,SAAS;YACT,KAAK,EAAE;gBACL,GAAG,YAAY;gBACf,kBAAkB,EAAE;oBAClB,GAAG,YAAY,CAAC,kBAAkB;oBAClC,GAAG,cAAc;iBAClB;gBACD,cAAc,EAAE,uBAAuB;oBACrC,CAAC,CAAC,CAAC;oBACH,CAAC,CAAC,YAAY,CAAC,cAAc;aAChC;SACF,CAAC,CAAC;;QA7EI,6DAAuB;QAEhC,wDAAmB;QAEV,sEAAwD;QAEjE,oEAAiD;QAExC,gEAAgC;QAEhC,6DAA8B;QAEvC,mEAA6C,EAAE,EAAC;QAmE9C,uBAAA,IAAI,4DAAgC;YAClC,GAAG,YAAY,CAAC,kBAAkB;SACnC,MAAA,CAAC;QACF,KAAK,MAAM,CAAC,QAAQ,EAAE,aAAa,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;YACvE,IAAI,uBAAA,IAAI,gEAA6B,CAAC,QAAQ,CAAC,KAAK,aAAa,EAAE,CAAC;gBAClE,OAAO,uBAAA,IAAI,gEAA6B,CAAC,QAAQ,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;QAED,uBAAA,IAAI,8CAAkB,aAAa,MAAA,CAAC;QACpC,uBAAA,IAAI,yCAAa,QAAQ,MAAA,CAAC;QAC1B,uBAAA,IAAI,uDAA2B,sBAAsB,MAAA,CAAC;QACtD,uBAAA,IAAI,iDAAqB,gBAAgB,MAAA,CAAC;QAC1C,uBAAA,IAAI,8CAAkB,aAAa,MAAA,CAAC;QAEpC,IAAI,CAAC,SAAS,CAAC,4BAA4B,CACzC,IAAI,EACJ,yBAAyB,CAC1B,CAAC;IACJ,CAAC;IAWD;;;;;OAKG;IACH,KAAK,CAAC,wBAAwB;QAC5B,IAAI,uBAAA,IAAI,6CAAU,IAAI,CAAC,uBAAA,IAAI,2FAAgB,MAApB,IAAI,CAAkB,EAAE,CAAC;YAC9C,OAAO;QACT,CAAC;QAED,IAAI,UAAU,CAAC;QAEf,IAAI,uBAAA,IAAI,yDAAsB,EAAE,CAAC;YAC/B,MAAM,uBAAA,IAAI,yDAAsB,CAAC;YACjC,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,uBAAA,IAAI,qDACF,uBAAA,IAAI,2DAAwB,CAAC,uBAAuB,EAAE,MAAA,CAAC;YAEzD,UAAU,GAAG,MAAM,uBAAA,IAAI,yDAAsB,CAAC;QAChD,CAAC;gBAAS,CAAC;YACT,uBAAA,IAAI,qDAAyB,SAAS,MAAA,CAAC;QACzC,CAAC;QAED,MAAM,uBAAA,IAAI,wFAAa,MAAjB,IAAI,EAAc,UAAU,CAAC,kBAAkB,CAAC,CAAC;IACzD,CAAC;IA8KD;;OAEG;IACH,MAAM;QACJ,uBAAA,IAAI,yCAAa,KAAK,MAAA,CAAC;IACzB,CAAC;IAED;;OAEG;IACH,OAAO;QACL,uBAAA,IAAI,yCAAa,IAAI,MAAA,CAAC;IACxB,CAAC;IAED;;;;;OAKG;IACH,eAAe,CAAC,QAAgB,EAAE,KAAW;QAC3C,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACf,MAAM,cAAc,GAAG;gBACrB,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc;gBAC5B,CAAC,QAAQ,CAAC,EAAE,KAAK;aAClB,CAAC;YAEF,OAAO;gBACL,GAAG,IAAI,CAAC,KAAK;gBACb,cAAc;gBACd,kBAAkB,EAAE;oBAClB,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB;oBAChC,CAAC,QAAQ,CAAC,EAAE,KAAK;iBAClB;aACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,kBAAkB,CAAC,QAAgB;QACjC,MAAM,iBAAiB,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;QAC3D,OAAO,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAEnC,MAAM,kBAAkB,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,EAAE,CAAC;QAChE,MAAM,cAAc,GAAG,uBAAA,IAAI,gEAA6B,CAAC,QAAQ,CAAC,CAAC;QAEnE,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;YACjC,OAAO,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QACtC,CAAC;aAAM,CAAC;YACN,kBAAkB,CAAC,QAAQ,CAAC,GAAG,cAAc,CAAC;QAChD,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACf,OAAO;gBACL,GAAG,IAAI,CAAC,KAAK;gBACb,cAAc,EAAE,iBAAiB;gBACjC,kBAAkB;aACnB,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,qBAAqB;QACnB,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACf,OAAO;gBACL,GAAG,IAAI,CAAC,KAAK;gBACb,cAAc,EAAE,EAAE;gBAClB,kBAAkB,EAAE,EAAE,GAAG,uBAAA,IAAI,gEAA6B,EAAE;aAC7D,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;CACF;;IA1RG,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,uBAAA,IAAI,kDAAe,CAAC;AACtE,CAAC;AAgCD;;;;GAIG;AACH,KAAK,mDAAc,kBAAgC;IACjD,MAAM,EACJ,cAAc,EACd,qBAAqB,EACrB,gCAAgC,GACjC,GAAG,MAAM,uBAAA,IAAI,sGAA2B,MAA/B,IAAI,EAA4B,kBAAkB,CAAC,CAAC;IAE9D,MAAM,aAAa,GAAG,uBAAA,IAAI,qDAAkB,MAAtB,IAAI,CAAoB,CAAC;IAC/C,MAAM,gBAAgB,GAAG,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAEzD,gCAAgC;IAChC,MAAM,qBAAqB,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,IAAI,EAAE,CAAC,EAAE,CAAC;IAEvE,uBAAuB;IACvB,KAAK,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,qBAAqB,CAAC,EAAE,CAAC;QAC1E,qBAAqB,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC;IAC9C,CAAC;IAED,yBAAyB;IACzB,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,EAAE,CAAC;QAC1D,MAAM,CAAC,mBAAmB,EAAE,GAAG,mBAAmB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1E,MAAM,cAAc,GAAG,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrD,IACE,mBAAmB,KAAK,aAAa;YACrC,CAAC,gBAAgB,CAAC,QAAQ,CAAC,cAAc,CAAC,EAC1C,CAAC;YACD,OAAO,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,oEAAoE;IACpE,yEAAyE;IACzE,kEAAkE;IAClE,0DAA0D;IAC1D,MAAM,sBAAsB,GAAG,oBAAoB,CAAC,cAAc,CAAC,CAAC;IAEpE,wDAAwD;IACxD,uBAAA,IAAI,4DAAgC,sBAAsB,MAAA,CAAC;IAE3D,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;QACf,OAAO;YACL,GAAG,IAAI,CAAC,KAAK;YACb,kBAAkB,EAAE;gBAClB,GAAG,sBAAsB;gBACzB,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc;aAC7B;YACD,qBAAqB,EAAE,oBAAoB,CAAC,kBAAkB,CAAC;YAC/D,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE;YAC1B,cAAc,EAAE,qBAAqB;YACrC,0BAA0B,EAAE,gCAAgC;SAC7D,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,uHAQwB,SAAe;IACtC,IAAI,CAAC,oBAAoB,CAAC,SAAS,CAAC,EAAE,CAAC;QACrC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,cAAc,CAAC,SAAS,EAAE,uBAAA,IAAI,kDAAe,CAAC,CAAC;AACxD,CAAC,2DAED,KAAK,iEAA4B,kBAAgC;IAK/D,MAAM,cAAc,GAAiB,EAAE,CAAC;IACxC,MAAM,aAAa,GAAG,uBAAA,IAAI,qDAAkB,MAAtB,IAAI,CAAoB,CAAC;IAC/C,MAAM,qBAAqB,GAA2B,EAAE,CAAC;IACzD,MAAM,gCAAgC,GAA2B,EAAE,CAAC;IAEpE,KAAK,MAAM,CACT,qBAAqB,EACrB,sBAAsB,EACvB,IAAI,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACxC,IAAI,cAAc,GAAG,uBAAA,IAAI,oGAAyB,MAA7B,IAAI,EACvB,sBAAsB,CACvB,CAAC;QACF,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;YAC5B,SAAS;QACX,CAAC;QAED,IAAI,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;YAClC,mFAAmF;YACnF,MAAM,kBAAkB,GAAG,cAAc,CAAC,IAAI,CAC5C,2BAA2B,CAC5B,CAAC;YAEF,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBACxB,yCAAyC;gBACzC,cAAc,CAAC,qBAAqB,CAAC,GAAG,cAAc,CAAC;gBACvD,SAAS;YACX,CAAC;YAED,8DAA8D;YAC9D,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,+DAA+D;gBAC/D,cAAc,CAAC,qBAAqB,CAAC,GAAG,cAAc,CAAC;gBACvD,SAAS;YACX,CAAC;YAED,0EAA0E;YAC1E,MAAM,uBAAuB,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YACnE,MAAM,aAAa,GAAG,mBAAmB,CACvC,cAAc,EACd,uBAAuB,CACxB,CAAC;YAEF,IAAI,aAAa,EAAE,CAAC;gBAClB,cAAc,GAAG,aAAa,CAAC,KAAK,CAAC;gBACrC,IAAI,aAAa,CAAC,IAAI,EAAE,CAAC;oBACvB,gCAAgC,CAAC,qBAAqB,CAAC;wBACrD,aAAa,CAAC,IAAI,CAAC;gBACvB,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,yDAAyD;gBACzD,MAAM,QAAQ,GAAG,GAAG,aAAa,IAAI,qBAAqB,EAAW,CAAC;gBACtE,IAAI,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC,QAAQ,CAAC,CAAC;gBAE3D,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;oBACjC,cAAc,GAAG,MAAM,yBAAyB,CAC9C,aAAa,EACb,qBAAqB,CACtB,CAAC;oBAEF,iDAAiD;oBACjD,qBAAqB,CAAC,QAAQ,CAAC,GAAG,cAAc,CAAC;gBACnD,CAAC;gBAED,MAAM,SAAS,GAAG,cAAc,CAAC;gBACjC,MAAM,aAAa,GAAG,cAAc,CAAC,IAAI,CACvC,CAAC,WAAW,EAAwC,EAAE;oBACpD,IAAI,CAAC,2BAA2B,CAAC,WAAW,CAAC,EAAE,CAAC;wBAC9C,OAAO,KAAK,CAAC;oBACf,CAAC;oBAED,OAAO,SAAS,IAAI,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC;gBAC9C,CAAC,CACF,CAAC;gBAEF,IAAI,aAAa,EAAE,CAAC;oBAClB,cAAc,GAAG,aAAa,CAAC,KAAK,CAAC;oBACrC,IAAI,aAAa,CAAC,IAAI,EAAE,CAAC;wBACvB,gCAAgC,CAAC,qBAAqB,CAAC;4BACrD,aAAa,CAAC,IAAI,CAAC;oBACvB,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,cAAc,CAAC,qBAAqB,CAAC,GAAG,cAAc,CAAC;IACzD,CAAC;IAED,OAAO;QACL,cAAc;QACd,qBAAqB;QACrB,gCAAgC;KACjC,CAAC;AACJ,CAAC","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 { 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 * Returns a copy of `flags` with `metaMetricsIds` removed from every\n * threshold entry. Used before persisting raw flags to state so that\n * MetaMetrics IDs are never written to state logs or debug snapshots.\n *\n * @param flags - The raw feature flags object from the API.\n * @returns A new object with the same structure but without any\n * `metaMetricsIds` fields inside threshold entry arrays.\n */\nfunction redactMetaMetricsIds(flags: FeatureFlags): FeatureFlags {\n const result: FeatureFlags = {};\n for (const [name, value] of Object.entries(flags)) {\n if (!Array.isArray(value)) {\n result[name] = value;\n continue;\n }\n result[name] = value.map((entry) => {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n return entry;\n }\n const entryRecord = entry as Record<string, Json>;\n if (entryRecord.metaMetricsIds === undefined) {\n return entry;\n }\n const copy: Record<string, Json> = { ...entryRecord };\n delete copy.metaMetricsIds;\n return copy as Json;\n });\n }\n return result;\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 #clientVersion: SemVerVersion;\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.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 */\n constructor({\n messenger,\n state,\n clientConfigApiService,\n fetchInterval = DEFAULT_CACHE_DURATION,\n disabled = false,\n getMetaMetricsId,\n clientVersion,\n prevClientVersion,\n }: {\n messenger: RemoteFeatureFlagControllerMessenger;\n state?: Partial<RemoteFeatureFlagControllerState>;\n clientConfigApiService: AbstractClientConfigApiService;\n getMetaMetricsId: () => string;\n fetchInterval?: number;\n disabled?: boolean;\n clientVersion: string;\n prevClientVersion?: string;\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 const localOverrides = initialState.localOverrides ?? {};\n\n super({\n name: controllerName,\n metadata: remoteFeatureFlagControllerMetadata,\n messenger,\n state: {\n ...initialState,\n remoteFeatureFlags: {\n ...initialState.remoteFeatureFlags,\n ...localOverrides,\n },\n cacheTimestamp: hasClientVersionChanged\n ? 0\n : initialState.cacheTimestamp,\n },\n });\n\n this.#processedRemoteFeatureFlags = {\n ...initialState.remoteFeatureFlags,\n };\n for (const [flagName, overrideValue] of Object.entries(localOverrides)) {\n if (this.#processedRemoteFeatureFlags[flagName] === overrideValue) {\n delete this.#processedRemoteFeatureFlags[flagName];\n }\n }\n\n this.#fetchInterval = fetchInterval;\n this.#disabled = disabled;\n this.#clientConfigApiService = clientConfigApiService;\n this.#getMetaMetricsId = getMetaMetricsId;\n this.#clientVersion = clientVersion;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\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 * @returns A promise that resolves to the current set of feature flags.\n */\n async updateRemoteFeatureFlags(): Promise<void> {\n if (this.#disabled || !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 * 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 {\n processedFlags,\n thresholdCacheUpdates,\n featureFlagThresholdGroupUpdates,\n } = await this.#processRemoteFeatureFlags(remoteFeatureFlags);\n\n const metaMetricsId = this.#getMetaMetricsId();\n const currentFlagNames = Object.keys(remoteFeatureFlags);\n\n // Build updated threshold cache\n const updatedThresholdCache = { ...(this.state.thresholdCache ?? {}) };\n\n // Apply new thresholds\n for (const [cacheKey, threshold] of Object.entries(thresholdCacheUpdates)) {\n updatedThresholdCache[cacheKey] = threshold;\n }\n\n // Clean up stale entries\n for (const cacheKey of Object.keys(updatedThresholdCache)) {\n const [cachedMetaMetricsId, ...cachedFlagNameParts] = cacheKey.split(':');\n const cachedFlagName = cachedFlagNameParts.join(':');\n if (\n cachedMetaMetricsId === metaMetricsId &&\n !currentFlagNames.includes(cachedFlagName)\n ) {\n delete updatedThresholdCache[cacheKey];\n }\n }\n\n // Strip metaMetricsIds from processed flags so they never appear in\n // remoteFeatureFlags state or #processedRemoteFeatureFlags. Arrays that\n // were preserved as-is (e.g. when metaMetricsId is missing) would\n // otherwise leak explicit-targeting IDs into diagnostics.\n const redactedProcessedFlags = redactMetaMetricsIds(processedFlags);\n\n // Single state update with all changes batched together\n this.#processedRemoteFeatureFlags = redactedProcessedFlags;\n\n this.update(() => {\n return {\n ...this.state,\n remoteFeatureFlags: {\n ...redactedProcessedFlags,\n ...this.state.localOverrides,\n },\n rawRemoteFeatureFlags: redactMetaMetricsIds(remoteFeatureFlags),\n cacheTimestamp: Date.now(),\n thresholdCache: updatedThresholdCache,\n featureFlagThresholdGroups: featureFlagThresholdGroupUpdates,\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 async #processRemoteFeatureFlags(remoteFeatureFlags: FeatureFlags): Promise<{\n processedFlags: FeatureFlags;\n thresholdCacheUpdates: Record<string, number>;\n featureFlagThresholdGroupUpdates: Record<string, string>;\n }> {\n const processedFlags: FeatureFlags = {};\n const metaMetricsId = this.#getMetaMetricsId();\n const thresholdCacheUpdates: Record<string, number> = {};\n const featureFlagThresholdGroupUpdates: 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 // Skip threshold processing if metaMetricsId is not available\n if (!metaMetricsId) {\n // Preserve array as-is when user hasn't opted into MetaMetrics\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 = findExplicitIdMatch(\n processedValue,\n normalizedMetaMetricsId,\n );\n\n if (explicitMatch) {\n processedValue = explicitMatch.value;\n if (explicitMatch.name) {\n featureFlagThresholdGroupUpdates[remoteFeatureFlagName] =\n explicitMatch.name;\n }\n } else {\n // Fall back to hash-based threshold selection with cache\n const cacheKey = `${metaMetricsId}:${remoteFeatureFlagName}` as const;\n let thresholdValue = this.state.thresholdCache?.[cacheKey];\n\n if (thresholdValue === undefined) {\n thresholdValue = await calculateThresholdForFlag(\n metaMetricsId,\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 featureFlagThresholdGroupUpdates[remoteFeatureFlagName] =\n selectedGroup.name;\n }\n }\n }\n }\n\n processedFlags[remoteFeatureFlagName] = processedValue;\n }\n\n return {\n processedFlags,\n thresholdCacheUpdates,\n featureFlagThresholdGroupUpdates,\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: {\n ...this.state.remoteFeatureFlags,\n [flagName]: value,\n },\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 const remoteFeatureFlags = { ...this.state.remoteFeatureFlags };\n const processedValue = this.#processedRemoteFeatureFlags[flagName];\n\n if (processedValue === undefined) {\n delete remoteFeatureFlags[flagName];\n } else {\n remoteFeatureFlags[flagName] = processedValue;\n }\n\n this.update(() => {\n return {\n ...this.state,\n localOverrides: newLocalOverrides,\n remoteFeatureFlags,\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.#processedRemoteFeatureFlags },\n };\n });\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-segmentation-utils.cjs","sourceRoot":"","sources":["../../src/utils/user-segmentation-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Json } from '@metamask/utils';\nimport { sha256, bytesToHex } from '@metamask/utils';\nimport { validate as uuidValidate, version as uuidVersion } from 'uuid';\n\nimport type { FeatureFlagScopeValue } from '../remote-feature-flag-controller-types';\n\n/**\n * Converts a UUID string to a BigInt by removing dashes and converting to hexadecimal.\n *\n * @param uuid - The UUID string to convert\n * @returns The UUID as a BigInt value\n */\nfunction uuidStringToBigInt(uuid: string): bigint {\n return BigInt(`0x${uuid.replace(/-/gu, '')}`);\n}\n\nconst MIN_UUID_V4 = '00000000-0000-4000-8000-000000000000';\nconst MAX_UUID_V4 = 'ffffffff-ffff-4fff-bfff-ffffffffffff';\nconst MIN_UUID_V4_BIGINT = uuidStringToBigInt(MIN_UUID_V4);\nconst MAX_UUID_V4_BIGINT = uuidStringToBigInt(MAX_UUID_V4);\nconst UUID_V4_VALUE_RANGE_BIGINT = MAX_UUID_V4_BIGINT - MIN_UUID_V4_BIGINT;\n\n/**\n * Calculates a deterministic threshold value between 0 and 1 for A/B testing.\n * This function hashes the user's MetaMetrics ID combined with the feature flag name\n * to ensure consistent group assignment across sessions while varying across different flags.\n *\n * @param metaMetricsId - The user's MetaMetrics ID (must be non-empty)\n * @param featureFlagName - The feature flag name to create unique threshold per flag\n * @returns A promise that resolves to a number between 0 and 1\n * @throws Error if metaMetricsId is empty\n */\nexport async function calculateThresholdForFlag(\n metaMetricsId: string,\n featureFlagName: string,\n): Promise<number> {\n if (!metaMetricsId) {\n throw new Error('MetaMetrics ID cannot be empty');\n }\n\n if (!featureFlagName) {\n throw new Error('Feature flag name cannot be empty');\n }\n\n const seed = metaMetricsId + featureFlagName;\n\n // Hash the combined seed\n const encoder = new TextEncoder();\n const hashBuffer = await sha256(encoder.encode(seed));\n\n // Convert hash bytes directly to 0-1 range\n const hash = bytesToHex(hashBuffer);\n const hashBigInt = BigInt(hash);\n const maxValue = BigInt(`0x${'f'.repeat(64)}`);\n\n // Use BigInt division first, then convert to number to maintain precision\n return Number((hashBigInt * BigInt(1_000_000)) / maxValue) / 1_000_000;\n}\n\n/**\n * Generates a deterministic random number between 0 and 1 based on a metaMetricsId.\n * This is useful for A/B testing and feature flag rollouts where we want\n * consistent group assignment for the same user.\n *\n * @param metaMetricsId - The unique identifier used to generate the deterministic random number. Must be either:\n * - A UUIDv4 string (e.g., '123e4567-e89b-12d3-a456-426614174000'\n * - A hex string with '0x' prefix (e.g., '0x86bacb9b2bf9a7e8d2b147eadb95ac9aaa26842327cd24afc8bd4b3c1d136420')\n * @returns A number between 0 and 1, deterministically generated from the input ID.\n * The same input will always produce the same output.\n */\nexport function generateDeterministicRandomNumber(\n metaMetricsId: string,\n): number {\n if (!metaMetricsId) {\n throw new Error('MetaMetrics ID cannot be empty');\n }\n\n let idValue: bigint;\n let maxValue: bigint;\n\n // uuidv4 format\n if (uuidValidate(metaMetricsId)) {\n if (uuidVersion(metaMetricsId) !== 4) {\n throw new Error(\n `Invalid UUID version. Expected v4, got v${uuidVersion(metaMetricsId)}`,\n );\n }\n idValue = uuidStringToBigInt(metaMetricsId) - MIN_UUID_V4_BIGINT;\n maxValue = UUID_V4_VALUE_RANGE_BIGINT;\n } else {\n // hex format with 0x prefix\n if (!metaMetricsId.startsWith('0x')) {\n throw new Error('Hex ID must start with 0x prefix');\n }\n\n const cleanId = metaMetricsId.slice(2);\n const EXPECTED_HEX_LENGTH = 64; // 32 bytes = 64 hex characters\n\n if (cleanId.length !== EXPECTED_HEX_LENGTH) {\n throw new Error(\n `Invalid hex ID length. Expected ${EXPECTED_HEX_LENGTH} characters, got ${cleanId.length}`,\n );\n }\n\n if (!/^[0-9a-f]+$/iu.test(cleanId)) {\n throw new Error('Hex ID contains invalid characters');\n }\n\n idValue = BigInt(`0x${cleanId}`);\n maxValue = BigInt(`0x${'f'.repeat(cleanId.length)}`);\n }\n\n // Use BigInt division first, then convert to number to maintain precision\n return Number((idValue * BigInt(1_000_000)) / maxValue) / 1_000_000;\n}\n\n/**\n * Type guard to check if a value is a feature flag with scope.\n * Used to validate feature flag objects that contain scope-based configurations.\n *\n * @param featureFlag - The value to check if it's a feature flag with scope\n * @returns True if the value is a feature flag with scope, false otherwise\n */\nexport const isFeatureFlagWithScopeValue = (\n featureFlag: Json,\n): featureFlag is FeatureFlagScopeValue => {\n return (\n typeof featureFlag === 'object' &&\n featureFlag !== null &&\n 'scope' in featureFlag\n );\n};\n"]}
|
|
1
|
+
{"version":3,"file":"user-segmentation-utils.cjs","sourceRoot":"","sources":["../../src/utils/user-segmentation-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Json } from '@metamask/utils';\nimport { sha256, bytesToHex } from '@metamask/utils';\nimport { validate as uuidValidate, version as uuidVersion } from 'uuid';\n\nimport type { FeatureFlagScopeValue } from '../remote-feature-flag-controller-types.js';\n\n/**\n * Converts a UUID string to a BigInt by removing dashes and converting to hexadecimal.\n *\n * @param uuid - The UUID string to convert\n * @returns The UUID as a BigInt value\n */\nfunction uuidStringToBigInt(uuid: string): bigint {\n return BigInt(`0x${uuid.replace(/-/gu, '')}`);\n}\n\nconst MIN_UUID_V4 = '00000000-0000-4000-8000-000000000000';\nconst MAX_UUID_V4 = 'ffffffff-ffff-4fff-bfff-ffffffffffff';\nconst MIN_UUID_V4_BIGINT = uuidStringToBigInt(MIN_UUID_V4);\nconst MAX_UUID_V4_BIGINT = uuidStringToBigInt(MAX_UUID_V4);\nconst UUID_V4_VALUE_RANGE_BIGINT = MAX_UUID_V4_BIGINT - MIN_UUID_V4_BIGINT;\n\n/**\n * Calculates a deterministic threshold value between 0 and 1 for A/B testing.\n * This function hashes the user's MetaMetrics ID combined with the feature flag name\n * to ensure consistent group assignment across sessions while varying across different flags.\n *\n * @param metaMetricsId - The user's MetaMetrics ID (must be non-empty)\n * @param featureFlagName - The feature flag name to create unique threshold per flag\n * @returns A promise that resolves to a number between 0 and 1\n * @throws Error if metaMetricsId is empty\n */\nexport async function calculateThresholdForFlag(\n metaMetricsId: string,\n featureFlagName: string,\n): Promise<number> {\n if (!metaMetricsId) {\n throw new Error('MetaMetrics ID cannot be empty');\n }\n\n if (!featureFlagName) {\n throw new Error('Feature flag name cannot be empty');\n }\n\n const seed = metaMetricsId + featureFlagName;\n\n // Hash the combined seed\n const encoder = new TextEncoder();\n const hashBuffer = await sha256(encoder.encode(seed));\n\n // Convert hash bytes directly to 0-1 range\n const hash = bytesToHex(hashBuffer);\n const hashBigInt = BigInt(hash);\n const maxValue = BigInt(`0x${'f'.repeat(64)}`);\n\n // Use BigInt division first, then convert to number to maintain precision\n return Number((hashBigInt * BigInt(1_000_000)) / maxValue) / 1_000_000;\n}\n\n/**\n * Generates a deterministic random number between 0 and 1 based on a metaMetricsId.\n * This is useful for A/B testing and feature flag rollouts where we want\n * consistent group assignment for the same user.\n *\n * @param metaMetricsId - The unique identifier used to generate the deterministic random number. Must be either:\n * - A UUIDv4 string (e.g., '123e4567-e89b-12d3-a456-426614174000'\n * - A hex string with '0x' prefix (e.g., '0x86bacb9b2bf9a7e8d2b147eadb95ac9aaa26842327cd24afc8bd4b3c1d136420')\n * @returns A number between 0 and 1, deterministically generated from the input ID.\n * The same input will always produce the same output.\n */\nexport function generateDeterministicRandomNumber(\n metaMetricsId: string,\n): number {\n if (!metaMetricsId) {\n throw new Error('MetaMetrics ID cannot be empty');\n }\n\n let idValue: bigint;\n let maxValue: bigint;\n\n // uuidv4 format\n if (uuidValidate(metaMetricsId)) {\n if (uuidVersion(metaMetricsId) !== 4) {\n throw new Error(\n `Invalid UUID version. Expected v4, got v${uuidVersion(metaMetricsId)}`,\n );\n }\n idValue = uuidStringToBigInt(metaMetricsId) - MIN_UUID_V4_BIGINT;\n maxValue = UUID_V4_VALUE_RANGE_BIGINT;\n } else {\n // hex format with 0x prefix\n if (!metaMetricsId.startsWith('0x')) {\n throw new Error('Hex ID must start with 0x prefix');\n }\n\n const cleanId = metaMetricsId.slice(2);\n const EXPECTED_HEX_LENGTH = 64; // 32 bytes = 64 hex characters\n\n if (cleanId.length !== EXPECTED_HEX_LENGTH) {\n throw new Error(\n `Invalid hex ID length. Expected ${EXPECTED_HEX_LENGTH} characters, got ${cleanId.length}`,\n );\n }\n\n if (!/^[0-9a-f]+$/iu.test(cleanId)) {\n throw new Error('Hex ID contains invalid characters');\n }\n\n idValue = BigInt(`0x${cleanId}`);\n maxValue = BigInt(`0x${'f'.repeat(cleanId.length)}`);\n }\n\n // Use BigInt division first, then convert to number to maintain precision\n return Number((idValue * BigInt(1_000_000)) / maxValue) / 1_000_000;\n}\n\n/**\n * Type guard to check if a value is a feature flag with scope.\n * Used to validate feature flag objects that contain scope-based configurations.\n *\n * @param featureFlag - The value to check if it's a feature flag with scope\n * @returns True if the value is a feature flag with scope, false otherwise\n */\nexport const isFeatureFlagWithScopeValue = (\n featureFlag: Json,\n): featureFlag is FeatureFlagScopeValue => {\n return (\n typeof featureFlag === 'object' &&\n featureFlag !== null &&\n 'scope' in featureFlag\n );\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-segmentation-utils.d.cts","sourceRoot":"","sources":["../../src/utils/user-segmentation-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAI5C,OAAO,KAAK,EAAE,qBAAqB,EAAE,
|
|
1
|
+
{"version":3,"file":"user-segmentation-utils.d.cts","sourceRoot":"","sources":["../../src/utils/user-segmentation-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAI5C,OAAO,KAAK,EAAE,qBAAqB,EAAE,oDAAmD;AAkBxF;;;;;;;;;GASG;AACH,wBAAsB,yBAAyB,CAC7C,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,CAAC,CAsBjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iCAAiC,CAC/C,aAAa,EAAE,MAAM,GACpB,MAAM,CA0CR;AAED;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,gBACzB,IAAI,yCAOlB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-segmentation-utils.d.mts","sourceRoot":"","sources":["../../src/utils/user-segmentation-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAI5C,OAAO,KAAK,EAAE,qBAAqB,EAAE,
|
|
1
|
+
{"version":3,"file":"user-segmentation-utils.d.mts","sourceRoot":"","sources":["../../src/utils/user-segmentation-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAI5C,OAAO,KAAK,EAAE,qBAAqB,EAAE,oDAAmD;AAkBxF;;;;;;;;;GASG;AACH,wBAAsB,yBAAyB,CAC7C,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,CAAC,CAsBjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iCAAiC,CAC/C,aAAa,EAAE,MAAM,GACpB,MAAM,CA0CR;AAED;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,gBACzB,IAAI,yCAOlB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-segmentation-utils.mjs","sourceRoot":"","sources":["../../src/utils/user-segmentation-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Json } from '@metamask/utils';\nimport { sha256, bytesToHex } from '@metamask/utils';\nimport { validate as uuidValidate, version as uuidVersion } from 'uuid';\n\nimport type { FeatureFlagScopeValue } from '../remote-feature-flag-controller-types';\n\n/**\n * Converts a UUID string to a BigInt by removing dashes and converting to hexadecimal.\n *\n * @param uuid - The UUID string to convert\n * @returns The UUID as a BigInt value\n */\nfunction uuidStringToBigInt(uuid: string): bigint {\n return BigInt(`0x${uuid.replace(/-/gu, '')}`);\n}\n\nconst MIN_UUID_V4 = '00000000-0000-4000-8000-000000000000';\nconst MAX_UUID_V4 = 'ffffffff-ffff-4fff-bfff-ffffffffffff';\nconst MIN_UUID_V4_BIGINT = uuidStringToBigInt(MIN_UUID_V4);\nconst MAX_UUID_V4_BIGINT = uuidStringToBigInt(MAX_UUID_V4);\nconst UUID_V4_VALUE_RANGE_BIGINT = MAX_UUID_V4_BIGINT - MIN_UUID_V4_BIGINT;\n\n/**\n * Calculates a deterministic threshold value between 0 and 1 for A/B testing.\n * This function hashes the user's MetaMetrics ID combined with the feature flag name\n * to ensure consistent group assignment across sessions while varying across different flags.\n *\n * @param metaMetricsId - The user's MetaMetrics ID (must be non-empty)\n * @param featureFlagName - The feature flag name to create unique threshold per flag\n * @returns A promise that resolves to a number between 0 and 1\n * @throws Error if metaMetricsId is empty\n */\nexport async function calculateThresholdForFlag(\n metaMetricsId: string,\n featureFlagName: string,\n): Promise<number> {\n if (!metaMetricsId) {\n throw new Error('MetaMetrics ID cannot be empty');\n }\n\n if (!featureFlagName) {\n throw new Error('Feature flag name cannot be empty');\n }\n\n const seed = metaMetricsId + featureFlagName;\n\n // Hash the combined seed\n const encoder = new TextEncoder();\n const hashBuffer = await sha256(encoder.encode(seed));\n\n // Convert hash bytes directly to 0-1 range\n const hash = bytesToHex(hashBuffer);\n const hashBigInt = BigInt(hash);\n const maxValue = BigInt(`0x${'f'.repeat(64)}`);\n\n // Use BigInt division first, then convert to number to maintain precision\n return Number((hashBigInt * BigInt(1_000_000)) / maxValue) / 1_000_000;\n}\n\n/**\n * Generates a deterministic random number between 0 and 1 based on a metaMetricsId.\n * This is useful for A/B testing and feature flag rollouts where we want\n * consistent group assignment for the same user.\n *\n * @param metaMetricsId - The unique identifier used to generate the deterministic random number. Must be either:\n * - A UUIDv4 string (e.g., '123e4567-e89b-12d3-a456-426614174000'\n * - A hex string with '0x' prefix (e.g., '0x86bacb9b2bf9a7e8d2b147eadb95ac9aaa26842327cd24afc8bd4b3c1d136420')\n * @returns A number between 0 and 1, deterministically generated from the input ID.\n * The same input will always produce the same output.\n */\nexport function generateDeterministicRandomNumber(\n metaMetricsId: string,\n): number {\n if (!metaMetricsId) {\n throw new Error('MetaMetrics ID cannot be empty');\n }\n\n let idValue: bigint;\n let maxValue: bigint;\n\n // uuidv4 format\n if (uuidValidate(metaMetricsId)) {\n if (uuidVersion(metaMetricsId) !== 4) {\n throw new Error(\n `Invalid UUID version. Expected v4, got v${uuidVersion(metaMetricsId)}`,\n );\n }\n idValue = uuidStringToBigInt(metaMetricsId) - MIN_UUID_V4_BIGINT;\n maxValue = UUID_V4_VALUE_RANGE_BIGINT;\n } else {\n // hex format with 0x prefix\n if (!metaMetricsId.startsWith('0x')) {\n throw new Error('Hex ID must start with 0x prefix');\n }\n\n const cleanId = metaMetricsId.slice(2);\n const EXPECTED_HEX_LENGTH = 64; // 32 bytes = 64 hex characters\n\n if (cleanId.length !== EXPECTED_HEX_LENGTH) {\n throw new Error(\n `Invalid hex ID length. Expected ${EXPECTED_HEX_LENGTH} characters, got ${cleanId.length}`,\n );\n }\n\n if (!/^[0-9a-f]+$/iu.test(cleanId)) {\n throw new Error('Hex ID contains invalid characters');\n }\n\n idValue = BigInt(`0x${cleanId}`);\n maxValue = BigInt(`0x${'f'.repeat(cleanId.length)}`);\n }\n\n // Use BigInt division first, then convert to number to maintain precision\n return Number((idValue * BigInt(1_000_000)) / maxValue) / 1_000_000;\n}\n\n/**\n * Type guard to check if a value is a feature flag with scope.\n * Used to validate feature flag objects that contain scope-based configurations.\n *\n * @param featureFlag - The value to check if it's a feature flag with scope\n * @returns True if the value is a feature flag with scope, false otherwise\n */\nexport const isFeatureFlagWithScopeValue = (\n featureFlag: Json,\n): featureFlag is FeatureFlagScopeValue => {\n return (\n typeof featureFlag === 'object' &&\n featureFlag !== null &&\n 'scope' in featureFlag\n );\n};\n"]}
|
|
1
|
+
{"version":3,"file":"user-segmentation-utils.mjs","sourceRoot":"","sources":["../../src/utils/user-segmentation-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Json } from '@metamask/utils';\nimport { sha256, bytesToHex } from '@metamask/utils';\nimport { validate as uuidValidate, version as uuidVersion } from 'uuid';\n\nimport type { FeatureFlagScopeValue } from '../remote-feature-flag-controller-types.js';\n\n/**\n * Converts a UUID string to a BigInt by removing dashes and converting to hexadecimal.\n *\n * @param uuid - The UUID string to convert\n * @returns The UUID as a BigInt value\n */\nfunction uuidStringToBigInt(uuid: string): bigint {\n return BigInt(`0x${uuid.replace(/-/gu, '')}`);\n}\n\nconst MIN_UUID_V4 = '00000000-0000-4000-8000-000000000000';\nconst MAX_UUID_V4 = 'ffffffff-ffff-4fff-bfff-ffffffffffff';\nconst MIN_UUID_V4_BIGINT = uuidStringToBigInt(MIN_UUID_V4);\nconst MAX_UUID_V4_BIGINT = uuidStringToBigInt(MAX_UUID_V4);\nconst UUID_V4_VALUE_RANGE_BIGINT = MAX_UUID_V4_BIGINT - MIN_UUID_V4_BIGINT;\n\n/**\n * Calculates a deterministic threshold value between 0 and 1 for A/B testing.\n * This function hashes the user's MetaMetrics ID combined with the feature flag name\n * to ensure consistent group assignment across sessions while varying across different flags.\n *\n * @param metaMetricsId - The user's MetaMetrics ID (must be non-empty)\n * @param featureFlagName - The feature flag name to create unique threshold per flag\n * @returns A promise that resolves to a number between 0 and 1\n * @throws Error if metaMetricsId is empty\n */\nexport async function calculateThresholdForFlag(\n metaMetricsId: string,\n featureFlagName: string,\n): Promise<number> {\n if (!metaMetricsId) {\n throw new Error('MetaMetrics ID cannot be empty');\n }\n\n if (!featureFlagName) {\n throw new Error('Feature flag name cannot be empty');\n }\n\n const seed = metaMetricsId + featureFlagName;\n\n // Hash the combined seed\n const encoder = new TextEncoder();\n const hashBuffer = await sha256(encoder.encode(seed));\n\n // Convert hash bytes directly to 0-1 range\n const hash = bytesToHex(hashBuffer);\n const hashBigInt = BigInt(hash);\n const maxValue = BigInt(`0x${'f'.repeat(64)}`);\n\n // Use BigInt division first, then convert to number to maintain precision\n return Number((hashBigInt * BigInt(1_000_000)) / maxValue) / 1_000_000;\n}\n\n/**\n * Generates a deterministic random number between 0 and 1 based on a metaMetricsId.\n * This is useful for A/B testing and feature flag rollouts where we want\n * consistent group assignment for the same user.\n *\n * @param metaMetricsId - The unique identifier used to generate the deterministic random number. Must be either:\n * - A UUIDv4 string (e.g., '123e4567-e89b-12d3-a456-426614174000'\n * - A hex string with '0x' prefix (e.g., '0x86bacb9b2bf9a7e8d2b147eadb95ac9aaa26842327cd24afc8bd4b3c1d136420')\n * @returns A number between 0 and 1, deterministically generated from the input ID.\n * The same input will always produce the same output.\n */\nexport function generateDeterministicRandomNumber(\n metaMetricsId: string,\n): number {\n if (!metaMetricsId) {\n throw new Error('MetaMetrics ID cannot be empty');\n }\n\n let idValue: bigint;\n let maxValue: bigint;\n\n // uuidv4 format\n if (uuidValidate(metaMetricsId)) {\n if (uuidVersion(metaMetricsId) !== 4) {\n throw new Error(\n `Invalid UUID version. Expected v4, got v${uuidVersion(metaMetricsId)}`,\n );\n }\n idValue = uuidStringToBigInt(metaMetricsId) - MIN_UUID_V4_BIGINT;\n maxValue = UUID_V4_VALUE_RANGE_BIGINT;\n } else {\n // hex format with 0x prefix\n if (!metaMetricsId.startsWith('0x')) {\n throw new Error('Hex ID must start with 0x prefix');\n }\n\n const cleanId = metaMetricsId.slice(2);\n const EXPECTED_HEX_LENGTH = 64; // 32 bytes = 64 hex characters\n\n if (cleanId.length !== EXPECTED_HEX_LENGTH) {\n throw new Error(\n `Invalid hex ID length. Expected ${EXPECTED_HEX_LENGTH} characters, got ${cleanId.length}`,\n );\n }\n\n if (!/^[0-9a-f]+$/iu.test(cleanId)) {\n throw new Error('Hex ID contains invalid characters');\n }\n\n idValue = BigInt(`0x${cleanId}`);\n maxValue = BigInt(`0x${'f'.repeat(cleanId.length)}`);\n }\n\n // Use BigInt division first, then convert to number to maintain precision\n return Number((idValue * BigInt(1_000_000)) / maxValue) / 1_000_000;\n}\n\n/**\n * Type guard to check if a value is a feature flag with scope.\n * Used to validate feature flag objects that contain scope-based configurations.\n *\n * @param featureFlag - The value to check if it's a feature flag with scope\n * @returns True if the value is a feature flag with scope, false otherwise\n */\nexport const isFeatureFlagWithScopeValue = (\n featureFlag: Json,\n): featureFlag is FeatureFlagScopeValue => {\n return (\n typeof featureFlag === 'object' &&\n featureFlag !== null &&\n 'scope' in featureFlag\n );\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.cjs","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":";;;AAAA,2CAAkE;AAKlE;;;GAGG;AACU,QAAA,uBAAuB,GAAG;IACrC,QAAQ,EAAE,UAAU;CACZ,CAAC;AAEX;;;;;GAKG;AACH,SAAgB,oBAAoB,CAClC,KAAW;IAEX,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACpB,CAAC,CAAC,+BAAuB,CAAC,QAAQ,IAAI,KAAK,CAAC,EAC5C,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,+BAAuB,CAAC,QAAQ,CAAC,CAAC;IAEzD,IACE,OAAO,QAAQ,KAAK,QAAQ;QAC5B,QAAQ,KAAK,IAAI;QACjB,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EACvB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,2DAA2D;IAC3D,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1C,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,IAAA,4BAAoB,EAAC,UAAU,CAAC,CAAC,CAAC;AAC7E,CAAC;AAzBD,oDAyBC;AAED;;;;;;;GAOG;AACH,SAAgB,cAAc,CAC5B,gBAA8C,EAC9C,iBAAgC;IAEhC,MAAM,cAAc,GAAG,gBAAgB,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,IAAI,CACrE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE;QACzB,OAAO,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC,CACF,CAAC;IAEF,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,cAAc,EAAE,CAAC;QAC7C,IAAI,gBAAgB,CAAC,iBAAiB,EAAE,OAAO,CAAC,EAAE,CAAC;YACjD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAjBD,wCAiBC;AAED;;;;;;;GAOG;AACH,SAAS,gBAAgB,CACvB,cAA6B,EAC7B,eAA8B;IAE9B,OAAO,CACL,cAAc,KAAK,eAAe;QAClC,IAAA,iBAAS,EAAC,cAAc,EAAE,eAAe,CAAC,CAC3C,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CACvB,KAAY;IAEZ,2EAA2E;IAC3E,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAwC,CAAC;AACtE,CAAC","sourcesContent":["import { gtVersion, isValidSemVerVersion } from '@metamask/utils';\nimport type { Json, SemVerVersion } from '@metamask/utils';\n\nimport type { MultiVersionFeatureFlagValue } from '../remote-feature-flag-controller-types';\n\n/**\n * Constants for MultiVersionFeatureFlagValue property names\n * to ensure consistency across validation, type checking, and other usage.\n */\nexport const MULTI_VERSION_FLAG_KEYS = {\n VERSIONS: 'versions',\n} as const;\n\n/**\n * Checks if a feature flag value is a multi-version gated flag (contains versions object with valid SemVer keys).\n *\n * @param value - The feature flag value to check\n * @returns true if the value is a multi-version feature flag with valid SemVer version keys, false otherwise\n */\nexport function isVersionFeatureFlag(\n value: Json,\n): value is MultiVersionFeatureFlagValue {\n if (\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value) ||\n !(MULTI_VERSION_FLAG_KEYS.VERSIONS in value)\n ) {\n return false;\n }\n\n const versions = value[MULTI_VERSION_FLAG_KEYS.VERSIONS];\n\n if (\n typeof versions !== 'object' ||\n versions === null ||\n Array.isArray(versions)\n ) {\n return false;\n }\n\n // Validate that all version keys are valid SemVer versions\n const versionKeys = Object.keys(versions);\n return versionKeys.every((versionKey) => isValidSemVerVersion(versionKey));\n}\n\n/**\n * Selects the appropriate version value from a multi-version feature flag based on current app version.\n * Returns the value from the highest version that the app version meets or exceeds.\n *\n * @param multiVersionFlag - The multi-version feature flag\n * @param currentAppVersion - The current application version\n * @returns The selected version value, or null if no version requirements are met\n */\nexport function getVersionData(\n multiVersionFlag: MultiVersionFeatureFlagValue,\n currentAppVersion: SemVerVersion,\n): Json | null {\n const sortedVersions = getObjectEntries(multiVersionFlag.versions).sort(\n ([versionA], [versionB]) => {\n return isVersionAtLeast(versionA, versionB) ? -1 : 1;\n },\n );\n\n for (const [version, data] of sortedVersions) {\n if (isVersionAtLeast(currentAppVersion, version)) {\n return data;\n }\n }\n\n return null;\n}\n\n/**\n * Compares two semantic version strings.\n * Both versions are expected to be valid SemVer format (e.g., \"13.9.5\").\n *\n * @param currentVersion - The current version (e.g., \"13.9.5\")\n * @param requiredVersion - The required minimum version (e.g., \"13.10.0\")\n * @returns true if currentVersion >= requiredVersion, false otherwise\n */\nfunction isVersionAtLeast(\n currentVersion: SemVerVersion,\n requiredVersion: SemVerVersion,\n): boolean {\n return (\n currentVersion === requiredVersion ||\n gtVersion(currentVersion, requiredVersion)\n );\n}\n\n/**\n * A utility function that calls `Object.entries` while preserving the index type.\n *\n * @param input - The object to get the entries of.\n * @returns - The object entries.\n */\nfunction getObjectEntries<Input extends Record<string, unknown>>(\n input: Input,\n): [keyof Input, Input[keyof Input]][] {\n // Use cast to preserve index type. Object.entries always widens to string.\n return Object.entries(input) as [keyof Input, Input[keyof Input]][];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"version.cjs","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":";;;AAAA,2CAAkE;AAKlE;;;GAGG;AACU,QAAA,uBAAuB,GAAG;IACrC,QAAQ,EAAE,UAAU;CACZ,CAAC;AAEX;;;;;GAKG;AACH,SAAgB,oBAAoB,CAClC,KAAW;IAEX,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACpB,CAAC,CAAC,+BAAuB,CAAC,QAAQ,IAAI,KAAK,CAAC,EAC5C,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,+BAAuB,CAAC,QAAQ,CAAC,CAAC;IAEzD,IACE,OAAO,QAAQ,KAAK,QAAQ;QAC5B,QAAQ,KAAK,IAAI;QACjB,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EACvB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,2DAA2D;IAC3D,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1C,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,IAAA,4BAAoB,EAAC,UAAU,CAAC,CAAC,CAAC;AAC7E,CAAC;AAzBD,oDAyBC;AAED;;;;;;;GAOG;AACH,SAAgB,cAAc,CAC5B,gBAA8C,EAC9C,iBAAgC;IAEhC,MAAM,cAAc,GAAG,gBAAgB,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,IAAI,CACrE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE;QACzB,OAAO,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC,CACF,CAAC;IAEF,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,cAAc,EAAE,CAAC;QAC7C,IAAI,gBAAgB,CAAC,iBAAiB,EAAE,OAAO,CAAC,EAAE,CAAC;YACjD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAjBD,wCAiBC;AAED;;;;;;;GAOG;AACH,SAAS,gBAAgB,CACvB,cAA6B,EAC7B,eAA8B;IAE9B,OAAO,CACL,cAAc,KAAK,eAAe;QAClC,IAAA,iBAAS,EAAC,cAAc,EAAE,eAAe,CAAC,CAC3C,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CACvB,KAAY;IAEZ,2EAA2E;IAC3E,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAwC,CAAC;AACtE,CAAC","sourcesContent":["import { gtVersion, isValidSemVerVersion } from '@metamask/utils';\nimport type { Json, SemVerVersion } from '@metamask/utils';\n\nimport type { MultiVersionFeatureFlagValue } from '../remote-feature-flag-controller-types.js';\n\n/**\n * Constants for MultiVersionFeatureFlagValue property names\n * to ensure consistency across validation, type checking, and other usage.\n */\nexport const MULTI_VERSION_FLAG_KEYS = {\n VERSIONS: 'versions',\n} as const;\n\n/**\n * Checks if a feature flag value is a multi-version gated flag (contains versions object with valid SemVer keys).\n *\n * @param value - The feature flag value to check\n * @returns true if the value is a multi-version feature flag with valid SemVer version keys, false otherwise\n */\nexport function isVersionFeatureFlag(\n value: Json,\n): value is MultiVersionFeatureFlagValue {\n if (\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value) ||\n !(MULTI_VERSION_FLAG_KEYS.VERSIONS in value)\n ) {\n return false;\n }\n\n const versions = value[MULTI_VERSION_FLAG_KEYS.VERSIONS];\n\n if (\n typeof versions !== 'object' ||\n versions === null ||\n Array.isArray(versions)\n ) {\n return false;\n }\n\n // Validate that all version keys are valid SemVer versions\n const versionKeys = Object.keys(versions);\n return versionKeys.every((versionKey) => isValidSemVerVersion(versionKey));\n}\n\n/**\n * Selects the appropriate version value from a multi-version feature flag based on current app version.\n * Returns the value from the highest version that the app version meets or exceeds.\n *\n * @param multiVersionFlag - The multi-version feature flag\n * @param currentAppVersion - The current application version\n * @returns The selected version value, or null if no version requirements are met\n */\nexport function getVersionData(\n multiVersionFlag: MultiVersionFeatureFlagValue,\n currentAppVersion: SemVerVersion,\n): Json | null {\n const sortedVersions = getObjectEntries(multiVersionFlag.versions).sort(\n ([versionA], [versionB]) => {\n return isVersionAtLeast(versionA, versionB) ? -1 : 1;\n },\n );\n\n for (const [version, data] of sortedVersions) {\n if (isVersionAtLeast(currentAppVersion, version)) {\n return data;\n }\n }\n\n return null;\n}\n\n/**\n * Compares two semantic version strings.\n * Both versions are expected to be valid SemVer format (e.g., \"13.9.5\").\n *\n * @param currentVersion - The current version (e.g., \"13.9.5\")\n * @param requiredVersion - The required minimum version (e.g., \"13.10.0\")\n * @returns true if currentVersion >= requiredVersion, false otherwise\n */\nfunction isVersionAtLeast(\n currentVersion: SemVerVersion,\n requiredVersion: SemVerVersion,\n): boolean {\n return (\n currentVersion === requiredVersion ||\n gtVersion(currentVersion, requiredVersion)\n );\n}\n\n/**\n * A utility function that calls `Object.entries` while preserving the index type.\n *\n * @param input - The object to get the entries of.\n * @returns - The object entries.\n */\nfunction getObjectEntries<Input extends Record<string, unknown>>(\n input: Input,\n): [keyof Input, Input[keyof Input]][] {\n // Use cast to preserve index type. Object.entries always widens to string.\n return Object.entries(input) as [keyof Input, Input[keyof Input]][];\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.cts","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,wBAAwB;AAE3D,OAAO,KAAK,EAAE,4BAA4B,EAAE,
|
|
1
|
+
{"version":3,"file":"version.d.cts","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,wBAAwB;AAE3D,OAAO,KAAK,EAAE,4BAA4B,EAAE,oDAAmD;AAE/F;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;CAE1B,CAAC;AAEX;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,IAAI,GACV,KAAK,IAAI,4BAA4B,CAuBvC;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,gBAAgB,EAAE,4BAA4B,EAC9C,iBAAiB,EAAE,aAAa,GAC/B,IAAI,GAAG,IAAI,CAcb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.mts","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,wBAAwB;AAE3D,OAAO,KAAK,EAAE,4BAA4B,EAAE,
|
|
1
|
+
{"version":3,"file":"version.d.mts","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,wBAAwB;AAE3D,OAAO,KAAK,EAAE,4BAA4B,EAAE,oDAAmD;AAE/F;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;CAE1B,CAAC;AAEX;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,IAAI,GACV,KAAK,IAAI,4BAA4B,CAuBvC;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,gBAAgB,EAAE,4BAA4B,EAC9C,iBAAiB,EAAE,aAAa,GAC/B,IAAI,GAAG,IAAI,CAcb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.mjs","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,wBAAwB;AAKlE;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,QAAQ,EAAE,UAAU;CACZ,CAAC;AAEX;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAW;IAEX,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACpB,CAAC,CAAC,uBAAuB,CAAC,QAAQ,IAAI,KAAK,CAAC,EAC5C,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAEzD,IACE,OAAO,QAAQ,KAAK,QAAQ;QAC5B,QAAQ,KAAK,IAAI;QACjB,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EACvB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,2DAA2D;IAC3D,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1C,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,gBAA8C,EAC9C,iBAAgC;IAEhC,MAAM,cAAc,GAAG,gBAAgB,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,IAAI,CACrE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE;QACzB,OAAO,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC,CACF,CAAC;IAEF,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,cAAc,EAAE,CAAC;QAC7C,IAAI,gBAAgB,CAAC,iBAAiB,EAAE,OAAO,CAAC,EAAE,CAAC;YACjD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,gBAAgB,CACvB,cAA6B,EAC7B,eAA8B;IAE9B,OAAO,CACL,cAAc,KAAK,eAAe;QAClC,SAAS,CAAC,cAAc,EAAE,eAAe,CAAC,CAC3C,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CACvB,KAAY;IAEZ,2EAA2E;IAC3E,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAwC,CAAC;AACtE,CAAC","sourcesContent":["import { gtVersion, isValidSemVerVersion } from '@metamask/utils';\nimport type { Json, SemVerVersion } from '@metamask/utils';\n\nimport type { MultiVersionFeatureFlagValue } from '../remote-feature-flag-controller-types';\n\n/**\n * Constants for MultiVersionFeatureFlagValue property names\n * to ensure consistency across validation, type checking, and other usage.\n */\nexport const MULTI_VERSION_FLAG_KEYS = {\n VERSIONS: 'versions',\n} as const;\n\n/**\n * Checks if a feature flag value is a multi-version gated flag (contains versions object with valid SemVer keys).\n *\n * @param value - The feature flag value to check\n * @returns true if the value is a multi-version feature flag with valid SemVer version keys, false otherwise\n */\nexport function isVersionFeatureFlag(\n value: Json,\n): value is MultiVersionFeatureFlagValue {\n if (\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value) ||\n !(MULTI_VERSION_FLAG_KEYS.VERSIONS in value)\n ) {\n return false;\n }\n\n const versions = value[MULTI_VERSION_FLAG_KEYS.VERSIONS];\n\n if (\n typeof versions !== 'object' ||\n versions === null ||\n Array.isArray(versions)\n ) {\n return false;\n }\n\n // Validate that all version keys are valid SemVer versions\n const versionKeys = Object.keys(versions);\n return versionKeys.every((versionKey) => isValidSemVerVersion(versionKey));\n}\n\n/**\n * Selects the appropriate version value from a multi-version feature flag based on current app version.\n * Returns the value from the highest version that the app version meets or exceeds.\n *\n * @param multiVersionFlag - The multi-version feature flag\n * @param currentAppVersion - The current application version\n * @returns The selected version value, or null if no version requirements are met\n */\nexport function getVersionData(\n multiVersionFlag: MultiVersionFeatureFlagValue,\n currentAppVersion: SemVerVersion,\n): Json | null {\n const sortedVersions = getObjectEntries(multiVersionFlag.versions).sort(\n ([versionA], [versionB]) => {\n return isVersionAtLeast(versionA, versionB) ? -1 : 1;\n },\n );\n\n for (const [version, data] of sortedVersions) {\n if (isVersionAtLeast(currentAppVersion, version)) {\n return data;\n }\n }\n\n return null;\n}\n\n/**\n * Compares two semantic version strings.\n * Both versions are expected to be valid SemVer format (e.g., \"13.9.5\").\n *\n * @param currentVersion - The current version (e.g., \"13.9.5\")\n * @param requiredVersion - The required minimum version (e.g., \"13.10.0\")\n * @returns true if currentVersion >= requiredVersion, false otherwise\n */\nfunction isVersionAtLeast(\n currentVersion: SemVerVersion,\n requiredVersion: SemVerVersion,\n): boolean {\n return (\n currentVersion === requiredVersion ||\n gtVersion(currentVersion, requiredVersion)\n );\n}\n\n/**\n * A utility function that calls `Object.entries` while preserving the index type.\n *\n * @param input - The object to get the entries of.\n * @returns - The object entries.\n */\nfunction getObjectEntries<Input extends Record<string, unknown>>(\n input: Input,\n): [keyof Input, Input[keyof Input]][] {\n // Use cast to preserve index type. Object.entries always widens to string.\n return Object.entries(input) as [keyof Input, Input[keyof Input]][];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"version.mjs","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,wBAAwB;AAKlE;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,QAAQ,EAAE,UAAU;CACZ,CAAC;AAEX;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAW;IAEX,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACpB,CAAC,CAAC,uBAAuB,CAAC,QAAQ,IAAI,KAAK,CAAC,EAC5C,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAEzD,IACE,OAAO,QAAQ,KAAK,QAAQ;QAC5B,QAAQ,KAAK,IAAI;QACjB,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EACvB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,2DAA2D;IAC3D,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1C,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,gBAA8C,EAC9C,iBAAgC;IAEhC,MAAM,cAAc,GAAG,gBAAgB,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,IAAI,CACrE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE;QACzB,OAAO,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC,CACF,CAAC;IAEF,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,cAAc,EAAE,CAAC;QAC7C,IAAI,gBAAgB,CAAC,iBAAiB,EAAE,OAAO,CAAC,EAAE,CAAC;YACjD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,gBAAgB,CACvB,cAA6B,EAC7B,eAA8B;IAE9B,OAAO,CACL,cAAc,KAAK,eAAe;QAClC,SAAS,CAAC,cAAc,EAAE,eAAe,CAAC,CAC3C,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CACvB,KAAY;IAEZ,2EAA2E;IAC3E,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAwC,CAAC;AACtE,CAAC","sourcesContent":["import { gtVersion, isValidSemVerVersion } from '@metamask/utils';\nimport type { Json, SemVerVersion } from '@metamask/utils';\n\nimport type { MultiVersionFeatureFlagValue } from '../remote-feature-flag-controller-types.js';\n\n/**\n * Constants for MultiVersionFeatureFlagValue property names\n * to ensure consistency across validation, type checking, and other usage.\n */\nexport const MULTI_VERSION_FLAG_KEYS = {\n VERSIONS: 'versions',\n} as const;\n\n/**\n * Checks if a feature flag value is a multi-version gated flag (contains versions object with valid SemVer keys).\n *\n * @param value - The feature flag value to check\n * @returns true if the value is a multi-version feature flag with valid SemVer version keys, false otherwise\n */\nexport function isVersionFeatureFlag(\n value: Json,\n): value is MultiVersionFeatureFlagValue {\n if (\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value) ||\n !(MULTI_VERSION_FLAG_KEYS.VERSIONS in value)\n ) {\n return false;\n }\n\n const versions = value[MULTI_VERSION_FLAG_KEYS.VERSIONS];\n\n if (\n typeof versions !== 'object' ||\n versions === null ||\n Array.isArray(versions)\n ) {\n return false;\n }\n\n // Validate that all version keys are valid SemVer versions\n const versionKeys = Object.keys(versions);\n return versionKeys.every((versionKey) => isValidSemVerVersion(versionKey));\n}\n\n/**\n * Selects the appropriate version value from a multi-version feature flag based on current app version.\n * Returns the value from the highest version that the app version meets or exceeds.\n *\n * @param multiVersionFlag - The multi-version feature flag\n * @param currentAppVersion - The current application version\n * @returns The selected version value, or null if no version requirements are met\n */\nexport function getVersionData(\n multiVersionFlag: MultiVersionFeatureFlagValue,\n currentAppVersion: SemVerVersion,\n): Json | null {\n const sortedVersions = getObjectEntries(multiVersionFlag.versions).sort(\n ([versionA], [versionB]) => {\n return isVersionAtLeast(versionA, versionB) ? -1 : 1;\n },\n );\n\n for (const [version, data] of sortedVersions) {\n if (isVersionAtLeast(currentAppVersion, version)) {\n return data;\n }\n }\n\n return null;\n}\n\n/**\n * Compares two semantic version strings.\n * Both versions are expected to be valid SemVer format (e.g., \"13.9.5\").\n *\n * @param currentVersion - The current version (e.g., \"13.9.5\")\n * @param requiredVersion - The required minimum version (e.g., \"13.10.0\")\n * @returns true if currentVersion >= requiredVersion, false otherwise\n */\nfunction isVersionAtLeast(\n currentVersion: SemVerVersion,\n requiredVersion: SemVerVersion,\n): boolean {\n return (\n currentVersion === requiredVersion ||\n gtVersion(currentVersion, requiredVersion)\n );\n}\n\n/**\n * A utility function that calls `Object.entries` while preserving the index type.\n *\n * @param input - The object to get the entries of.\n * @returns - The object entries.\n */\nfunction getObjectEntries<Input extends Record<string, unknown>>(\n input: Input,\n): [keyof Input, Input[keyof Input]][] {\n // Use cast to preserve index type. Object.entries always widens to string.\n return Object.entries(input) as [keyof Input, Input[keyof Input]][];\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask-previews/remote-feature-flag-controller",
|
|
3
|
-
"version": "4.2.2-preview-
|
|
3
|
+
"version": "4.2.2-preview-a3e31582e",
|
|
4
4
|
"description": "The RemoteFeatureFlagController manages the retrieval and caching of remote feature flags",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Ethereum",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"changelog:validate": "../../scripts/validate-changelog.sh @metamask/remote-feature-flag-controller",
|
|
47
47
|
"lint:tsconfigs": "tsx ../../scripts/lint-tsconfigs/lint-tsconfigs.mts",
|
|
48
48
|
"lint:tsconfigs:fix": "tsx ../../scripts/lint-tsconfigs/lint-tsconfigs.mts --fix",
|
|
49
|
-
"messenger-action-types:check": "tsx ../../packages/messenger-cli/src/cli.ts --formatter oxfmt --check",
|
|
50
|
-
"messenger-action-types:generate": "tsx ../../packages/messenger-cli/src/cli.ts --formatter oxfmt --generate",
|
|
49
|
+
"messenger-action-types:check": "tsx ../../packages/messenger-cli/src/cli.ts --formatter oxfmt --esm --check",
|
|
50
|
+
"messenger-action-types:generate": "tsx ../../packages/messenger-cli/src/cli.ts --formatter oxfmt --esm --generate",
|
|
51
51
|
"since-latest-release": "../../scripts/since-latest-release.sh",
|
|
52
52
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
|
|
53
53
|
"test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
|
|
@@ -65,11 +65,11 @@
|
|
|
65
65
|
"@lavamoat/allow-scripts": "^3.0.4",
|
|
66
66
|
"@metamask/auto-changelog": "^6.1.0",
|
|
67
67
|
"@ts-bridge/cli": "^0.6.4",
|
|
68
|
-
"@types/jest": "^
|
|
68
|
+
"@types/jest": "^30.0.0",
|
|
69
69
|
"deepmerge": "^4.2.2",
|
|
70
|
-
"jest": "^
|
|
70
|
+
"jest": "^30.4.2",
|
|
71
71
|
"nock": "^13.3.1",
|
|
72
|
-
"ts-jest": "^29.
|
|
72
|
+
"ts-jest": "^29.4.11",
|
|
73
73
|
"tsx": "^4.20.5",
|
|
74
74
|
"typedoc": "^0.25.13",
|
|
75
75
|
"typedoc-plugin-missing-exports": "^2.0.0",
|