@metamask-previews/remote-feature-flag-controller 5.0.0-preview-57cd9b4fb → 5.0.0-preview-d21e2aaf9

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
@@ -11,18 +11,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
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
13
 
14
- ### Changed
15
-
16
- - **BREAKING:** Add `RemoteFeatureFlagController.init` method ([#9816](https://github.com/MetaMask/core/pull/9816))
17
- - This must be called during initialization to ensure `remoteFeatureFlags` is properly recomputed.
18
- - **BREAKING:** Stop redacting IDs from `rawRemoteFeatureFlags` ([#9816](https://github.com/MetaMask/core/pull/9816))
19
- - Existing `rawRemoteFeatureFlags` properties should be deleted in a migration, so they do not get used for recomputing flags (which would not work properly with a redacted input).
20
-
21
- ### Fixed
22
-
23
- - Restore remote flag value when overrides are removed/cleared ([#9816](https://github.com/MetaMask/core/pull/9816))
24
- - Previously the underlying remote value would be removed as well.
25
-
26
14
  ## [5.0.0]
27
15
 
28
16
  ### Added
@@ -108,6 +108,37 @@ function findExplicitIdMatch(entries, normalizedId) {
108
108
  }
109
109
  return undefined;
110
110
  }
111
+ /**
112
+ * Returns a copy of `flags` with `metaMetricsIds` removed from every
113
+ * threshold entry. Used before persisting raw flags to state so that
114
+ * MetaMetrics IDs are never written to state logs or debug snapshots.
115
+ *
116
+ * @param flags - The raw feature flags object from the API.
117
+ * @returns A new object with the same structure but without any
118
+ * `metaMetricsIds` fields inside threshold entry arrays.
119
+ */
120
+ function redactMetaMetricsIds(flags) {
121
+ const result = {};
122
+ for (const [name, value] of Object.entries(flags)) {
123
+ if (!Array.isArray(value)) {
124
+ result[name] = value;
125
+ continue;
126
+ }
127
+ result[name] = value.map((entry) => {
128
+ if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
129
+ return entry;
130
+ }
131
+ const entryRecord = entry;
132
+ if (entryRecord.metaMetricsIds === undefined) {
133
+ return entry;
134
+ }
135
+ const copy = { ...entryRecord };
136
+ delete copy.metaMetricsIds;
137
+ return copy;
138
+ });
139
+ }
140
+ return result;
141
+ }
111
142
  /**
112
143
  * The RemoteFeatureFlagController manages the retrieval and caching of remote feature flags.
113
144
  * It fetches feature flags from a remote API, caches them, and provides methods to access
@@ -139,12 +170,28 @@ class RemoteFeatureFlagController extends base_controller_1.BaseController {
139
170
  };
140
171
  const hasClientVersionChanged = (0, utils_1.isValidSemVerVersion)(prevClientVersion) &&
141
172
  prevClientVersion !== clientVersion;
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
+ }
142
184
  super({
143
185
  name: exports.controllerName,
144
186
  metadata: remoteFeatureFlagControllerMetadata,
145
187
  messenger,
146
188
  state: {
147
189
  ...initialState,
190
+ remoteFeatureFlags: {
191
+ ...defaultFeatureFlags,
192
+ ...processedRemoteFeatureFlags,
193
+ ...localOverrides,
194
+ },
148
195
  cacheTimestamp: hasClientVersionChanged
149
196
  ? 0
150
197
  : initialState.cacheTimestamp,
@@ -158,13 +205,9 @@ class RemoteFeatureFlagController extends base_controller_1.BaseController {
158
205
  _RemoteFeatureFlagController_getMetaMetricsId.set(this, void 0);
159
206
  _RemoteFeatureFlagController_clientVersion.set(this, void 0);
160
207
  _RemoteFeatureFlagController_defaultFeatureFlags.set(this, void 0);
161
- _RemoteFeatureFlagController_processedRemoteFeatureFlags.set(this, void 0);
208
+ _RemoteFeatureFlagController_processedRemoteFeatureFlags.set(this, {});
162
209
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_defaultFeatureFlags, defaultFeatureFlags, "f");
163
- // Last session's effective flags stand in for the remote layer until
164
- // `init` re-derives it from the persisted raw flags, or a fetch replaces
165
- // it. Overrides are layered on top rather than subtracted out, so a remote
166
- // flag that happens to share an override's value is not lost.
167
- __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, initialState.remoteFeatureFlags, "f");
210
+ __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, processedRemoteFeatureFlags, "f");
168
211
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_fetchInterval, fetchInterval, "f");
169
212
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_disabled, disabled, "f");
170
213
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_clientConfigApiService, clientConfigApiService, "f");
@@ -196,35 +239,6 @@ class RemoteFeatureFlagController extends base_controller_1.BaseController {
196
239
  }
197
240
  await __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_updateCache).call(this, serverData.remoteFeatureFlags);
198
241
  }
199
- /**
200
- * Computes the effective feature flags, re-deriving the remote layer from the
201
- * raw flags already in state. Threshold selection needs to await a hash and
202
- * so cannot run in the constructor, which is why this cannot be part of
203
- * construction. Clients must call this once after constructing the
204
- * controller.
205
- *
206
- * When there are no persisted raw flags, as on a fresh install or for state
207
- * persisted before raw flags were stored, the previous session's flags stand
208
- * in for the remote layer so that nothing is lost.
209
- */
210
- async init() {
211
- const { rawRemoteFeatureFlags } = this.state;
212
- const hasRawRemoteFeatureFlags = rawRemoteFeatureFlags && Object.keys(rawRemoteFeatureFlags).length > 0;
213
- const resolved = hasRawRemoteFeatureFlags
214
- ? await __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_processRemoteFeatureFlags).call(this, rawRemoteFeatureFlags)
215
- : undefined;
216
- __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, resolved?.processedFlags ?? this.state.remoteFeatureFlags, "f");
217
- this.update(() => {
218
- return {
219
- ...this.state,
220
- remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this),
221
- ...(resolved && {
222
- thresholdCache: resolved.thresholdCache,
223
- featureFlagThresholdGroups: resolved.featureFlagThresholdGroups,
224
- }),
225
- };
226
- });
227
- }
228
242
  /**
229
243
  * Enables the controller, allowing it to make network requests.
230
244
  */
@@ -252,7 +266,7 @@ class RemoteFeatureFlagController extends base_controller_1.BaseController {
252
266
  return {
253
267
  ...this.state,
254
268
  localOverrides,
255
- remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, { localOverrides }),
269
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), localOverrides),
256
270
  };
257
271
  });
258
272
  }
@@ -268,9 +282,7 @@ class RemoteFeatureFlagController extends base_controller_1.BaseController {
268
282
  return {
269
283
  ...this.state,
270
284
  localOverrides: newLocalOverrides,
271
- remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, {
272
- localOverrides: newLocalOverrides,
273
- }),
285
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), newLocalOverrides),
274
286
  };
