@metamask-previews/remote-feature-flag-controller 5.0.0-preview-4c8a5dfcb → 5.0.0-preview-d74e69d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ - Add optional `defaultFeatureFlags` constructor option to `RemoteFeatureFlagController` for client-side defaults as the lowest-precedence layer under processed remote flags and local overrides ([#9747](https://github.com/MetaMask/core/pull/9747))
13
+
10
14
  ## [5.0.0]
11
15
 
12
16
  ### Added
@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _RemoteFeatureFlagController_instances, _RemoteFeatureFlagController_fetchInterval, _RemoteFeatureFlagController_disabled, _RemoteFeatureFlagController_clientConfigApiService, _RemoteFeatureFlagController_inProgressFlagUpdate, _RemoteFeatureFlagController_getMetaMetricsId, _RemoteFeatureFlagController_clientVersion, _RemoteFeatureFlagController_processedRemoteFeatureFlags, _RemoteFeatureFlagController_isCacheExpired, _RemoteFeatureFlagController_updateCache, _RemoteFeatureFlagController_processVersionBasedFlag, _RemoteFeatureFlagController_processRemoteFeatureFlags;
13
+ var _RemoteFeatureFlagController_instances, _RemoteFeatureFlagController_fetchInterval, _RemoteFeatureFlagController_disabled, _RemoteFeatureFlagController_clientConfigApiService, _RemoteFeatureFlagController_inProgressFlagUpdate, _RemoteFeatureFlagController_getMetaMetricsId, _RemoteFeatureFlagController_clientVersion, _RemoteFeatureFlagController_defaultFeatureFlags, _RemoteFeatureFlagController_processedRemoteFeatureFlags, _RemoteFeatureFlagController_getEffectiveFeatureFlags, _RemoteFeatureFlagController_isCacheExpired, _RemoteFeatureFlagController_updateCache, _RemoteFeatureFlagController_processVersionBasedFlag, _RemoteFeatureFlagController_processRemoteFeatureFlags;
14
14
  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");
@@ -158,8 +158,9 @@ class RemoteFeatureFlagController extends base_controller_1.BaseController {
158
158
  * @param options.getMetaMetricsId - Returns metaMetricsId.
159
159
  * @param options.clientVersion - The current client version for version-based feature flag filtering. Must be a valid 3-part SemVer version string.
160
160
  * @param options.prevClientVersion - The previous client version for feature flag cache invalidation.
161
+ * @param options.defaultFeatureFlags - Client-side default feature flags used as the lowest-precedence layer under processed remote flags and local overrides. Not persisted.
161
162
  */
162
- constructor({ messenger, state, clientConfigApiService, fetchInterval = exports.DEFAULT_CACHE_DURATION, disabled = false, getMetaMetricsId, clientVersion, prevClientVersion, }) {
163
+ constructor({ messenger, state, clientConfigApiService, fetchInterval = exports.DEFAULT_CACHE_DURATION, disabled = false, getMetaMetricsId, clientVersion, prevClientVersion, defaultFeatureFlags = {}, }) {
163
164
  if (!(0, utils_1.isValidSemVerVersion)(clientVersion)) {
164
165
  throw new Error(`Invalid clientVersion: "${clientVersion}". Must be a valid 3-part SemVer version string`);
165
166
  }
@@ -170,6 +171,16 @@ class RemoteFeatureFlagController extends base_controller_1.BaseController {
170
171
  const hasClientVersionChanged = (0, utils_1.isValidSemVerVersion)(prevClientVersion) &&
171
172
  prevClientVersion !== clientVersion;
172
173
  const localOverrides = initialState.localOverrides ?? {};
174
+ // Rebuild the processed remote layer from last session's effective flags by
175
+ // stripping local overrides.
176
+ const processedRemoteFeatureFlags = {
177
+ ...initialState.remoteFeatureFlags,
178
+ };
179
+ for (const [flagName, overrideValue] of Object.entries(localOverrides)) {
180
+ if (processedRemoteFeatureFlags[flagName] === overrideValue) {
181
+ delete processedRemoteFeatureFlags[flagName];
182
+ }
183
+ }
173
184
  super({
174
185
  name: exports.controllerName,
175
186
  metadata: remoteFeatureFlagControllerMetadata,
@@ -177,7 +188,8 @@ class RemoteFeatureFlagController extends base_controller_1.BaseController {
177
188
  state: {
178
189
  ...initialState,
179
190
  remoteFeatureFlags: {
180
- ...initialState.remoteFeatureFlags,
191
+ ...defaultFeatureFlags,
192
+ ...processedRemoteFeatureFlags,
181
193
  ...localOverrides,
182
194
  },
183
195
  cacheTimestamp: hasClientVersionChanged
@@ -192,15 +204,10 @@ class RemoteFeatureFlagController extends base_controller_1.BaseController {
192
204
  _RemoteFeatureFlagController_inProgressFlagUpdate.set(this, void 0);
193
205
  _RemoteFeatureFlagController_getMetaMetricsId.set(this, void 0);
194
206
  _RemoteFeatureFlagController_clientVersion.set(this, void 0);
207
+ _RemoteFeatureFlagController_defaultFeatureFlags.set(this, void 0);
195
208
  _RemoteFeatureFlagController_processedRemoteFeatureFlags.set(this, {});
196
- __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, {
197
- ...initialState.remoteFeatureFlags,
198
- }, "f");
199
- for (const [flagName, overrideValue] of Object.entries(localOverrides)) {
200
- if (__classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f")[flagName] === overrideValue) {
201
- delete __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f")[flagName];
202
- }
203
- }
209
+ __classPrivateFieldSet(this, _RemoteFeatureFlagController_defaultFeatureFlags, defaultFeatureFlags, "f");
210
+ __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, processedRemoteFeatureFlags, "f");
204
211
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_fetchInterval, fetchInterval, "f");
205
212
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_disabled, disabled, "f");
206
213
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_clientConfigApiService, clientConfigApiService, "f");
@@ -259,10 +266,7 @@ class RemoteFeatureFlagController extends base_controller_1.BaseController {
259
266
  return {
260
267
  ...this.state,
261
268
  localOverrides,
262
- remoteFeatureFlags: {
263
- ...this.state.remoteFeatureFlags,
264
- [flagName]: value,
265
- },
269
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), localOverrides),
266
270
  };
267
271
  });
268
272
  }
@@ -274,19 +278,11 @@ class RemoteFeatureFlagController extends base_controller_1.BaseController {
274
278
  removeFlagOverride(flagName) {
275
279
  const newLocalOverrides = { ...this.state.localOverrides };
276
280
  delete newLocalOverrides[flagName];
277
- const remoteFeatureFlags = { ...this.state.remoteFeatureFlags };
278
- const processedValue = __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f")[flagName];
279
- if (processedValue === undefined) {
280
- delete remoteFeatureFlags[flagName];
281
- }
282
- else {
283
- remoteFeatureFlags[flagName] = processedValue;
284
- }
285
281
  this.update(() => {
286
282
  return {
287
283
  ...this.state,
288
284
  localOverrides: newLocalOverrides,
289
- remoteFeatureFlags,
285
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), newLocalOverrides),
290
286
  };
291
287
  });
292
288
  }
@@ -298,13 +294,19 @@ class RemoteFeatureFlagController extends base_controller_1.BaseController {
298
294
  return {
299
295
  ...this.state,
300
296
  localOverrides: {},
301
- remoteFeatureFlags: { ...__classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f") },
297
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), {}),
302
298
  };
303
299
  });