275
287
  });
276
288
  }
@@ -282,18 +294,16 @@ class RemoteFeatureFlagController extends base_controller_1.BaseController {
282
294
  return {
283
295
  ...this.state,
284
296
  localOverrides: {},
285
- remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, {
286
- localOverrides: {},
287
- }),
297
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), {}),
288
298
  };
289
299
  });
290
300
  }
291
301
  }
292
302
  exports.RemoteFeatureFlagController = RemoteFeatureFlagController;
293
- _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({ processedRemoteFeatureFlags = __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), localOverrides = this.state.localOverrides, } = {}) {
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 ?? {}) {
294
304
  return {
295
305
  ...__classPrivateFieldGet(this, _RemoteFeatureFlagController_defaultFeatureFlags, "f"),
296
- ...processedRemoteFeatureFlags,
306
+ ...processedRemote,
297
307
  ...localOverrides,
298
308
  };
299
309
  }, _RemoteFeatureFlagController_isCacheExpired = function _RemoteFeatureFlagController_isCacheExpired() {
@@ -305,19 +315,39 @@ _RemoteFeatureFlagController_fetchInterval = new WeakMap(), _RemoteFeatureFlagCo
305
315
  * @param remoteFeatureFlags - The new feature flags to cache.
306
316
  */
307
317
  async function _RemoteFeatureFlagController_updateCache(remoteFeatureFlags) {
308
- const resolved = await __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_processRemoteFeatureFlags).call(this, remoteFeatureFlags);
309
- __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, resolved.processedFlags, "f");
318
+ const { processedFlags, thresholdCacheUpdates, featureFlagThresholdGroupUpdates, } = await __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_processRemoteFeatureFlags).call(this, remoteFeatureFlags);
319
+ const metaMetricsId = __classPrivateFieldGet(this, _RemoteFeatureFlagController_getMetaMetricsId, "f").call(this);
320
+ const currentFlagNames = Object.keys(remoteFeatureFlags);
321
+ // Build updated threshold cache
322
+ const updatedThresholdCache = { ...(this.state.thresholdCache ?? {}) };
323
+ // Apply new thresholds
324
+ for (const [cacheKey, threshold] of Object.entries(thresholdCacheUpdates)) {
325
+ updatedThresholdCache[cacheKey] = threshold;
326
+ }
327
+ // Clean up stale entries
328
+ for (const cacheKey of Object.keys(updatedThresholdCache)) {
329
+ const [cachedMetaMetricsId, ...cachedFlagNameParts] = cacheKey.split(':');
330
+ const cachedFlagName = cachedFlagNameParts.join(':');
331
+ if (cachedMetaMetricsId === metaMetricsId &&
332
+ !currentFlagNames.includes(cachedFlagName)) {
333
+ delete updatedThresholdCache[cacheKey];
334
+ }
335
+ }
336
+ // Strip metaMetricsIds from processed flags so they never appear in
337
+ // remoteFeatureFlags state or #processedRemoteFeatureFlags. Arrays that
338
+ // were preserved as-is (e.g. when metaMetricsId is missing) would
339
+ // otherwise leak explicit-targeting IDs into diagnostics.
340
+ const redactedProcessedFlags = redactMetaMetricsIds(processedFlags);
310
341
  // Single state update with all changes batched together
342
+ __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, redactedProcessedFlags, "f");
311
343
  this.update(() => {
312
344
  return {
313
345
  ...this.state,
314
- remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, {
315
- processedRemoteFeatureFlags: resolved.processedFlags,
316
- }),
317
- rawRemoteFeatureFlags: remoteFeatureFlags,
346
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, redactedProcessedFlags),
347
+ rawRemoteFeatureFlags: redactMetaMetricsIds(remoteFeatureFlags),
318
348
  cacheTimestamp: Date.now(),
319
- thresholdCache: resolved.thresholdCache,
320
- featureFlagThresholdGroups: resolved.featureFlagThresholdGroups,
349
+ thresholdCache: updatedThresholdCache,
350
+ featureFlagThresholdGroups: featureFlagThresholdGroupUpdates,
321
351
  };
322
352
  });