304
300
  }
305
301
  }
306
302
  exports.RemoteFeatureFlagController = RemoteFeatureFlagController;
307
- _RemoteFeatureFlagController_fetchInterval = new WeakMap(), _RemoteFeatureFlagController_disabled = new WeakMap(), _RemoteFeatureFlagController_clientConfigApiService = new WeakMap(), _RemoteFeatureFlagController_inProgressFlagUpdate = new WeakMap(), _RemoteFeatureFlagController_getMetaMetricsId = new WeakMap(), _RemoteFeatureFlagController_clientVersion = new WeakMap(), _RemoteFeatureFlagController_processedRemoteFeatureFlags = new WeakMap(), _RemoteFeatureFlagController_instances = new WeakSet(), _RemoteFeatureFlagController_isCacheExpired = function _RemoteFeatureFlagController_isCacheExpired() {
303
+ _RemoteFeatureFlagController_fetchInterval = new WeakMap(), _RemoteFeatureFlagController_disabled = new WeakMap(), _RemoteFeatureFlagController_clientConfigApiService = new WeakMap(), _RemoteFeatureFlagController_inProgressFlagUpdate = new WeakMap(), _RemoteFeatureFlagController_getMetaMetricsId = new WeakMap(), _RemoteFeatureFlagController_clientVersion = new WeakMap(), _RemoteFeatureFlagController_defaultFeatureFlags = new WeakMap(), _RemoteFeatureFlagController_processedRemoteFeatureFlags = new WeakMap(), _RemoteFeatureFlagController_instances = new WeakSet(), _RemoteFeatureFlagController_getEffectiveFeatureFlags = function _RemoteFeatureFlagController_getEffectiveFeatureFlags(processedRemote, localOverrides = this.state.localOverrides ?? {}) {
304
+ return {
305
+ ...__classPrivateFieldGet(this, _RemoteFeatureFlagController_defaultFeatureFlags, "f"),
306
+ ...processedRemote,
307
+ ...localOverrides,
308
+ };
309
+ }, _RemoteFeatureFlagController_isCacheExpired = function _RemoteFeatureFlagController_isCacheExpired() {
308
310
  return Date.now() - this.state.cacheTimestamp > __classPrivateFieldGet(this, _RemoteFeatureFlagController_fetchInterval, "f");
309
311
  }, _RemoteFeatureFlagController_updateCache =
310
312
  /**
@@ -341,10 +343,7 @@ async function _RemoteFeatureFlagController_updateCache(remoteFeatureFlags) {
341
343
  this.update(() => {
342
344
  return {
343
345
  ...this.state,
344
- remoteFeatureFlags: {
345
- ...redactedProcessedFlags,
346
- ...this.state.localOverrides,
347
- },
346
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, redactedProcessedFlags),
348
347
  rawRemoteFeatureFlags: redactMetaMetricsIds(remoteFeatureFlags),
349
348
  cacheTimestamp: Date.now(),
350
349
  thresholdCache: updatedThresholdCache,
@@ -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.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
+ {"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.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 readonly #defaultFeatureFlags: FeatureFlags;\n\n #processedRemoteFeatureFlags: FeatureFlags = {};\n\n /**\n * Constructs a new RemoteFeatureFlagController instance.\n *\n * @param options - The controller options.\n * @param options.messenger - The messenger used for communication.\n * @param options.state - The initial state of the controller.\n * @param options.clientConfigApiService - The service instance to fetch remote feature flags.\n * @param options.fetchInterval - The interval in milliseconds before cached flags expire. Defaults to 1 day.\n * @param options.disabled - Determines if the controller should be disabled initially. Defaults to false.\n * @param options.getMetaMetricsId - Returns metaMetricsId.\n * @param options.clientVersion - The current client version for version-based feature flag filtering. Must be a valid 3-part SemVer version string.\n * @param options.prevClientVersion - The previous client version for feature flag cache invalidation.\n * @param options.defaultFeatureFlags - Client-side default feature flags used as the lowest-precedence layer under processed remote flags and local overrides. Not persisted.\n */\n constructor({\n messenger,\n state,\n clientConfigApiService,\n fetchInterval = DEFAULT_CACHE_DURATION,\n disabled = false,\n getMetaMetricsId,\n clientVersion,\n prevClientVersion,\n defaultFeatureFlags = {},\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 defaultFeatureFlags?: FeatureFlags;\n }) {\n if (!isValidSemVerVersion(clientVersion)) {\n throw new Error(\n `Invalid clientVersion: \"${clientVersion}\". Must be a valid 3-part SemVer version string`,\n );\n }\n\n const initialState: RemoteFeatureFlagControllerState = {\n ...getDefaultRemoteFeatureFlagControllerState(),\n ...state,\n };\n\n const hasClientVersionChanged =\n isValidSemVerVersion(prevClientVersion) &&\n prevClientVersion !== clientVersion;\n\n const localOverrides = initialState.localOverrides ?? {};\n\n // Rebuild the processed remote layer from last session's effective flags by\n // stripping local overrides.\n const processedRemoteFeatureFlags = {\n ...initialState.remoteFeatureFlags,\n };\n for (const [flagName, overrideValue] of Object.entries(localOverrides)) {\n if (processedRemoteFeatureFlags[flagName] === overrideValue) {\n delete processedRemoteFeatureFlags[flagName];\n }\n }\n\n super({\n name: controllerName,\n metadata: remoteFeatureFlagControllerMetadata,\n messenger,\n state: {\n ...initialState,\n remoteFeatureFlags: {\n ...defaultFeatureFlags,\n ...processedRemoteFeatureFlags,\n ...localOverrides,\n },\n cacheTimestamp: hasClientVersionChanged\n ? 0\n : initialState.cacheTimestamp,\n },\n });\n\n this.#defaultFeatureFlags = defaultFeatureFlags;\n this.#processedRemoteFeatureFlags = processedRemoteFeatureFlags;\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 * Computes effective feature flags with precedence:\n * defaults < processed remote < local overrides.\n *\n * @param processedRemote - The processed remote feature flags.\n * @param localOverrides - Local overrides. Defaults to current state overrides.\n * @returns The effective feature flags.\n */\n #getEffectiveFeatureFlags(\n processedRemote: FeatureFlags,\n localOverrides: FeatureFlags = this.state.localOverrides ?? {},\n ): FeatureFlags {\n return {\n ...this.#defaultFeatureFlags,\n ...processedRemote,\n ...localOverrides,\n };\n }\n\n /**\n * Checks if the cached feature flags are expired based on the fetch interval.\n *\n * @returns Whether the cache is expired (`true`) or still valid (`false`).\n */\n #isCacheExpired(): boolean {\n return Date.now() - this.state.cacheTimestamp > this.#fetchInterval;\n }\n\n /**\n * Retrieves the remote feature flags, fetching from the API if necessary.\n * Uses caching to prevent redundant API calls and handles concurrent fetches.\n *\n * @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: this.#getEffectiveFeatureFlags(\n redactedProcessedFlags,\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: this.#getEffectiveFeatureFlags(\n this.#processedRemoteFeatureFlags,\n localOverrides,\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 this.update(() => {\n return {\n ...this.state,\n localOverrides: newLocalOverrides,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags(\n this.#processedRemoteFeatureFlags,\n newLocalOverrides,\n ),\n };\n });\n }\n\n /**\n * Clears all local feature flag overrides.\n */\n clearAllFlagOverrides(): void {\n this.update(() => {\n return {\n ...this.state,\n localOverrides: {},\n remoteFeatureFlags: this.#getEffectiveFeatureFlags(\n this.#processedRemoteFeatureFlags,\n {},\n ),\n };\n });\n }\n}\n"]}
@@ -46,8 +46,9 @@ export declare class RemoteFeatureFlagController extends BaseController<typeof c
46
46
  * @param options.getMetaMetricsId - Returns metaMetricsId.
47
47
  * @param options.clientVersion - The current client version for version-based feature flag filtering. Must be a valid 3-part SemVer version string.
48
48
  * @param options.prevClientVersion - The previous client version for feature flag cache invalidation.
49
+ * @param options.defaultFeatureFlags - Client-side default feature flags used as the lowest-precedence layer under processed remote flags and local overrides. Not persisted.
49
50
  */
50
- constructor({ messenger, state, clientConfigApiService, fetchInterval, disabled, getMetaMetricsId, clientVersion, prevClientVersion, }: {
51
+ constructor({ messenger, state, clientConfigApiService, fetchInterval, disabled, getMetaMetricsId, clientVersion, prevClientVersion, defaultFeatureFlags, }: {
51
52
  messenger: RemoteFeatureFlagControllerMessenger;
52
53
  state?: Partial<RemoteFeatureFlagControllerState>;
53
54
  clientConfigApiService: AbstractClientConfigApiService;
@@ -56,6 +57,7 @@ export declare class RemoteFeatureFlagController extends BaseController<typeof c
56
57
  disabled?: boolean;
57
58
  clientVersion: string;
58
59
  prevClientVersion?: string;
60
+ defaultFeatureFlags?: FeatureFlags;
59
61
  });
60
62
  /**
61
63
  * Retrieves the remote feature flags, fetching from the API if necessary.
@@ -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,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
+ {"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;;IAiBC;;;;;;;;;;;;;OAaG;gBACS,EACV,SAAS,EACT,KAAK,EACL,sBAAsB,EACtB,aAAsC,EACtC,QAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,mBAAwB,GACzB,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;QAC3B,mBAAmB,CAAC,EAAE,YAAY,CAAC;KACpC;IAwFD;;;;;OAKG;IACG,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAmM/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;IAgB1C;;OAEG;IACH,qBAAqB,IAAI,IAAI;CAY9B"}
@@ -46,8 +46,9 @@ export declare class RemoteFeatureFlagController extends BaseController<typeof c
46
46
  * @param options.getMetaMetricsId - Returns metaMetricsId.
47
47
  * @param options.clientVersion - The current client version for version-based feature flag filtering. Must be a valid 3-part SemVer version string.
48
48
  * @param options.prevClientVersion - The previous client version for feature flag cache invalidation.
49
+ * @param options.defaultFeatureFlags - Client-side default feature flags used as the lowest-precedence layer under processed remote flags and local overrides. Not persisted.
49
50
  */
50
- constructor({ messenger, state, clientConfigApiService, fetchInterval, disabled, getMetaMetricsId, clientVersion, prevClientVersion, }: {
51
+ constructor({ messenger, state, clientConfigApiService, fetchInterval, disabled, getMetaMetricsId, clientVersion, prevClientVersion, defaultFeatureFlags, }: {
51
52
  messenger: RemoteFeatureFlagControllerMessenger;
52
53
  state?: Partial<RemoteFeatureFlagControllerState>;
53
54
  clientConfigApiService: AbstractClientConfigApiService;
@@ -56,6 +57,7 @@ export declare class RemoteFeatureFlagController extends BaseController<typeof c
56
57
  disabled?: boolean;
57
58
  clientVersion: string;
58
59
  prevClientVersion?: string;
60
+ defaultFeatureFlags?: FeatureFlags;
59
61
  });
60
62
  /**
61
63
  * Retrieves the remote feature flags, fetching from the API if necessary.
@@ -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,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
+ {"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;;IAiBC;;;;;;;;;;;;;OAaG;gBACS,EACV,SAAS,EACT,KAAK,EACL,sBAAsB,EACtB,aAAsC,EACtC,QAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,mBAAwB,GACzB,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;QAC3B,mBAAmB,CAAC,EAAE,YAAY,CAAC;KACpC;IAwFD;;;;;OAKG;IACG,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAmM/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;IAgB1C;;OAEG;IACH,qBAAqB,IAAI,IAAI;CAY9B"}
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _RemoteFeatureFlagController_instances, _RemoteFeatureFlagController_fetchInterval, _RemoteFeatureFlagController_disabled, _RemoteFeatureFlagController_clientConfigApiService, _RemoteFeatureFlagController_inProgressFlagUpdate, _RemoteFeatureFlagController_getMetaMetricsId, _RemoteFeatureFlagController_clientVersion, _RemoteFeatureFlagController_processedRemoteFeatureFlags, _RemoteFeatureFlagController_isCacheExpired, _RemoteFeatureFlagController_updateCache, _RemoteFeatureFlagController_processVersionBasedFlag, _RemoteFeatureFlagController_processRemoteFeatureFlags;
12
+ var _RemoteFeatureFlagController_instances, _RemoteFeatureFlagController_fetchInterval, _RemoteFeatureFlagController_disabled, _RemoteFeatureFlagController_clientConfigApiService, _RemoteFeatureFlagController_inProgressFlagUpdate, _RemoteFeatureFlagController_getMetaMetricsId, _RemoteFeatureFlagController_clientVersion, _RemoteFeatureFlagController_defaultFeatureFlags, _RemoteFeatureFlagController_processedRemoteFeatureFlags, _RemoteFeatureFlagController_getEffectiveFeatureFlags, _RemoteFeatureFlagController_isCacheExpired, _RemoteFeatureFlagController_updateCache, _RemoteFeatureFlagController_processVersionBasedFlag, _RemoteFeatureFlagController_processRemoteFeatureFlags;
13
13
  import { BaseController } from "@metamask/base-controller";
14
14
  import { isValidSemVerVersion } from "@metamask/utils";
15
15
  import { calculateThresholdForFlag, isFeatureFlagWithScopeValue } from "./utils/user-segmentation-utils.mjs";
@@ -154,8 +154,9 @@ export class RemoteFeatureFlagController extends BaseController {
154
154
  * @param options.getMetaMetricsId - Returns metaMetricsId.
155
155
  * @param options.clientVersion - The current client version for version-based feature flag filtering. Must be a valid 3-part SemVer version string.
156
156
  * @param options.prevClientVersion - The previous client version for feature flag cache invalidation.
157
+ * @param options.defaultFeatureFlags - Client-side default feature flags used as the lowest-precedence layer under processed remote flags and local overrides. Not persisted.
157
158
  */
158
- constructor({ messenger, state, clientConfigApiService, fetchInterval = DEFAULT_CACHE_DURATION, disabled = false, getMetaMetricsId, clientVersion, prevClientVersion, }) {
159
+ constructor({ messenger, state, clientConfigApiService, fetchInterval = DEFAULT_CACHE_DURATION, disabled = false, getMetaMetricsId, clientVersion, prevClientVersion, defaultFeatureFlags = {}, }) {
159
160
  if (!isValidSemVerVersion(clientVersion)) {
160
161
  throw new Error(`Invalid clientVersion: "${clientVersion}". Must be a valid 3-part SemVer version string`);
161
162
  }
@@ -166,6 +167,16 @@ export class RemoteFeatureFlagController extends BaseController {
166
167
  const hasClientVersionChanged = isValidSemVerVersion(prevClientVersion) &&
167
168
  prevClientVersion !== clientVersion;
168
169
  const localOverrides = initialState.localOverrides ?? {};
170
+ // Rebuild the processed remote layer from last session's effective flags by
171
+ // stripping local overrides.
172
+ const processedRemoteFeatureFlags = {
173
+ ...initialState.remoteFeatureFlags,
174
+ };
175
+ for (const [flagName, overrideValue] of Object.entries(localOverrides)) {
176
+ if (processedRemoteFeatureFlags[flagName] === overrideValue) {
177
+ delete processedRemoteFeatureFlags[flagName];
178
+ }
179
+ }
169
180
  super({
170
181
  name: controllerName,
171
182
  metadata: remoteFeatureFlagControllerMetadata,
@@ -173,7 +184,8 @@ export class RemoteFeatureFlagController extends BaseController {
173
184
  state: {
174
185
  ...initialState,
175
186
  remoteFeatureFlags: {
176
- ...initialState.remoteFeatureFlags,
187
+ ...defaultFeatureFlags,
188
+ ...processedRemoteFeatureFlags,
177
189
  ...localOverrides,
178
190
  },
179
191
  cacheTimestamp: hasClientVersionChanged
@@ -188,15 +200,10 @@ export class RemoteFeatureFlagController extends BaseController {
188
200
  _RemoteFeatureFlagController_inProgressFlagUpdate.set(this, void 0);
189
201
  _RemoteFeatureFlagController_getMetaMetricsId.set(this, void 0);
190
202
  _RemoteFeatureFlagController_clientVersion.set(this, void 0);
203
+ _RemoteFeatureFlagController_defaultFeatureFlags.set(this, void 0);
191
204
  _RemoteFeatureFlagController_processedRemoteFeatureFlags.set(this, {});
192
- __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, {
193
- ...initialState.remoteFeatureFlags,
194
- }, "f");
195
- for (const [flagName, overrideValue] of Object.entries(localOverrides)) {
196
- if (__classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f")[flagName] === overrideValue) {
197
- delete __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f")[flagName];
198
- }
199
- }
205
+ __classPrivateFieldSet(this, _RemoteFeatureFlagController_defaultFeatureFlags, defaultFeatureFlags, "f");
206
+ __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, processedRemoteFeatureFlags, "f");
200
207
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_fetchInterval, fetchInterval, "f");
201
208
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_disabled, disabled, "f");
202
209
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_clientConfigApiService, clientConfigApiService, "f");
@@ -255,10 +262,7 @@ export class RemoteFeatureFlagController extends BaseController {
255
262
  return {
256
263
  ...this.state,
257
264
  localOverrides,
258
- remoteFeatureFlags: {
259
- ...this.state.remoteFeatureFlags,
260
- [flagName]: value,
261
- },
265
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), localOverrides),
262
266
  };
263
267
  });
264
268
  }
@@ -270,19 +274,11 @@ export class RemoteFeatureFlagController extends BaseController {
270
274
  removeFlagOverride(flagName) {
271
275
  const newLocalOverrides = { ...this.state.localOverrides };
272
276
  delete newLocalOverrides[flagName];
273
- const remoteFeatureFlags = { ...this.state.remoteFeatureFlags };
274
- const processedValue = __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f")[flagName];
275
- if (processedValue === undefined) {
276
- delete remoteFeatureFlags[flagName];
277
- }
278
- else {
279
- remoteFeatureFlags[flagName] = processedValue;
280
- }
281
277
  this.update(() => {
282
278
  return {
283
279
  ...this.state,
284
280
  localOverrides: newLocalOverrides,
285
- remoteFeatureFlags,
281
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), newLocalOverrides),
286
282
  };
287
283
  });
288
284
  }
@@ -294,12 +290,18 @@ export class RemoteFeatureFlagController extends BaseController {
294
290
  return {
295
291
  ...this.state,
296
292
  localOverrides: {},
297
- remoteFeatureFlags: { ...__classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f") },
293
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), {}),
298
294
  };
299
295
  });
300
296
  }
301
297
  }