323
353
  }, _RemoteFeatureFlagController_processVersionBasedFlag = function _RemoteFeatureFlagController_processVersionBasedFlag(flagValue) {
@@ -325,21 +355,11 @@ async function _RemoteFeatureFlagController_updateCache(remoteFeatureFlags) {
325
355
  return flagValue;
326
356
  }
327
357
  return (0, version_js_1.getVersionData)(flagValue, __classPrivateFieldGet(this, _RemoteFeatureFlagController_clientVersion, "f"));
328
- }, _RemoteFeatureFlagController_processRemoteFeatureFlags =
329
- /**
330
- * Resolves raw feature flags into the values that apply to this client and
331
- * user, selecting version and threshold entries and reconciling the
332
- * threshold cache against the flags the server currently serves.
333
- *
334
- * @param remoteFeatureFlags - The unprocessed feature flags.
335
- * @returns The processed flags, the updated threshold cache, and the
336
- * selected threshold group names.
337
- */
338
- async function _RemoteFeatureFlagController_processRemoteFeatureFlags(remoteFeatureFlags) {
358
+ }, _RemoteFeatureFlagController_processRemoteFeatureFlags = async function _RemoteFeatureFlagController_processRemoteFeatureFlags(remoteFeatureFlags) {
339
359
  const processedFlags = {};
340
360
  const metaMetricsId = __classPrivateFieldGet(this, _RemoteFeatureFlagController_getMetaMetricsId, "f").call(this);
341
361
  const thresholdCacheUpdates = {};
342
- const featureFlagThresholdGroups = {};
362
+ const featureFlagThresholdGroupUpdates = {};
343
363
  for (const [remoteFeatureFlagName, remoteFeatureFlagValue,] of Object.entries(remoteFeatureFlags)) {
344
364
  let processedValue = __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_processVersionBasedFlag).call(this, remoteFeatureFlagValue);
345
365
  if (processedValue === null) {
@@ -365,7 +385,7 @@ async function _RemoteFeatureFlagController_processRemoteFeatureFlags(remoteFeat
365
385
  if (explicitMatch) {
366
386
  processedValue = explicitMatch.value;
367
387
  if (explicitMatch.name) {
368
- featureFlagThresholdGroups[remoteFeatureFlagName] =
388
+ featureFlagThresholdGroupUpdates[remoteFeatureFlagName] =
369
389
  explicitMatch.name;
370
390
  }
371
391
  }
@@ -388,7 +408,7 @@ async function _RemoteFeatureFlagController_processRemoteFeatureFlags(remoteFeat
388
408
  if (selectedGroup) {
389
409
  processedValue = selectedGroup.value;
390
410
  if (selectedGroup.name) {
391
- featureFlagThresholdGroups[remoteFeatureFlagName] =
411
+ featureFlagThresholdGroupUpdates[remoteFeatureFlagName] =
392
412
  selectedGroup.name;
393
413
  }
394
414
  }
@@ -396,24 +416,10 @@ async function _RemoteFeatureFlagController_processRemoteFeatureFlags(remoteFeat
396
416
  }
397
417
  processedFlags[remoteFeatureFlagName] = processedValue;
398
418
  }
399
- const thresholdCache = {
400
- ...this.state.thresholdCache,
401
- ...thresholdCacheUpdates,
402
- };
403
- // Drop cached thresholds for flags this user is no longer served.
404
- const currentFlagNames = Object.keys(remoteFeatureFlags);
405
- for (const cacheKey of Object.keys(thresholdCache)) {
406
- const [cachedMetaMetricsId, ...cachedFlagNameParts] = cacheKey.split(':');
407
- const cachedFlagName = cachedFlagNameParts.join(':');
408
- if (cachedMetaMetricsId === metaMetricsId &&
409
- !currentFlagNames.includes(cachedFlagName)) {
410
- delete thresholdCache[cacheKey];
411
- }
412
- }
413
419
  return {
414
420
  processedFlags,
415
- thresholdCache,
416
- featureFlagThresholdGroups,
421
+ thresholdCacheUpdates,
422
+ featureFlagThresholdGroupUpdates,
417
423
  };
418
424
  };
419
425
  //# sourceMappingURL=remote-feature-flag-controller.cjs.map
@@ -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 * 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 super({\n name: controllerName,\n metadata: remoteFeatureFlagControllerMetadata,\n messenger,\n state: {\n ...initialState,\n cacheTimestamp: hasClientVersionChanged\n ? 0\n : initialState.cacheTimestamp,\n },\n });\n\n this.#defaultFeatureFlags = defaultFeatureFlags;\n // Last session's effective flags stand in for the remote layer until\n // `init` re-derives it from the persisted raw flags, or a fetch replaces\n // it. Overrides are layered on top rather than subtracted out, so a remote\n // flag that happens to share an override's value is not lost.\n this.#processedRemoteFeatureFlags = initialState.remoteFeatureFlags;\n this.#fetchInterval = fetchInterval;\n this.#disabled = disabled;\n this.#clientConfigApiService = clientConfigApiService;\n this.#getMetaMetricsId = getMetaMetricsId;\n this.#clientVersion = clientVersion;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Computes effective feature flags with precedence:\n * defaults < processed remote < local overrides.\n *\n * @param options - The layers to merge. Each defaults to the current layer.\n * @param options.processedRemoteFeatureFlags - The processed remote feature\n * flags. Defaults to the currently resolved remote layer.\n * @param options.localOverrides - Local overrides. Defaults to current state\n * overrides.\n * @returns The effective feature flags.\n */\n #getEffectiveFeatureFlags({\n processedRemoteFeatureFlags = this.#processedRemoteFeatureFlags,\n localOverrides = this.state.localOverrides,\n }: {\n processedRemoteFeatureFlags?: FeatureFlags;\n localOverrides?: FeatureFlags;\n } = {}): FeatureFlags {\n return {\n ...this.#defaultFeatureFlags,\n ...processedRemoteFeatureFlags,\n ...localOverrides,\n };\n }\n\n /**\n * Checks if the cached feature flags are expired based on the fetch interval.\n *\n * @returns Whether the cache is expired (`true`) or still valid (`false`).\n */\n #isCacheExpired(): boolean {\n return Date.now() - this.state.cacheTimestamp > this.#fetchInterval;\n }\n\n /**\n * Retrieves the remote feature flags, fetching from the API if necessary.\n * Uses caching to prevent redundant API calls and handles concurrent fetches.\n *\n * @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 * Computes the effective feature flags, re-deriving the remote layer from the\n * raw flags already in state. Threshold selection needs to await a hash and\n * so cannot run in the constructor, which is why this cannot be part of\n * construction. Clients must call this once after constructing the\n * controller.\n *\n * When there are no persisted raw flags, as on a fresh install or for state\n * persisted before raw flags were stored, the previous session's flags stand\n * in for the remote layer so that nothing is lost.\n */\n async init(): Promise<void> {\n const { rawRemoteFeatureFlags } = this.state;\n const hasRawRemoteFeatureFlags =\n rawRemoteFeatureFlags && Object.keys(rawRemoteFeatureFlags).length > 0;\n\n const resolved = hasRawRemoteFeatureFlags\n ? await this.#processRemoteFeatureFlags(rawRemoteFeatureFlags)\n : undefined;\n\n this.#processedRemoteFeatureFlags =\n resolved?.processedFlags ?? this.state.remoteFeatureFlags;\n\n this.update(() => {\n return {\n ...this.state,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags(),\n ...(resolved && {\n thresholdCache: resolved.thresholdCache,\n featureFlagThresholdGroups: resolved.featureFlagThresholdGroups,\n }),\n };\n });\n }\n\n /**\n * Updates the controller's state with new feature flags and resets the cache timestamp.\n *\n * @param remoteFeatureFlags - The new feature flags to cache.\n */\n async #updateCache(remoteFeatureFlags: FeatureFlags): Promise<void> {\n const resolved = await this.#processRemoteFeatureFlags(remoteFeatureFlags);\n\n this.#processedRemoteFeatureFlags = resolved.processedFlags;\n\n // Single state update with all changes batched together\n this.update(() => {\n return {\n ...this.state,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({\n processedRemoteFeatureFlags: resolved.processedFlags,\n }),\n rawRemoteFeatureFlags: remoteFeatureFlags,\n cacheTimestamp: Date.now(),\n thresholdCache: resolved.thresholdCache,\n featureFlagThresholdGroups: resolved.featureFlagThresholdGroups,\n };\n });\n }\n\n /**\n * Processes a version-based feature flag to get the appropriate value for the current client version.\n *\n * @param flagValue - The feature flag value to process\n * @returns The processed value, or null if no version qualifies (skip this flag)\n */\n #processVersionBasedFlag(flagValue: Json): Json | null {\n if (!isVersionFeatureFlag(flagValue)) {\n return flagValue;\n }\n\n return getVersionData(flagValue, this.#clientVersion);\n }\n\n /**\n * Resolves raw feature flags into the values that apply to this client and\n * user, selecting version and threshold entries and reconciling the\n * threshold cache against the flags the server currently serves.\n *\n * @param remoteFeatureFlags - The unprocessed feature flags.\n * @returns The processed flags, the updated threshold cache, and the\n * selected threshold group names.\n */\n async #processRemoteFeatureFlags(remoteFeatureFlags: FeatureFlags): Promise<{\n processedFlags: FeatureFlags;\n thresholdCache: Record<string, number>;\n featureFlagThresholdGroups: Record<string, string>;\n }> {\n const processedFlags: FeatureFlags = {};\n const metaMetricsId = this.#getMetaMetricsId();\n const thresholdCacheUpdates: Record<string, number> = {};\n const featureFlagThresholdGroups: Record<string, string> = {};\n\n for (const [\n remoteFeatureFlagName,\n remoteFeatureFlagValue,\n ] of Object.entries(remoteFeatureFlags)) {\n let processedValue = this.#processVersionBasedFlag(\n remoteFeatureFlagValue,\n );\n if (processedValue === null) {\n continue;\n }\n\n if (Array.isArray(processedValue)) {\n // Validate array has valid threshold items before doing expensive crypto operation\n const hasValidThresholds = processedValue.some(\n isFeatureFlagWithScopeValue,\n );\n\n if (!hasValidThresholds) {\n // Not a threshold array - preserve as-is\n processedFlags[remoteFeatureFlagName] = processedValue;\n continue;\n }\n\n // 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 featureFlagThresholdGroups[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 featureFlagThresholdGroups[remoteFeatureFlagName] =\n selectedGroup.name;\n }\n }\n }\n }\n\n processedFlags[remoteFeatureFlagName] = processedValue;\n }\n\n const thresholdCache = {\n ...this.state.thresholdCache,\n ...thresholdCacheUpdates,\n };\n\n // Drop cached thresholds for flags this user is no longer served.\n const currentFlagNames = Object.keys(remoteFeatureFlags);\n for (const cacheKey of Object.keys(thresholdCache)) {\n const [cachedMetaMetricsId, ...cachedFlagNameParts] = cacheKey.split(':');\n const cachedFlagName = cachedFlagNameParts.join(':');\n if (\n cachedMetaMetricsId === metaMetricsId &&\n !currentFlagNames.includes(cachedFlagName)\n ) {\n delete thresholdCache[cacheKey];\n }\n }\n\n return {\n processedFlags,\n thresholdCache,\n featureFlagThresholdGroups,\n };\n }\n\n /**\n * Enables the controller, allowing it to make network requests.\n */\n enable(): void {\n this.#disabled = false;\n }\n\n /**\n * Disables the controller, preventing it from making network requests.\n */\n disable(): void {\n this.#disabled = true;\n }\n\n /**\n * Sets a local override for a specific feature flag.\n *\n * @param flagName - The name of the feature flag to override.\n * @param value - The override value for the feature flag.\n */\n setFlagOverride(flagName: string, value: Json): void {\n this.update(() => {\n const localOverrides = {\n ...this.state.localOverrides,\n [flagName]: value,\n };\n\n return {\n ...this.state,\n localOverrides,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({ localOverrides }),\n };\n });\n }\n\n /**\n * Clears the local override for a specific feature flag.\n *\n * @param flagName - The name of the feature flag to clear.\n */\n removeFlagOverride(flagName: string): void {\n const newLocalOverrides = { ...this.state.localOverrides };\n delete newLocalOverrides[flagName];\n\n this.update(() => {\n return {\n ...this.state,\n localOverrides: newLocalOverrides,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({\n localOverrides: newLocalOverrides,\n }),\n };\n });\n }\n\n /**\n * Clears all local feature flag overrides.\n */\n clearAllFlagOverrides(): void {\n this.update(() => {\n return {\n ...this.state,\n localOverrides: {},\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({\n localOverrides: {},\n }),\n };\n });\n }\n}\n"]}
1
+ {"version":3,"file":"remote-feature-flag-controller.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"]}
@@ -66,18 +66,6 @@ export declare class RemoteFeatureFlagController extends BaseController<typeof c
66
66
  * @returns A promise that resolves to the current set of feature flags.
67
67
  */
68
68
  updateRemoteFeatureFlags(): Promise<void>;
69
- /**
70
- * Computes the effective feature flags, re-deriving the remote layer from the
71
- * raw flags already in state. Threshold selection needs to await a hash and
72
- * so cannot run in the constructor, which is why this cannot be part of
73
- * construction. Clients must call this once after constructing the
74
- * controller.
75
- *
76
- * When there are no persisted raw flags, as on a fresh install or for state
77
- * persisted before raw flags were stored, the previous session's flags stand
78
- * in for the remote layer so that nothing is lost.
79
- */
80
- init(): Promise<void>;
81
69
  /**
82
70
  * Enables the controller, allowing it to make network requests.
83
71
  */
@@ -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;AAoCD;;;;;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;IAgFD;;;;;OAKG;IACG,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB/C;;;;;;;;;;OAUG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA6L3B;;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;IAepD;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAe1C;;OAEG;IACH,qBAAqB,IAAI,IAAI;CAW9B"}
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"}
@@ -66,18 +66,6 @@ export declare class RemoteFeatureFlagController extends BaseController<typeof c
66
66
  * @returns A promise that resolves to the current set of feature flags.
67
67
  */
68
68
  updateRemoteFeatureFlags(): Promise<void>;
69
- /**
70
- * Computes the effective feature flags, re-deriving the remote layer from the
71
- * raw flags already in state. Threshold selection needs to await a hash and
72
- * so cannot run in the constructor, which is why this cannot be part of
73
- * construction. Clients must call this once after constructing the
74
- * controller.
75
- *
76
- * When there are no persisted raw flags, as on a fresh install or for state
77
- * persisted before raw flags were stored, the previous session's flags stand
78
- * in for the remote layer so that nothing is lost.
79
- */
80
- init(): Promise<void>;
81
69
  /**
82
70
  * Enables the controller, allowing it to make network requests.
83
71
  */
@@ -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;AAoCD;;;;;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;IAgFD;;;;;OAKG;IACG,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB/C;;;;;;;;;;OAUG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA6L3B;;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;IAepD;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAe1C;;OAEG;IACH,qBAAqB,IAAI,IAAI;CAW9B"}
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"}
@@ -104,6 +104,37 @@ function findExplicitIdMatch(entries, normalizedId) {
104
104
  }
105
105
  return undefined;
106
106
  }
107
+ /**
108
+ * Returns a copy of `flags` with `metaMetricsIds` removed from every
109
+ * threshold entry. Used before persisting raw flags to state so that
110
+ * MetaMetrics IDs are never written to state logs or debug snapshots.
111
+ *
112
+ * @param flags - The raw feature flags object from the API.
113
+ * @returns A new object with the same structure but without any
114
+ * `metaMetricsIds` fields inside threshold entry arrays.
115
+ */
116
+ function redactMetaMetricsIds(flags) {
117
+ const result = {};
118
+ for (const [name, value] of Object.entries(flags)) {
119
+ if (!Array.isArray(value)) {
120
+ result[name] = value;
121
+ continue;
122
+ }
123
+ result[name] = value.map((entry) => {
124
+ if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
125
+ return entry;
126
+ }
127
+ const entryRecord = entry;
128
+ if (entryRecord.metaMetricsIds === undefined) {
129
+ return entry;
130
+ }
131
+ const copy = { ...entryRecord };
132
+ delete copy.metaMetricsIds;
133
+ return copy;
134
+ });
135
+ }
136
+ return result;
137
+ }
107
138
  /**
108
139
  * The RemoteFeatureFlagController manages the retrieval and caching of remote feature flags.
109
140
  * It fetches feature flags from a remote API, caches them, and provides methods to access
@@ -135,12 +166,28 @@ export class RemoteFeatureFlagController extends BaseController {
135
166
  };
136
167
  const hasClientVersionChanged = isValidSemVerVersion(prevClientVersion) &&
137
168
  prevClientVersion !== clientVersion;
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
+ }
138
180
  super({
139
181
  name: controllerName,
140
182
  metadata: remoteFeatureFlagControllerMetadata,
141
183
  messenger,
142
184
  state: {
143
185
  ...initialState,
186
+ remoteFeatureFlags: {
187
+ ...defaultFeatureFlags,
188
+ ...processedRemoteFeatureFlags,
189
+ ...localOverrides,
190
+ },
144
191
  cacheTimestamp: hasClientVersionChanged
145
192
  ? 0
146
193
  : initialState.cacheTimestamp,
@@ -154,13 +201,9 @@ export class RemoteFeatureFlagController extends BaseController {
154
201
  _RemoteFeatureFlagController_getMetaMetricsId.set(this, void 0);
155
202
  _RemoteFeatureFlagController_clientVersion.set(this, void 0);
156
203
  _RemoteFeatureFlagController_defaultFeatureFlags.set(this, void 0);
157
- _RemoteFeatureFlagController_processedRemoteFeatureFlags.set(this, void 0);
204
+ _RemoteFeatureFlagController_processedRemoteFeatureFlags.set(this, {});
158
205
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_defaultFeatureFlags, defaultFeatureFlags, "f");
159
- // Last session's effective flags stand in for the remote layer until
160
- // `init` re-derives it from the persisted raw flags, or a fetch replaces
161
- // it. Overrides are layered on top rather than subtracted out, so a remote
162
- // flag that happens to share an override's value is not lost.
163
- __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, initialState.remoteFeatureFlags, "f");
206
+ __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, processedRemoteFeatureFlags, "f");
164
207
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_fetchInterval, fetchInterval, "f");
165
208
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_disabled, disabled, "f");
166
209
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_clientConfigApiService, clientConfigApiService, "f");
@@ -192,35 +235,6 @@ export class RemoteFeatureFlagController extends BaseController {
192
235
  }
193
236
  await __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_updateCache).call(this, serverData.remoteFeatureFlags);
194
237
  }
195
- /**
196
- * Computes the effective feature flags, re-deriving the remote layer from the
197
- * raw flags already in state. Threshold selection needs to await a hash and
198
- * so cannot run in the constructor, which is why this cannot be part of
199
- * construction. Clients must call this once after constructing the
200
- * controller.
201
- *
202
- * When there are no persisted raw flags, as on a fresh install or for state
203
- * persisted before raw flags were stored, the previous session's flags stand
204
- * in for the remote layer so that nothing is lost.
205
- */
206
- async init() {
207
- const { rawRemoteFeatureFlags } = this.state;
208
- const hasRawRemoteFeatureFlags = rawRemoteFeatureFlags && Object.keys(rawRemoteFeatureFlags).length > 0;
209
- const resolved = hasRawRemoteFeatureFlags
210
- ? await __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_processRemoteFeatureFlags).call(this, rawRemoteFeatureFlags)
211
- : undefined;
212
- __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, resolved?.processedFlags ?? this.state.remoteFeatureFlags, "f");
213
- this.update(() => {
214
- return {
215
- ...this.state,
216
- remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this),
217
- ...(resolved && {
218
- thresholdCache: resolved.thresholdCache,
219
- featureFlagThresholdGroups: resolved.featureFlagThresholdGroups,
220
- }),
221
- };
222
- });
223
- }
224
238
  /**
225
239
  * Enables the controller, allowing it to make network requests.
226
240
  */