302
- _RemoteFeatureFlagController_fetchInterval = new WeakMap(), _RemoteFeatureFlagController_disabled = new WeakMap(), _RemoteFeatureFlagController_clientConfigApiService = new WeakMap(), _RemoteFeatureFlagController_inProgressFlagUpdate = new WeakMap(), _RemoteFeatureFlagController_getMetaMetricsId = new WeakMap(), _RemoteFeatureFlagController_clientVersion = new WeakMap(), _RemoteFeatureFlagController_processedRemoteFeatureFlags = new WeakMap(), _RemoteFeatureFlagController_instances = new WeakSet(), _RemoteFeatureFlagController_isCacheExpired = function _RemoteFeatureFlagController_isCacheExpired() {
298
+ _RemoteFeatureFlagController_fetchInterval = new WeakMap(), _RemoteFeatureFlagController_disabled = new WeakMap(), _RemoteFeatureFlagController_clientConfigApiService = new WeakMap(), _RemoteFeatureFlagController_inProgressFlagUpdate = new WeakMap(), _RemoteFeatureFlagController_getMetaMetricsId = new WeakMap(), _RemoteFeatureFlagController_clientVersion = new WeakMap(), _RemoteFeatureFlagController_defaultFeatureFlags = new WeakMap(), _RemoteFeatureFlagController_processedRemoteFeatureFlags = new WeakMap(), _RemoteFeatureFlagController_instances = new WeakSet(), _RemoteFeatureFlagController_getEffectiveFeatureFlags = function _RemoteFeatureFlagController_getEffectiveFeatureFlags(processedRemote, localOverrides = this.state.localOverrides ?? {}) {
299
+ return {
300
+ ...__classPrivateFieldGet(this, _RemoteFeatureFlagController_defaultFeatureFlags, "f"),
301
+ ...processedRemote,
302
+ ...localOverrides,
303
+ };
304
+ }, _RemoteFeatureFlagController_isCacheExpired = function _RemoteFeatureFlagController_isCacheExpired() {
303
305
  return Date.now() - this.state.cacheTimestamp > __classPrivateFieldGet(this, _RemoteFeatureFlagController_fetchInterval, "f");
304
306
  }, _RemoteFeatureFlagController_updateCache =
305
307
  /**
@@ -336,10 +338,7 @@ async function _RemoteFeatureFlagController_updateCache(remoteFeatureFlags) {
336
338
  this.update(() => {
337
339
  return {
338
340
  ...this.state,
339
- remoteFeatureFlags: {
340
- ...redactedProcessedFlags,
341
- ...this.state.localOverrides,
342
- },
341
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, redactedProcessedFlags),
343
342
  rawRemoteFeatureFlags: redactMetaMetricsIds(remoteFeatureFlags),
344
343
  cacheTimestamp: Date.now(),
345
344
  thresholdCache: updatedThresholdCache,
@@ -1 +1 @@
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
+ {"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.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 readonly #defaultFeatureFlags: FeatureFlags;\n\n #processedRemoteFeatureFlags: FeatureFlags = {};\n\n /**\n * Constructs a new RemoteFeatureFlagController instance.\n *\n * @param options - The controller options.\n * @param options.messenger - The messenger used for communication.\n * @param options.state - The initial state of the controller.\n * @param options.clientConfigApiService - The service instance to fetch remote feature flags.\n * @param options.fetchInterval - The interval in milliseconds before cached flags expire. Defaults to 1 day.\n * @param options.disabled - Determines if the controller should be disabled initially. Defaults to false.\n * @param options.getMetaMetricsId - Returns metaMetricsId.\n * @param options.clientVersion - The current client version for version-based feature flag filtering. Must be a valid 3-part SemVer version string.\n * @param options.prevClientVersion - The previous client version for feature flag cache invalidation.\n * @param options.defaultFeatureFlags - Client-side default feature flags used as the lowest-precedence layer under processed remote flags and local overrides. Not persisted.\n */\n constructor({\n messenger,\n state,\n clientConfigApiService,\n fetchInterval = DEFAULT_CACHE_DURATION,\n disabled = false,\n getMetaMetricsId,\n clientVersion,\n prevClientVersion,\n defaultFeatureFlags = {},\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 defaultFeatureFlags?: FeatureFlags;\n }) {\n if (!isValidSemVerVersion(clientVersion)) {\n throw new Error(\n `Invalid clientVersion: \"${clientVersion}\". Must be a valid 3-part SemVer version string`,\n );\n }\n\n const initialState: RemoteFeatureFlagControllerState = {\n ...getDefaultRemoteFeatureFlagControllerState(),\n ...state,\n };\n\n const hasClientVersionChanged =\n isValidSemVerVersion(prevClientVersion) &&\n prevClientVersion !== clientVersion;\n\n const localOverrides = initialState.localOverrides ?? {};\n\n // Rebuild the processed remote layer from last session's effective flags by\n // stripping local overrides.\n const processedRemoteFeatureFlags = {\n ...initialState.remoteFeatureFlags,\n };\n for (const [flagName, overrideValue] of Object.entries(localOverrides)) {\n if (processedRemoteFeatureFlags[flagName] === overrideValue) {\n delete processedRemoteFeatureFlags[flagName];\n }\n }\n\n super({\n name: controllerName,\n metadata: remoteFeatureFlagControllerMetadata,\n messenger,\n state: {\n ...initialState,\n remoteFeatureFlags: {\n ...defaultFeatureFlags,\n ...processedRemoteFeatureFlags,\n ...localOverrides,\n },\n cacheTimestamp: hasClientVersionChanged\n ? 0\n : initialState.cacheTimestamp,\n },\n });\n\n this.#defaultFeatureFlags = defaultFeatureFlags;\n this.#processedRemoteFeatureFlags = processedRemoteFeatureFlags;\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 * Computes effective feature flags with precedence:\n * defaults < processed remote < local overrides.\n *\n * @param processedRemote - The processed remote feature flags.\n * @param localOverrides - Local overrides. Defaults to current state overrides.\n * @returns The effective feature flags.\n */\n #getEffectiveFeatureFlags(\n processedRemote: FeatureFlags,\n localOverrides: FeatureFlags = this.state.localOverrides ?? {},\n ): FeatureFlags {\n return {\n ...this.#defaultFeatureFlags,\n ...processedRemote,\n ...localOverrides,\n };\n }\n\n /**\n * Checks if the cached feature flags are expired based on the fetch interval.\n *\n * @returns Whether the cache is expired (`true`) or still valid (`false`).\n */\n #isCacheExpired(): boolean {\n return Date.now() - this.state.cacheTimestamp > this.#fetchInterval;\n }\n\n /**\n * Retrieves the remote feature flags, fetching from the API if necessary.\n * Uses caching to prevent redundant API calls and handles concurrent fetches.\n *\n * @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: this.#getEffectiveFeatureFlags(\n redactedProcessedFlags,\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: this.#getEffectiveFeatureFlags(\n this.#processedRemoteFeatureFlags,\n localOverrides,\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 this.update(() => {\n return {\n ...this.state,\n localOverrides: newLocalOverrides,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags(\n this.#processedRemoteFeatureFlags,\n newLocalOverrides,\n ),\n };\n });\n }\n\n /**\n * Clears all local feature flag overrides.\n */\n clearAllFlagOverrides(): void {\n this.update(() => {\n return {\n ...this.state,\n localOverrides: {},\n remoteFeatureFlags: this.#getEffectiveFeatureFlags(\n this.#processedRemoteFeatureFlags,\n {},\n ),\n };\n });\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/remote-feature-flag-controller",
3
- "version": "5.0.0-preview-4c8a5dfcb",
3
+ "version": "5.0.0-preview-d74e69d",
4
4
  "description": "The RemoteFeatureFlagController manages the retrieval and caching of remote feature flags",
5
5
  "keywords": [
6
6
  "Ethereum",