@@ -248,7 +262,7 @@ export class RemoteFeatureFlagController extends BaseController {
248
262
  return {
249
263
  ...this.state,
250
264
  localOverrides,
251
- remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, { localOverrides }),
265
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), localOverrides),
252
266
  };
253
267
  });
254
268
  }
@@ -264,9 +278,7 @@ export class RemoteFeatureFlagController extends BaseController {
264
278
  return {
265
279
  ...this.state,
266
280
  localOverrides: newLocalOverrides,
267
- remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, {
268
- localOverrides: newLocalOverrides,
269
- }),
281
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), newLocalOverrides),
270
282
  };
271
283
  });
272
284
  }
@@ -278,17 +290,15 @@ export class RemoteFeatureFlagController extends BaseController {
278
290
  return {
279
291
  ...this.state,
280
292
  localOverrides: {},
281
- remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, {
282
- localOverrides: {},
283
- }),
293
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), {}),
284
294
  };
285
295
  });
286
296
  }
287
297
  }
288
- _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({ processedRemoteFeatureFlags = __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), localOverrides = this.state.localOverrides, } = {}) {
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 ?? {}) {
289
299
  return {
290
300
  ...__classPrivateFieldGet(this, _RemoteFeatureFlagController_defaultFeatureFlags, "f"),
291
- ...processedRemoteFeatureFlags,
301
+ ...processedRemote,
292
302
  ...localOverrides,
293
303
  };
294
304
  }, _RemoteFeatureFlagController_isCacheExpired = function _RemoteFeatureFlagController_isCacheExpired() {
@@ -300,19 +310,39 @@ _RemoteFeatureFlagController_fetchInterval = new WeakMap(), _RemoteFeatureFlagCo
300
310
  * @param remoteFeatureFlags - The new feature flags to cache.
301
311
  */
302
312
  async function _RemoteFeatureFlagController_updateCache(remoteFeatureFlags) {
303
- const resolved = await __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_processRemoteFeatureFlags).call(this, remoteFeatureFlags);
304
- __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, resolved.processedFlags, "f");
313
+ const { processedFlags, thresholdCacheUpdates, featureFlagThresholdGroupUpdates, } = await __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_processRemoteFeatureFlags).call(this, remoteFeatureFlags);
314
+ const metaMetricsId = __classPrivateFieldGet(this, _RemoteFeatureFlagController_getMetaMetricsId, "f").call(this);
315
+ const currentFlagNames = Object.keys(remoteFeatureFlags);
316
+ // Build updated threshold cache
317
+ const updatedThresholdCache = { ...(this.state.thresholdCache ?? {}) };
318
+ // Apply new thresholds
319
+ for (const [cacheKey, threshold] of Object.entries(thresholdCacheUpdates)) {
320
+ updatedThresholdCache[cacheKey] = threshold;
321
+ }
322
+ // Clean up stale entries
323
+ for (const cacheKey of Object.keys(updatedThresholdCache)) {
324
+ const [cachedMetaMetricsId, ...cachedFlagNameParts] = cacheKey.split(':');
325
+ const cachedFlagName = cachedFlagNameParts.join(':');
326
+ if (cachedMetaMetricsId === metaMetricsId &&
327
+ !currentFlagNames.includes(cachedFlagName)) {
328
+ delete updatedThresholdCache[cacheKey];
329
+ }
330
+ }
331
+ // Strip metaMetricsIds from processed flags so they never appear in
332
+ // remoteFeatureFlags state or #processedRemoteFeatureFlags. Arrays that
333
+ // were preserved as-is (e.g. when metaMetricsId is missing) would
334
+ // otherwise leak explicit-targeting IDs into diagnostics.
335
+ const redactedProcessedFlags = redactMetaMetricsIds(processedFlags);
305
336
  // Single state update with all changes batched together
337
+ __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, redactedProcessedFlags, "f");
306
338
  this.update(() => {
307
339
  return {
308
340
  ...this.state,
309
- remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, {
310
- processedRemoteFeatureFlags: resolved.processedFlags,
311
- }),
312
- rawRemoteFeatureFlags: remoteFeatureFlags,
341
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, redactedProcessedFlags),
342
+ rawRemoteFeatureFlags: redactMetaMetricsIds(remoteFeatureFlags),
313
343
  cacheTimestamp: Date.now(),
314
- thresholdCache: resolved.thresholdCache,
315
- featureFlagThresholdGroups: resolved.featureFlagThresholdGroups,
344
+ thresholdCache: updatedThresholdCache,
345
+ featureFlagThresholdGroups: featureFlagThresholdGroupUpdates,
316
346
  };
317
347
  });
318
348
  }, _RemoteFeatureFlagController_processVersionBasedFlag = function _RemoteFeatureFlagController_processVersionBasedFlag(flagValue) {
@@ -320,21 +350,11 @@ async function _RemoteFeatureFlagController_updateCache(remoteFeatureFlags) {
320
350
  return flagValue;
321
351
  }
322
352
  return getVersionData(flagValue, __classPrivateFieldGet(this, _RemoteFeatureFlagController_clientVersion, "f"));
323
- }, _RemoteFeatureFlagController_processRemoteFeatureFlags =
324
- /**
325
- * Resolves raw feature flags into the values that apply to this client and
326
- * user, selecting version and threshold entries and reconciling the
327
- * threshold cache against the flags the server currently serves.
328
- *
329
- * @param remoteFeatureFlags - The unprocessed feature flags.
330
- * @returns The processed flags, the updated threshold cache, and the
331
- * selected threshold group names.
332
- */
333
- async function _RemoteFeatureFlagController_processRemoteFeatureFlags(remoteFeatureFlags) {
353
+ }, _RemoteFeatureFlagController_processRemoteFeatureFlags = async function _RemoteFeatureFlagController_processRemoteFeatureFlags(remoteFeatureFlags) {
334
354
  const processedFlags = {};
335
355
  const metaMetricsId = __classPrivateFieldGet(this, _RemoteFeatureFlagController_getMetaMetricsId, "f").call(this);
336
356
  const thresholdCacheUpdates = {};
337
- const featureFlagThresholdGroups = {};
357
+ const featureFlagThresholdGroupUpdates = {};
338
358
  for (const [remoteFeatureFlagName, remoteFeatureFlagValue,] of Object.entries(remoteFeatureFlags)) {
339
359
  let processedValue = __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_processVersionBasedFlag).call(this, remoteFeatureFlagValue);
340
360
  if (processedValue === null) {
@@ -360,7 +380,7 @@ async function _RemoteFeatureFlagController_processRemoteFeatureFlags(remoteFeat
360
380
  if (explicitMatch) {
361
381
  processedValue = explicitMatch.value;
362
382
  if (explicitMatch.name) {
363
- featureFlagThresholdGroups[remoteFeatureFlagName] =
383
+ featureFlagThresholdGroupUpdates[remoteFeatureFlagName] =
364
384
  explicitMatch.name;
365
385
  }
366
386
  }
@@ -383,7 +403,7 @@ async function _RemoteFeatureFlagController_processRemoteFeatureFlags(remoteFeat
383
403
  if (selectedGroup) {
384
404
  processedValue = selectedGroup.value;
385
405
  if (selectedGroup.name) {
386
- featureFlagThresholdGroups[remoteFeatureFlagName] =
406
+ featureFlagThresholdGroupUpdates[remoteFeatureFlagName] =
387
407
  selectedGroup.name;
388
408
  }
389
409
  }
@@ -391,24 +411,10 @@ async function _RemoteFeatureFlagController_processRemoteFeatureFlags(remoteFeat
391
411
  }
392
412
  processedFlags[remoteFeatureFlagName] = processedValue;
393
413
  }
394
- const thresholdCache = {
395
- ...this.state.thresholdCache,
396
- ...thresholdCacheUpdates,
397
- };
398
- // Drop cached thresholds for flags this user is no longer served.
399
- const currentFlagNames = Object.keys(remoteFeatureFlags);
400
- for (const cacheKey of Object.keys(thresholdCache)) {
401
- const [cachedMetaMetricsId, ...cachedFlagNameParts] = cacheKey.split(':');
402
- const cachedFlagName = cachedFlagNameParts.join(':');
403
- if (cachedMetaMetricsId === metaMetricsId &&
404
- !currentFlagNames.includes(cachedFlagName)) {
405
- delete thresholdCache[cacheKey];
406
- }
407
- }
408
414
  return {
409
415
  processedFlags,
410
- thresholdCache,
411
- featureFlagThresholdGroups,
416
+ thresholdCacheUpdates,
417
+ featureFlagThresholdGroupUpdates,
412
418
  };
413
419
  };
414
420
  //# sourceMappingURL=remote-feature-flag-controller.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"remote-feature-flag-controller.mjs","sourceRoot":"","sources":["../src/remote-feature-flag-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BaseController,\n ControllerGetStateAction,\n} from '@metamask/base-controller';\nimport type { ControllerStateChangeEvent } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport { isValidSemVerVersion } from '@metamask/utils';\nimport type { Json, SemVerVersion } from '@metamask/utils';\n\nimport type { AbstractClientConfigApiService } from './client-config-api-service/abstract-client-config-api-service.js';\nimport type { RemoteFeatureFlagControllerMethodActions } from './remote-feature-flag-controller-method-action-types.js';\nimport type {\n FeatureFlags,\n ServiceResponse,\n FeatureFlagScopeValue,\n} from './remote-feature-flag-controller-types.js';\nimport {\n calculateThresholdForFlag,\n isFeatureFlagWithScopeValue,\n} from './utils/user-segmentation-utils.js';\nimport { isVersionFeatureFlag, getVersionData } from './utils/version.js';\n\n// === GENERAL ===\n\nexport const controllerName = 'RemoteFeatureFlagController';\nexport const DEFAULT_CACHE_DURATION = 24 * 60 * 60 * 1000; // 1 day\n\n// === STATE ===\n\nexport type RemoteFeatureFlagControllerState = {\n remoteFeatureFlags: FeatureFlags;\n localOverrides?: FeatureFlags;\n rawRemoteFeatureFlags?: FeatureFlags;\n cacheTimestamp: number;\n thresholdCache?: Record<string, number>;\n featureFlagThresholdGroups?: Record<string, string>;\n};\n\nconst remoteFeatureFlagControllerMetadata = {\n remoteFeatureFlags: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n localOverrides: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n rawRemoteFeatureFlags: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: false,\n },\n cacheTimestamp: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: false,\n },\n thresholdCache: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: false,\n },\n featureFlagThresholdGroups: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: false,\n },\n};\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'clearAllFlagOverrides',\n 'disable',\n 'enable',\n 'removeFlagOverride',\n 'setFlagOverride',\n 'updateRemoteFeatureFlags',\n] as const;\n\nexport type RemoteFeatureFlagControllerGetStateAction =\n ControllerGetStateAction<\n typeof controllerName,\n RemoteFeatureFlagControllerState\n >;\n\nexport type RemoteFeatureFlagControllerActions =\n | RemoteFeatureFlagControllerGetStateAction\n | RemoteFeatureFlagControllerMethodActions;\n\nexport type RemoteFeatureFlagControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n RemoteFeatureFlagControllerState\n >;\n\nexport type RemoteFeatureFlagControllerEvents =\n RemoteFeatureFlagControllerStateChangeEvent;\n\nexport type RemoteFeatureFlagControllerMessenger = Messenger<\n typeof controllerName,\n RemoteFeatureFlagControllerActions,\n RemoteFeatureFlagControllerEvents\n>;\n\n/**\n * Returns the default state for the RemoteFeatureFlagController.\n *\n * @returns The default controller state.\n */\nexport function getDefaultRemoteFeatureFlagControllerState(): RemoteFeatureFlagControllerState {\n return {\n remoteFeatureFlags: {},\n localOverrides: {},\n rawRemoteFeatureFlags: {},\n cacheTimestamp: 0,\n };\n}\n\n/**\n * Searches threshold entries for an explicit MetaMetrics ID match.\n * Returns the first entry whose `metaMetricsIds` list contains the given\n * normalized ID. Entries with malformed `metaMetricsIds` (not an array) are\n * skipped without throwing.\n *\n * @param entries - The array of raw threshold entries for a feature flag.\n * @param normalizedId - The current user's MetaMetrics ID, already trimmed and\n * lower-cased.\n * @returns The first matching entry, or `undefined` if none match.\n */\nfunction findExplicitIdMatch(\n entries: Json[],\n normalizedId: string,\n): FeatureFlagScopeValue | undefined {\n for (const entry of entries) {\n if (!isFeatureFlagWithScopeValue(entry)) {\n continue;\n }\n const { metaMetricsIds } = entry;\n if (!Array.isArray(metaMetricsIds)) {\n continue;\n }\n const hasMatch = metaMetricsIds.some(\n (id) =>\n typeof id === 'string' && id.trim().toLowerCase() === normalizedId,\n );\n if (hasMatch) {\n return entry;\n }\n }\n return undefined;\n}\n\n/**\n * The RemoteFeatureFlagController manages the retrieval and caching of remote feature flags.\n * It fetches feature flags from a remote API, caches them, and provides methods to access\n * and manage these flags. The controller ensures that feature flags are refreshed based on\n * a specified interval and handles cases where the controller is disabled or the network is unavailable.\n */\nexport class RemoteFeatureFlagController extends BaseController<\n typeof controllerName,\n RemoteFeatureFlagControllerState,\n RemoteFeatureFlagControllerMessenger\n> {\n readonly #fetchInterval: number;\n\n #disabled: boolean;\n\n readonly #clientConfigApiService: AbstractClientConfigApiService;\n\n #inProgressFlagUpdate?: Promise<ServiceResponse>;\n\n readonly #getMetaMetricsId: () => string;\n\n readonly #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 super({\n name: controllerName,\n metadata: remoteFeatureFlagControllerMetadata,\n messenger,\n state: {\n ...initialState,\n cacheTimestamp: hasClientVersionChanged\n ? 0\n : initialState.cacheTimestamp,\n },\n });\n\n this.#defaultFeatureFlags = defaultFeatureFlags;\n // Last session's effective flags stand in for the remote layer until\n // `init` re-derives it from the persisted raw flags, or a fetch replaces\n // it. Overrides are layered on top rather than subtracted out, so a remote\n // flag that happens to share an override's value is not lost.\n this.#processedRemoteFeatureFlags = initialState.remoteFeatureFlags;\n this.#fetchInterval = fetchInterval;\n this.#disabled = disabled;\n this.#clientConfigApiService = clientConfigApiService;\n this.#getMetaMetricsId = getMetaMetricsId;\n this.#clientVersion = clientVersion;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Computes effective feature flags with precedence:\n * defaults < processed remote < local overrides.\n *\n * @param options - The layers to merge. Each defaults to the current layer.\n * @param options.processedRemoteFeatureFlags - The processed remote feature\n * flags. Defaults to the currently resolved remote layer.\n * @param options.localOverrides - Local overrides. Defaults to current state\n * overrides.\n * @returns The effective feature flags.\n */\n #getEffectiveFeatureFlags({\n processedRemoteFeatureFlags = this.#processedRemoteFeatureFlags,\n localOverrides = this.state.localOverrides,\n }: {\n processedRemoteFeatureFlags?: FeatureFlags;\n localOverrides?: FeatureFlags;\n } = {}): FeatureFlags {\n return {\n ...this.#defaultFeatureFlags,\n ...processedRemoteFeatureFlags,\n ...localOverrides,\n };\n }\n\n /**\n * Checks if the cached feature flags are expired based on the fetch interval.\n *\n * @returns Whether the cache is expired (`true`) or still valid (`false`).\n */\n #isCacheExpired(): boolean {\n return Date.now() - this.state.cacheTimestamp > this.#fetchInterval;\n }\n\n /**\n * Retrieves the remote feature flags, fetching from the API if necessary.\n * Uses caching to prevent redundant API calls and handles concurrent fetches.\n *\n * @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 * Computes the effective feature flags, re-deriving the remote layer from the\n * raw flags already in state. Threshold selection needs to await a hash and\n * so cannot run in the constructor, which is why this cannot be part of\n * construction. Clients must call this once after constructing the\n * controller.\n *\n * When there are no persisted raw flags, as on a fresh install or for state\n * persisted before raw flags were stored, the previous session's flags stand\n * in for the remote layer so that nothing is lost.\n */\n async init(): Promise<void> {\n const { rawRemoteFeatureFlags } = this.state;\n const hasRawRemoteFeatureFlags =\n rawRemoteFeatureFlags && Object.keys(rawRemoteFeatureFlags).length > 0;\n\n const resolved = hasRawRemoteFeatureFlags\n ? await this.#processRemoteFeatureFlags(rawRemoteFeatureFlags)\n : undefined;\n\n this.#processedRemoteFeatureFlags =\n resolved?.processedFlags ?? this.state.remoteFeatureFlags;\n\n this.update(() => {\n return {\n ...this.state,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags(),\n ...(resolved && {\n thresholdCache: resolved.thresholdCache,\n featureFlagThresholdGroups: resolved.featureFlagThresholdGroups,\n }),\n };\n });\n }\n\n /**\n * Updates the controller's state with new feature flags and resets the cache timestamp.\n *\n * @param remoteFeatureFlags - The new feature flags to cache.\n */\n async #updateCache(remoteFeatureFlags: FeatureFlags): Promise<void> {\n const resolved = await this.#processRemoteFeatureFlags(remoteFeatureFlags);\n\n this.#processedRemoteFeatureFlags = resolved.processedFlags;\n\n // Single state update with all changes batched together\n this.update(() => {\n return {\n ...this.state,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({\n processedRemoteFeatureFlags: resolved.processedFlags,\n }),\n rawRemoteFeatureFlags: remoteFeatureFlags,\n cacheTimestamp: Date.now(),\n thresholdCache: resolved.thresholdCache,\n featureFlagThresholdGroups: resolved.featureFlagThresholdGroups,\n };\n });\n }\n\n /**\n * Processes a version-based feature flag to get the appropriate value for the current client version.\n *\n * @param flagValue - The feature flag value to process\n * @returns The processed value, or null if no version qualifies (skip this flag)\n */\n #processVersionBasedFlag(flagValue: Json): Json | null {\n if (!isVersionFeatureFlag(flagValue)) {\n return flagValue;\n }\n\n return getVersionData(flagValue, this.#clientVersion);\n }\n\n /**\n * Resolves raw feature flags into the values that apply to this client and\n * user, selecting version and threshold entries and reconciling the\n * threshold cache against the flags the server currently serves.\n *\n * @param remoteFeatureFlags - The unprocessed feature flags.\n * @returns The processed flags, the updated threshold cache, and the\n * selected threshold group names.\n */\n async #processRemoteFeatureFlags(remoteFeatureFlags: FeatureFlags): Promise<{\n processedFlags: FeatureFlags;\n thresholdCache: Record<string, number>;\n featureFlagThresholdGroups: Record<string, string>;\n }> {\n const processedFlags: FeatureFlags = {};\n const metaMetricsId = this.#getMetaMetricsId();\n const thresholdCacheUpdates: Record<string, number> = {};\n const featureFlagThresholdGroups: Record<string, string> = {};\n\n for (const [\n remoteFeatureFlagName,\n remoteFeatureFlagValue,\n ] of Object.entries(remoteFeatureFlags)) {\n let processedValue = this.#processVersionBasedFlag(\n remoteFeatureFlagValue,\n );\n if (processedValue === null) {\n continue;\n }\n\n if (Array.isArray(processedValue)) {\n // Validate array has valid threshold items before doing expensive crypto operation\n const hasValidThresholds = processedValue.some(\n isFeatureFlagWithScopeValue,\n );\n\n if (!hasValidThresholds) {\n // Not a threshold array - preserve as-is\n processedFlags[remoteFeatureFlagName] = processedValue;\n continue;\n }\n\n // 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 featureFlagThresholdGroups[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 featureFlagThresholdGroups[remoteFeatureFlagName] =\n selectedGroup.name;\n }\n }\n }\n }\n\n processedFlags[remoteFeatureFlagName] = processedValue;\n }\n\n const thresholdCache = {\n ...this.state.thresholdCache,\n ...thresholdCacheUpdates,\n };\n\n // Drop cached thresholds for flags this user is no longer served.\n const currentFlagNames = Object.keys(remoteFeatureFlags);\n for (const cacheKey of Object.keys(thresholdCache)) {\n const [cachedMetaMetricsId, ...cachedFlagNameParts] = cacheKey.split(':');\n const cachedFlagName = cachedFlagNameParts.join(':');\n if (\n cachedMetaMetricsId === metaMetricsId &&\n !currentFlagNames.includes(cachedFlagName)\n ) {\n delete thresholdCache[cacheKey];\n }\n }\n\n return {\n processedFlags,\n thresholdCache,\n featureFlagThresholdGroups,\n };\n }\n\n /**\n * Enables the controller, allowing it to make network requests.\n */\n enable(): void {\n this.#disabled = false;\n }\n\n /**\n * Disables the controller, preventing it from making network requests.\n */\n disable(): void {\n this.#disabled = true;\n }\n\n /**\n * Sets a local override for a specific feature flag.\n *\n * @param flagName - The name of the feature flag to override.\n * @param value - The override value for the feature flag.\n */\n setFlagOverride(flagName: string, value: Json): void {\n this.update(() => {\n const localOverrides = {\n ...this.state.localOverrides,\n [flagName]: value,\n };\n\n return {\n ...this.state,\n localOverrides,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({ localOverrides }),\n };\n });\n }\n\n /**\n * Clears the local override for a specific feature flag.\n *\n * @param flagName - The name of the feature flag to clear.\n */\n removeFlagOverride(flagName: string): void {\n const newLocalOverrides = { ...this.state.localOverrides };\n delete newLocalOverrides[flagName];\n\n this.update(() => {\n return {\n ...this.state,\n localOverrides: newLocalOverrides,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({\n localOverrides: newLocalOverrides,\n }),\n };\n });\n }\n\n /**\n * Clears all local feature flag overrides.\n */\n clearAllFlagOverrides(): void {\n this.update(() => {\n return {\n ...this.state,\n localOverrides: {},\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({\n localOverrides: {},\n }),\n };\n });\n }\n}\n"]}
1
+ {"version":3,"file":"remote-feature-flag-controller.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-57cd9b4fb",
3
+ "version": "5.0.0-preview-d21e2aaf9",
4
4
  "description": "The RemoteFeatureFlagController manages the retrieval and caching of remote feature flags",
5
5
  "keywords": [
6
6
  "Ethereum",