@metamask-previews/remote-feature-flag-controller 5.0.0-preview-6b4bcdab3 → 5.0.0-preview-0b5bfc2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"remote-feature-flag-controller.d.mts","sourceRoot":"","sources":["../src/remote-feature-flag-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,wBAAwB,EACzB,kCAAkC;AACnC,OAAO,KAAK,EAAE,0BAA0B,EAAE,kCAAkC;AAC5E,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAErD,OAAO,KAAK,EAAE,IAAI,EAAiB,wBAAwB;AAE3D,OAAO,KAAK,EAAE,8BAA8B,EAAE,2EAA0E;AACxH,OAAO,KAAK,EAAE,wCAAwC,EAAE,iEAAgE;AACxH,OAAO,KAAK,EACV,YAAY,EAGb,mDAAkD;AASnD,eAAO,MAAM,cAAc,gCAAgC,CAAC;AAC5D,eAAO,MAAM,sBAAsB,QAAsB,CAAC;AAI1D,MAAM,MAAM,gCAAgC,GAAG;IAC7C,kBAAkB,EAAE,YAAY,CAAC;IACjC,cAAc,CAAC,EAAE,YAAY,CAAC;IAC9B,qBAAqB,CAAC,EAAE,YAAY,CAAC;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,0BAA0B,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrD,CAAC;AAoDF,MAAM,MAAM,yCAAyC,GACnD,wBAAwB,CACtB,OAAO,cAAc,EACrB,gCAAgC,CACjC,CAAC;AAEJ,MAAM,MAAM,kCAAkC,GAC1C,yCAAyC,GACzC,wCAAwC,CAAC;AAE7C,MAAM,MAAM,2CAA2C,GACrD,0BAA0B,CACxB,OAAO,cAAc,EACrB,gCAAgC,CACjC,CAAC;AAEJ,MAAM,MAAM,iCAAiC,GAC3C,2CAA2C,CAAC;AAE9C,MAAM,MAAM,oCAAoC,GAAG,SAAS,CAC1D,OAAO,cAAc,EACrB,kCAAkC,EAClC,iCAAiC,CAClC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,0CAA0C,IAAI,gCAAgC,CAO7F;AAoED;;;;;GAKG;AACH,qBAAa,2BAA4B,SAAQ,cAAc,CAC7D,OAAO,cAAc,EACrB,gCAAgC,EAChC,oCAAoC,CACrC;;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"}
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;;IAqBC;;;;;;;;;;;;;;;OAeG;gBACS,EACV,SAAS,EACT,KAAK,EACL,sBAAsB,EACtB,aAAsC,EACtC,QAAgB,EAChB,gBAAgB,EAChB,qBAAqB,EACrB,gBAAqB,EACrB,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,qBAAqB,EAAE,MAAM,MAAM,CAAC;QACpC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACrC,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;IAkFD;;;;;OAKG;IACG,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB/C;;;;;;;;;;OAUG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgN3B;;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"}
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _RemoteFeatureFlagController_instances, _RemoteFeatureFlagController_fetchInterval, _RemoteFeatureFlagController_disabled, _RemoteFeatureFlagController_clientConfigApiService, _RemoteFeatureFlagController_inProgressFlagUpdate, _RemoteFeatureFlagController_getMetaMetricsId, _RemoteFeatureFlagController_clientVersion, _RemoteFeatureFlagController_defaultFeatureFlags, _RemoteFeatureFlagController_processedRemoteFeatureFlags, _RemoteFeatureFlagController_getEffectiveFeatureFlags, _RemoteFeatureFlagController_isCacheExpired, _RemoteFeatureFlagController_updateCache, _RemoteFeatureFlagController_processVersionBasedFlag, _RemoteFeatureFlagController_processRemoteFeatureFlags;
12
+ var _RemoteFeatureFlagController_instances, _RemoteFeatureFlagController_fetchInterval, _RemoteFeatureFlagController_disabled, _RemoteFeatureFlagController_clientConfigApiService, _RemoteFeatureFlagController_inProgressFlagUpdate, _RemoteFeatureFlagController_getMetaMetricsId, _RemoteFeatureFlagController_getCanonicalProfileId, _RemoteFeatureFlagController_metaMetricsFlags, _RemoteFeatureFlagController_clientVersion, _RemoteFeatureFlagController_defaultFeatureFlags, _RemoteFeatureFlagController_processedRemoteFeatureFlags, _RemoteFeatureFlagController_getEffectiveFeatureFlags, _RemoteFeatureFlagController_isCacheExpired, _RemoteFeatureFlagController_updateCache, _RemoteFeatureFlagController_processVersionBasedFlag, _RemoteFeatureFlagController_getSegmentationId, _RemoteFeatureFlagController_processRemoteFeatureFlags;
13
13
  import { BaseController } from "@metamask/base-controller";
14
14
  import { isValidSemVerVersion } from "@metamask/utils";
15
15
  import { calculateThresholdForFlag, isFeatureFlagWithScopeValue } from "./utils/user-segmentation-utils.mjs";
@@ -104,37 +104,6 @@ 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
- }
138
107
  /**
139
108
  * The RemoteFeatureFlagController manages the retrieval and caching of remote feature flags.
140
109
  * It fetches feature flags from a remote API, caches them, and provides methods to access
@@ -152,11 +121,13 @@ export class RemoteFeatureFlagController extends BaseController {
152
121
  * @param options.fetchInterval - The interval in milliseconds before cached flags expire. Defaults to 1 day.
153
122
  * @param options.disabled - Determines if the controller should be disabled initially. Defaults to false.
154
123
  * @param options.getMetaMetricsId - Returns metaMetricsId.
124
+ * @param options.getCanonicalProfileId - Returns the canonical profile identifier used for threshold flags by default. Must return an empty string, rather than throwing, when the identifier is unavailable.
125
+ * @param options.metaMetricsFlags - Names of feature flags that should use MetaMetrics ID for threshold assignment.
155
126
  * @param options.clientVersion - The current client version for version-based feature flag filtering. Must be a valid 3-part SemVer version string.
156
127
  * @param options.prevClientVersion - The previous client version for feature flag cache invalidation.
157
128
  * @param options.defaultFeatureFlags - Client-side default feature flags used as the lowest-precedence layer under processed remote flags and local overrides. Not persisted.
158
129
  */
159
- constructor({ messenger, state, clientConfigApiService, fetchInterval = DEFAULT_CACHE_DURATION, disabled = false, getMetaMetricsId, clientVersion, prevClientVersion, defaultFeatureFlags = {}, }) {
130
+ constructor({ messenger, state, clientConfigApiService, fetchInterval = DEFAULT_CACHE_DURATION, disabled = false, getMetaMetricsId, getCanonicalProfileId, metaMetricsFlags = [], clientVersion, prevClientVersion, defaultFeatureFlags = {}, }) {
160
131
  if (!isValidSemVerVersion(clientVersion)) {
161
132
  throw new Error(`Invalid clientVersion: "${clientVersion}". Must be a valid 3-part SemVer version string`);
162
133
  }
@@ -166,28 +137,12 @@ export class RemoteFeatureFlagController extends BaseController {
166
137
  };
167
138
  const hasClientVersionChanged = isValidSemVerVersion(prevClientVersion) &&
168
139
  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
- }
180
140
  super({
181
141
  name: controllerName,
182
142
  metadata: remoteFeatureFlagControllerMetadata,
183
143
  messenger,
184
144
  state: {
185
145
  ...initialState,
186
- remoteFeatureFlags: {
187
- ...defaultFeatureFlags,
188
- ...processedRemoteFeatureFlags,
189
- ...localOverrides,
190
- },
191
146
  cacheTimestamp: hasClientVersionChanged
192
147
  ? 0
193
148
  : initialState.cacheTimestamp,
@@ -199,15 +154,23 @@ export class RemoteFeatureFlagController extends BaseController {
199
154
  _RemoteFeatureFlagController_clientConfigApiService.set(this, void 0);
200
155
  _RemoteFeatureFlagController_inProgressFlagUpdate.set(this, void 0);
201
156
  _RemoteFeatureFlagController_getMetaMetricsId.set(this, void 0);
157
+ _RemoteFeatureFlagController_getCanonicalProfileId.set(this, void 0);
158
+ _RemoteFeatureFlagController_metaMetricsFlags.set(this, void 0);
202
159
  _RemoteFeatureFlagController_clientVersion.set(this, void 0);
203
160
  _RemoteFeatureFlagController_defaultFeatureFlags.set(this, void 0);
204
- _RemoteFeatureFlagController_processedRemoteFeatureFlags.set(this, {});
161
+ _RemoteFeatureFlagController_processedRemoteFeatureFlags.set(this, void 0);
205
162
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_defaultFeatureFlags, defaultFeatureFlags, "f");
206
- __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, processedRemoteFeatureFlags, "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");
207
168
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_fetchInterval, fetchInterval, "f");
208
169
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_disabled, disabled, "f");
209
170
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_clientConfigApiService, clientConfigApiService, "f");
210
171
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_getMetaMetricsId, getMetaMetricsId, "f");
172
+ __classPrivateFieldSet(this, _RemoteFeatureFlagController_getCanonicalProfileId, getCanonicalProfileId, "f");
173
+ __classPrivateFieldSet(this, _RemoteFeatureFlagController_metaMetricsFlags, new Set(metaMetricsFlags), "f");
211
174
  __classPrivateFieldSet(this, _RemoteFeatureFlagController_clientVersion, clientVersion, "f");
212
175
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
213
176
  }
@@ -235,6 +198,35 @@ export class RemoteFeatureFlagController extends BaseController {
235
198
  }
236
199
  await __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_updateCache).call(this, serverData.remoteFeatureFlags);
237
200
  }
201
+ /**
202
+ * Computes the effective feature flags, re-deriving the remote layer from the
203
+ * raw flags already in state. Threshold selection needs to await a hash and
204
+ * so cannot run in the constructor, which is why this cannot be part of
205
+ * construction. Clients must call this once after constructing the
206
+ * controller.
207
+ *
208
+ * When there are no persisted raw flags, as on a fresh install or for state
209
+ * persisted before raw flags were stored, the previous session's flags stand
210
+ * in for the remote layer so that nothing is lost.
211
+ */
212
+ async init() {
213
+ const { rawRemoteFeatureFlags } = this.state;
214
+ const hasRawRemoteFeatureFlags = rawRemoteFeatureFlags && Object.keys(rawRemoteFeatureFlags).length > 0;
215
+ const resolved = hasRawRemoteFeatureFlags
216
+ ? await __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_processRemoteFeatureFlags).call(this, rawRemoteFeatureFlags)
217
+ : undefined;
218
+ __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, resolved?.processedFlags ?? this.state.remoteFeatureFlags, "f");
219
+ this.update(() => {
220
+ return {
221
+ ...this.state,
222
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this),
223
+ ...(resolved && {
224
+ thresholdCache: resolved.thresholdCache,
225
+ featureFlagThresholdGroups: resolved.featureFlagThresholdGroups,
226
+ }),
227
+ };
228
+ });
229
+ }
238
230
  /**
239
231
  * Enables the controller, allowing it to make network requests.
240
232
  */
@@ -262,7 +254,7 @@ export class RemoteFeatureFlagController extends BaseController {
262
254
  return {
263
255
  ...this.state,
264
256
  localOverrides,
265
- remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), localOverrides),
257
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, { localOverrides }),
266
258
  };
267
259
  });
268
260
  }
@@ -278,7 +270,9 @@ export class RemoteFeatureFlagController extends BaseController {
278
270
  return {
279
271
  ...this.state,
280
272
  localOverrides: newLocalOverrides,
281
- remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), newLocalOverrides),
273
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, {
274
+ localOverrides: newLocalOverrides,
275
+ }),
282
276
  };
283
277
  });
284
278
  }
@@ -290,15 +284,17 @@ export class RemoteFeatureFlagController extends BaseController {
290
284
  return {
291
285
  ...this.state,
292
286
  localOverrides: {},
293
- remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, __classPrivateFieldGet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, "f"), {}),
287
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, {
288
+ localOverrides: {},
289
+ }),
294
290
  };
295
291
  });
296
292
  }
297
293
  }
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 ?? {}) {
294
+ _RemoteFeatureFlagController_fetchInterval = new WeakMap(), _RemoteFeatureFlagController_disabled = new WeakMap(), _RemoteFeatureFlagController_clientConfigApiService = new WeakMap(), _RemoteFeatureFlagController_inProgressFlagUpdate = new WeakMap(), _RemoteFeatureFlagController_getMetaMetricsId = new WeakMap(), _RemoteFeatureFlagController_getCanonicalProfileId = new WeakMap(), _RemoteFeatureFlagController_metaMetricsFlags = 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, } = {}) {
299
295
  return {
300
296
  ...__classPrivateFieldGet(this, _RemoteFeatureFlagController_defaultFeatureFlags, "f"),
301
- ...processedRemote,
297
+ ...processedRemoteFeatureFlags,
302
298
  ...localOverrides,
303
299
  };
304
300
  }, _RemoteFeatureFlagController_isCacheExpired = function _RemoteFeatureFlagController_isCacheExpired() {
@@ -310,39 +306,19 @@ _RemoteFeatureFlagController_fetchInterval = new WeakMap(), _RemoteFeatureFlagCo
310
306
  * @param remoteFeatureFlags - The new feature flags to cache.
311
307
  */
312
308
  async function _RemoteFeatureFlagController_updateCache(remoteFeatureFlags) {
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);
309
+ const resolved = await __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_processRemoteFeatureFlags).call(this, remoteFeatureFlags);
310
+ __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, resolved.processedFlags, "f");
336
311
  // Single state update with all changes batched together
337
- __classPrivateFieldSet(this, _RemoteFeatureFlagController_processedRemoteFeatureFlags, redactedProcessedFlags, "f");
338
312
  this.update(() => {
339
313
  return {
340
314
  ...this.state,
341
- remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, redactedProcessedFlags),
342
- rawRemoteFeatureFlags: redactMetaMetricsIds(remoteFeatureFlags),
315
+ remoteFeatureFlags: __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getEffectiveFeatureFlags).call(this, {
316
+ processedRemoteFeatureFlags: resolved.processedFlags,
317
+ }),
318
+ rawRemoteFeatureFlags: remoteFeatureFlags,
343
319
  cacheTimestamp: Date.now(),
344
- thresholdCache: updatedThresholdCache,
345
- featureFlagThresholdGroups: featureFlagThresholdGroupUpdates,
320
+ thresholdCache: resolved.thresholdCache,
321
+ featureFlagThresholdGroups: resolved.featureFlagThresholdGroups,
346
322
  };
347
323
  });
348
324
  }, _RemoteFeatureFlagController_processVersionBasedFlag = function _RemoteFeatureFlagController_processVersionBasedFlag(flagValue) {
@@ -350,11 +326,26 @@ async function _RemoteFeatureFlagController_updateCache(remoteFeatureFlags) {
350
326
  return flagValue;
351
327
  }
352
328
  return getVersionData(flagValue, __classPrivateFieldGet(this, _RemoteFeatureFlagController_clientVersion, "f"));
353
- }, _RemoteFeatureFlagController_processRemoteFeatureFlags = async function _RemoteFeatureFlagController_processRemoteFeatureFlags(remoteFeatureFlags) {
329
+ }, _RemoteFeatureFlagController_getSegmentationId = function _RemoteFeatureFlagController_getSegmentationId(featureFlagName) {
330
+ if (__classPrivateFieldGet(this, _RemoteFeatureFlagController_metaMetricsFlags, "f").has(featureFlagName)) {
331
+ return __classPrivateFieldGet(this, _RemoteFeatureFlagController_getMetaMetricsId, "f").call(this);
332
+ }
333
+ return __classPrivateFieldGet(this, _RemoteFeatureFlagController_getCanonicalProfileId, "f").call(this);
334
+ }, _RemoteFeatureFlagController_processRemoteFeatureFlags =
335
+ /**
336
+ * Resolves raw feature flags into the values that apply to this client and
337
+ * user, selecting version and threshold entries and reconciling the
338
+ * threshold cache against the flags the server currently serves.
339
+ *
340
+ * @param remoteFeatureFlags - The unprocessed feature flags.
341
+ * @returns The processed flags, the updated threshold cache, and the
342
+ * selected threshold group names.
343
+ */
344
+ async function _RemoteFeatureFlagController_processRemoteFeatureFlags(remoteFeatureFlags) {
354
345
  const processedFlags = {};
355
346
  const metaMetricsId = __classPrivateFieldGet(this, _RemoteFeatureFlagController_getMetaMetricsId, "f").call(this);
356
347
  const thresholdCacheUpdates = {};
357
- const featureFlagThresholdGroupUpdates = {};
348
+ const featureFlagThresholdGroups = {};
358
349
  for (const [remoteFeatureFlagName, remoteFeatureFlagValue,] of Object.entries(remoteFeatureFlags)) {
359
350
  let processedValue = __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_processVersionBasedFlag).call(this, remoteFeatureFlagValue);
360
351
  if (processedValue === null) {
@@ -368,28 +359,29 @@ async function _RemoteFeatureFlagController_updateCache(remoteFeatureFlags) {
368
359
  processedFlags[remoteFeatureFlagName] = processedValue;
369
360
  continue;
370
361
  }
371
- // Skip threshold processing if metaMetricsId is not available
372
- if (!metaMetricsId) {
373
- // Preserve array as-is when user hasn't opted into MetaMetrics
374
- processedFlags[remoteFeatureFlagName] = processedValue;
375
- continue;
376
- }
377
362
  // Explicit-ID matching: check before hash-based threshold, bypasses cache
378
363
  const normalizedMetaMetricsId = metaMetricsId.trim().toLowerCase();
379
- const explicitMatch = findExplicitIdMatch(processedValue, normalizedMetaMetricsId);
364
+ const explicitMatch = normalizedMetaMetricsId
365
+ ? findExplicitIdMatch(processedValue, normalizedMetaMetricsId)
366
+ : undefined;
380
367
  if (explicitMatch) {
381
368
  processedValue = explicitMatch.value;
382
369
  if (explicitMatch.name) {
383
- featureFlagThresholdGroupUpdates[remoteFeatureFlagName] =
370
+ featureFlagThresholdGroups[remoteFeatureFlagName] =
384
371
  explicitMatch.name;
385
372
  }
386
373
  }
387
374
  else {
375
+ const segmentationId = __classPrivateFieldGet(this, _RemoteFeatureFlagController_instances, "m", _RemoteFeatureFlagController_getSegmentationId).call(this, remoteFeatureFlagName);
376
+ if (!segmentationId) {
377
+ processedFlags[remoteFeatureFlagName] = processedValue;
378
+ continue;
379
+ }
388
380
  // Fall back to hash-based threshold selection with cache
389
- const cacheKey = `${metaMetricsId}:${remoteFeatureFlagName}`;
381
+ const cacheKey = `${segmentationId}:${remoteFeatureFlagName}`;
390
382
  let thresholdValue = this.state.thresholdCache?.[cacheKey];
391
383
  if (thresholdValue === undefined) {
392
- thresholdValue = await calculateThresholdForFlag(metaMetricsId, remoteFeatureFlagName);
384
+ thresholdValue = await calculateThresholdForFlag(segmentationId, remoteFeatureFlagName);
393
385
  // Collect new threshold for batched state update
394
386
  thresholdCacheUpdates[cacheKey] = thresholdValue;
395
387
  }
@@ -403,7 +395,7 @@ async function _RemoteFeatureFlagController_updateCache(remoteFeatureFlags) {
403
395
  if (selectedGroup) {
404
396
  processedValue = selectedGroup.value;
405
397
  if (selectedGroup.name) {
406
- featureFlagThresholdGroupUpdates[remoteFeatureFlagName] =
398
+ featureFlagThresholdGroups[remoteFeatureFlagName] =
407
399
  selectedGroup.name;
408
400
  }
409
401
  }
@@ -411,10 +403,27 @@ async function _RemoteFeatureFlagController_updateCache(remoteFeatureFlags) {
411
403
  }
412
404
  processedFlags[remoteFeatureFlagName] = processedValue;
413
405
  }
406
+ const thresholdCache = {
407
+ ...this.state.thresholdCache,
408
+ ...thresholdCacheUpdates,
409
+ };
410
+ // Drop cached thresholds for flags this user is no longer served, under
411
+ // either identifier they may have been bucketed by.
412
+ const canonicalProfileId = __classPrivateFieldGet(this, _RemoteFeatureFlagController_getCanonicalProfileId, "f").call(this);
413
+ const currentFlagNames = Object.keys(remoteFeatureFlags);
414
+ for (const cacheKey of Object.keys(thresholdCache)) {
415
+ const [cachedSegmentationId, ...cachedFlagNameParts] = cacheKey.split(':');
416
+ const cachedFlagName = cachedFlagNameParts.join(':');
417
+ if ((cachedSegmentationId === metaMetricsId ||
418
+ cachedSegmentationId === canonicalProfileId) &&
419
+ !currentFlagNames.includes(cachedFlagName)) {
420
+ delete thresholdCache[cacheKey];
421
+ }
422
+ }
414
423
  return {
415
424
  processedFlags,
416
- thresholdCacheUpdates,
417
- featureFlagThresholdGroupUpdates,
425
+ thresholdCache,
426
+ featureFlagThresholdGroups,
418
427
  };
419
428
  };
420
429
  //# 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 * 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"]}
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 #getCanonicalProfileId: () => string;\n\n readonly #metaMetricsFlags: ReadonlySet<string>;\n\n readonly #clientVersion: SemVerVersion;\n\n readonly #defaultFeatureFlags: FeatureFlags;\n\n #processedRemoteFeatureFlags: FeatureFlags;\n\n /**\n * Constructs a new RemoteFeatureFlagController instance.\n *\n * @param options - The controller options.\n * @param options.messenger - The messenger used for communication.\n * @param options.state - The initial state of the controller.\n * @param options.clientConfigApiService - The service instance to fetch remote feature flags.\n * @param options.fetchInterval - The interval in milliseconds before cached flags expire. Defaults to 1 day.\n * @param options.disabled - Determines if the controller should be disabled initially. Defaults to false.\n * @param options.getMetaMetricsId - Returns metaMetricsId.\n * @param options.getCanonicalProfileId - Returns the canonical profile identifier used for threshold flags by default. Must return an empty string, rather than throwing, when the identifier is unavailable.\n * @param options.metaMetricsFlags - Names of feature flags that should use MetaMetrics ID for threshold assignment.\n * @param options.clientVersion - The current client version for version-based feature flag filtering. Must be a valid 3-part SemVer version string.\n * @param options.prevClientVersion - The previous client version for feature flag cache invalidation.\n * @param options.defaultFeatureFlags - Client-side default feature flags used as the lowest-precedence layer under processed remote flags and local overrides. Not persisted.\n */\n constructor({\n messenger,\n state,\n clientConfigApiService,\n fetchInterval = DEFAULT_CACHE_DURATION,\n disabled = false,\n getMetaMetricsId,\n getCanonicalProfileId,\n metaMetricsFlags = [],\n clientVersion,\n prevClientVersion,\n defaultFeatureFlags = {},\n }: {\n messenger: RemoteFeatureFlagControllerMessenger;\n state?: Partial<RemoteFeatureFlagControllerState>;\n clientConfigApiService: AbstractClientConfigApiService;\n getMetaMetricsId: () => string;\n getCanonicalProfileId: () => string;\n metaMetricsFlags?: readonly string[];\n fetchInterval?: number;\n disabled?: boolean;\n clientVersion: string;\n prevClientVersion?: string;\n defaultFeatureFlags?: FeatureFlags;\n }) {\n if (!isValidSemVerVersion(clientVersion)) {\n throw new Error(\n `Invalid clientVersion: \"${clientVersion}\". Must be a valid 3-part SemVer version string`,\n );\n }\n\n const initialState: RemoteFeatureFlagControllerState = {\n ...getDefaultRemoteFeatureFlagControllerState(),\n ...state,\n };\n\n const hasClientVersionChanged =\n isValidSemVerVersion(prevClientVersion) &&\n prevClientVersion !== clientVersion;\n\n super({\n name: controllerName,\n metadata: remoteFeatureFlagControllerMetadata,\n messenger,\n state: {\n ...initialState,\n cacheTimestamp: hasClientVersionChanged\n ? 0\n : initialState.cacheTimestamp,\n },\n });\n\n this.#defaultFeatureFlags = defaultFeatureFlags;\n // Last session's effective flags stand in for the remote layer until\n // `init` re-derives it from the persisted raw flags, or a fetch replaces\n // it. Overrides are layered on top rather than subtracted out, so a remote\n // flag that happens to share an override's value is not lost.\n this.#processedRemoteFeatureFlags = initialState.remoteFeatureFlags;\n this.#fetchInterval = fetchInterval;\n this.#disabled = disabled;\n this.#clientConfigApiService = clientConfigApiService;\n this.#getMetaMetricsId = getMetaMetricsId;\n this.#getCanonicalProfileId = getCanonicalProfileId;\n this.#metaMetricsFlags = new Set(metaMetricsFlags);\n this.#clientVersion = clientVersion;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Computes effective feature flags with precedence:\n * defaults < processed remote < local overrides.\n *\n * @param options - The layers to merge. Each defaults to the current layer.\n * @param options.processedRemoteFeatureFlags - The processed remote feature\n * flags. Defaults to the currently resolved remote layer.\n * @param options.localOverrides - Local overrides. Defaults to current state\n * overrides.\n * @returns The effective feature flags.\n */\n #getEffectiveFeatureFlags({\n processedRemoteFeatureFlags = this.#processedRemoteFeatureFlags,\n localOverrides = this.state.localOverrides,\n }: {\n processedRemoteFeatureFlags?: FeatureFlags;\n localOverrides?: FeatureFlags;\n } = {}): FeatureFlags {\n return {\n ...this.#defaultFeatureFlags,\n ...processedRemoteFeatureFlags,\n ...localOverrides,\n };\n }\n\n /**\n * Checks if the cached feature flags are expired based on the fetch interval.\n *\n * @returns Whether the cache is expired (`true`) or still valid (`false`).\n */\n #isCacheExpired(): boolean {\n return Date.now() - this.state.cacheTimestamp > this.#fetchInterval;\n }\n\n /**\n * Retrieves the remote feature flags, fetching from the API if necessary.\n * Uses caching to prevent redundant API calls and handles concurrent fetches.\n *\n * @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 * Selects the identifier used to bucket a threshold flag. Flags named in\n * `metaMetricsFlags` segment by MetaMetrics ID; all others segment by\n * canonical profile ID.\n *\n * @param featureFlagName - The name of the feature flag being processed.\n * @returns The segmentation identifier, which may be empty when unavailable.\n */\n #getSegmentationId(featureFlagName: string): string {\n if (this.#metaMetricsFlags.has(featureFlagName)) {\n return this.#getMetaMetricsId();\n }\n return this.#getCanonicalProfileId();\n }\n\n /**\n * Resolves raw feature flags into the values that apply to this client and\n * user, selecting version and threshold entries and reconciling the\n * threshold cache against the flags the server currently serves.\n *\n * @param remoteFeatureFlags - The unprocessed feature flags.\n * @returns The processed flags, the updated threshold cache, and the\n * selected threshold group names.\n */\n async #processRemoteFeatureFlags(remoteFeatureFlags: FeatureFlags): Promise<{\n processedFlags: FeatureFlags;\n thresholdCache: Record<string, number>;\n featureFlagThresholdGroups: Record<string, string>;\n }> {\n const processedFlags: FeatureFlags = {};\n const metaMetricsId = this.#getMetaMetricsId();\n const thresholdCacheUpdates: Record<string, number> = {};\n const featureFlagThresholdGroups: Record<string, string> = {};\n\n for (const [\n remoteFeatureFlagName,\n remoteFeatureFlagValue,\n ] of Object.entries(remoteFeatureFlags)) {\n let processedValue = this.#processVersionBasedFlag(\n remoteFeatureFlagValue,\n );\n if (processedValue === null) {\n continue;\n }\n\n if (Array.isArray(processedValue)) {\n // Validate array has valid threshold items before doing expensive crypto operation\n const hasValidThresholds = processedValue.some(\n isFeatureFlagWithScopeValue,\n );\n\n if (!hasValidThresholds) {\n // Not a threshold array - preserve as-is\n processedFlags[remoteFeatureFlagName] = processedValue;\n continue;\n }\n\n // Explicit-ID matching: check before hash-based threshold, bypasses cache\n const normalizedMetaMetricsId = metaMetricsId.trim().toLowerCase();\n const explicitMatch = normalizedMetaMetricsId\n ? findExplicitIdMatch(processedValue, normalizedMetaMetricsId)\n : undefined;\n\n if (explicitMatch) {\n processedValue = explicitMatch.value;\n if (explicitMatch.name) {\n featureFlagThresholdGroups[remoteFeatureFlagName] =\n explicitMatch.name;\n }\n } else {\n const segmentationId = this.#getSegmentationId(remoteFeatureFlagName);\n\n if (!segmentationId) {\n processedFlags[remoteFeatureFlagName] = processedValue;\n continue;\n }\n\n // Fall back to hash-based threshold selection with cache\n const cacheKey =\n `${segmentationId}:${remoteFeatureFlagName}` as const;\n let thresholdValue = this.state.thresholdCache?.[cacheKey];\n\n if (thresholdValue === undefined) {\n thresholdValue = await calculateThresholdForFlag(\n segmentationId,\n remoteFeatureFlagName,\n );\n\n // Collect new threshold for batched state update\n thresholdCacheUpdates[cacheKey] = thresholdValue;\n }\n\n const threshold = thresholdValue;\n const selectedGroup = processedValue.find(\n (featureFlag): featureFlag is FeatureFlagScopeValue => {\n if (!isFeatureFlagWithScopeValue(featureFlag)) {\n return false;\n }\n\n return threshold <= featureFlag.scope.value;\n },\n );\n\n if (selectedGroup) {\n processedValue = selectedGroup.value;\n if (selectedGroup.name) {\n featureFlagThresholdGroups[remoteFeatureFlagName] =\n selectedGroup.name;\n }\n }\n }\n }\n\n processedFlags[remoteFeatureFlagName] = processedValue;\n }\n\n const thresholdCache = {\n ...this.state.thresholdCache,\n ...thresholdCacheUpdates,\n };\n\n // Drop cached thresholds for flags this user is no longer served, under\n // either identifier they may have been bucketed by.\n const canonicalProfileId = this.#getCanonicalProfileId();\n const currentFlagNames = Object.keys(remoteFeatureFlags);\n for (const cacheKey of Object.keys(thresholdCache)) {\n const [cachedSegmentationId, ...cachedFlagNameParts] =\n cacheKey.split(':');\n const cachedFlagName = cachedFlagNameParts.join(':');\n if (\n (cachedSegmentationId === metaMetricsId ||\n cachedSegmentationId === canonicalProfileId) &&\n !currentFlagNames.includes(cachedFlagName)\n ) {\n delete thresholdCache[cacheKey];\n }\n }\n\n return {\n processedFlags,\n thresholdCache,\n featureFlagThresholdGroups,\n };\n }\n\n /**\n * Enables the controller, allowing it to make network requests.\n */\n enable(): void {\n this.#disabled = false;\n }\n\n /**\n * Disables the controller, preventing it from making network requests.\n */\n disable(): void {\n this.#disabled = true;\n }\n\n /**\n * Sets a local override for a specific feature flag.\n *\n * @param flagName - The name of the feature flag to override.\n * @param value - The override value for the feature flag.\n */\n setFlagOverride(flagName: string, value: Json): void {\n this.update(() => {\n const localOverrides = {\n ...this.state.localOverrides,\n [flagName]: value,\n };\n\n return {\n ...this.state,\n localOverrides,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({ localOverrides }),\n };\n });\n }\n\n /**\n * Clears the local override for a specific feature flag.\n *\n * @param flagName - The name of the feature flag to clear.\n */\n removeFlagOverride(flagName: string): void {\n const newLocalOverrides = { ...this.state.localOverrides };\n delete newLocalOverrides[flagName];\n\n this.update(() => {\n return {\n ...this.state,\n localOverrides: newLocalOverrides,\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({\n localOverrides: newLocalOverrides,\n }),\n };\n });\n }\n\n /**\n * Clears all local feature flag overrides.\n */\n clearAllFlagOverrides(): void {\n this.update(() => {\n return {\n ...this.state,\n localOverrides: {},\n remoteFeatureFlags: this.#getEffectiveFeatureFlags({\n localOverrides: {},\n }),\n };\n });\n }\n}\n"]}
@@ -19,22 +19,22 @@ const MAX_UUID_V4_BIGINT = uuidStringToBigInt(MAX_UUID_V4);
19
19
  const UUID_V4_VALUE_RANGE_BIGINT = MAX_UUID_V4_BIGINT - MIN_UUID_V4_BIGINT;
20
20
  /**
21
21
  * Calculates a deterministic threshold value between 0 and 1 for A/B testing.
22
- * This function hashes the user's MetaMetrics ID combined with the feature flag name
22
+ * This function hashes the segmentation ID combined with the feature flag name
23
23
  * to ensure consistent group assignment across sessions while varying across different flags.
24
24
  *
25
- * @param metaMetricsId - The user's MetaMetrics ID (must be non-empty)
25
+ * @param segmentationId - The identifier used for threshold segmentation (must be non-empty)
26
26
  * @param featureFlagName - The feature flag name to create unique threshold per flag
27
27
  * @returns A promise that resolves to a number between 0 and 1
28
- * @throws Error if metaMetricsId is empty
28
+ * @throws Error if segmentationId is empty
29
29
  */
30
- async function calculateThresholdForFlag(metaMetricsId, featureFlagName) {
31
- if (!metaMetricsId) {
32
- throw new Error('MetaMetrics ID cannot be empty');
30
+ async function calculateThresholdForFlag(segmentationId, featureFlagName) {
31
+ if (!segmentationId) {
32
+ throw new Error('Segmentation ID cannot be empty');
33
33
  }
34
34
  if (!featureFlagName) {
35
35
  throw new Error('Feature flag name cannot be empty');
36
36
  }
37
- const seed = metaMetricsId + featureFlagName;
37
+ const seed = segmentationId + featureFlagName;
38
38
  // Hash the combined seed
39
39
  const encoder = new TextEncoder();
40
40
  const hashBuffer = await (0, utils_1.sha256)(encoder.encode(seed));
@@ -1 +1 @@
1
- {"version":3,"file":"user-segmentation-utils.cjs","sourceRoot":"","sources":["../../src/utils/user-segmentation-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Json } from '@metamask/utils';\nimport { sha256, bytesToHex } from '@metamask/utils';\nimport { validate as uuidValidate, version as uuidVersion } from 'uuid';\n\nimport type { FeatureFlagScopeValue } from '../remote-feature-flag-controller-types.js';\n\n/**\n * Converts a UUID string to a BigInt by removing dashes and converting to hexadecimal.\n *\n * @param uuid - The UUID string to convert\n * @returns The UUID as a BigInt value\n */\nfunction uuidStringToBigInt(uuid: string): bigint {\n return BigInt(`0x${uuid.replace(/-/gu, '')}`);\n}\n\nconst MIN_UUID_V4 = '00000000-0000-4000-8000-000000000000';\nconst MAX_UUID_V4 = 'ffffffff-ffff-4fff-bfff-ffffffffffff';\nconst MIN_UUID_V4_BIGINT = uuidStringToBigInt(MIN_UUID_V4);\nconst MAX_UUID_V4_BIGINT = uuidStringToBigInt(MAX_UUID_V4);\nconst UUID_V4_VALUE_RANGE_BIGINT = MAX_UUID_V4_BIGINT - MIN_UUID_V4_BIGINT;\n\n/**\n * Calculates a deterministic threshold value between 0 and 1 for A/B testing.\n * This function hashes the user's MetaMetrics ID combined with the feature flag name\n * to ensure consistent group assignment across sessions while varying across different flags.\n *\n * @param metaMetricsId - The user's MetaMetrics ID (must be non-empty)\n * @param featureFlagName - The feature flag name to create unique threshold per flag\n * @returns A promise that resolves to a number between 0 and 1\n * @throws Error if metaMetricsId is empty\n */\nexport async function calculateThresholdForFlag(\n metaMetricsId: string,\n featureFlagName: string,\n): Promise<number> {\n if (!metaMetricsId) {\n throw new Error('MetaMetrics ID cannot be empty');\n }\n\n if (!featureFlagName) {\n throw new Error('Feature flag name cannot be empty');\n }\n\n const seed = metaMetricsId + featureFlagName;\n\n // Hash the combined seed\n const encoder = new TextEncoder();\n const hashBuffer = await sha256(encoder.encode(seed));\n\n // Convert hash bytes directly to 0-1 range\n const hash = bytesToHex(hashBuffer);\n const hashBigInt = BigInt(hash);\n const maxValue = BigInt(`0x${'f'.repeat(64)}`);\n\n // Use BigInt division first, then convert to number to maintain precision\n return Number((hashBigInt * BigInt(1_000_000)) / maxValue) / 1_000_000;\n}\n\n/**\n * Generates a deterministic random number between 0 and 1 based on a metaMetricsId.\n * This is useful for A/B testing and feature flag rollouts where we want\n * consistent group assignment for the same user.\n *\n * @param metaMetricsId - The unique identifier used to generate the deterministic random number. Must be either:\n * - A UUIDv4 string (e.g., '123e4567-e89b-12d3-a456-426614174000'\n * - A hex string with '0x' prefix (e.g., '0x86bacb9b2bf9a7e8d2b147eadb95ac9aaa26842327cd24afc8bd4b3c1d136420')\n * @returns A number between 0 and 1, deterministically generated from the input ID.\n * The same input will always produce the same output.\n */\nexport function generateDeterministicRandomNumber(\n metaMetricsId: string,\n): number {\n if (!metaMetricsId) {\n throw new Error('MetaMetrics ID cannot be empty');\n }\n\n let idValue: bigint;\n let maxValue: bigint;\n\n // uuidv4 format\n if (uuidValidate(metaMetricsId)) {\n if (uuidVersion(metaMetricsId) !== 4) {\n throw new Error(\n `Invalid UUID version. Expected v4, got v${uuidVersion(metaMetricsId)}`,\n );\n }\n idValue = uuidStringToBigInt(metaMetricsId) - MIN_UUID_V4_BIGINT;\n maxValue = UUID_V4_VALUE_RANGE_BIGINT;\n } else {\n // hex format with 0x prefix\n if (!metaMetricsId.startsWith('0x')) {\n throw new Error('Hex ID must start with 0x prefix');\n }\n\n const cleanId = metaMetricsId.slice(2);\n const EXPECTED_HEX_LENGTH = 64; // 32 bytes = 64 hex characters\n\n if (cleanId.length !== EXPECTED_HEX_LENGTH) {\n throw new Error(\n `Invalid hex ID length. Expected ${EXPECTED_HEX_LENGTH} characters, got ${cleanId.length}`,\n );\n }\n\n if (!/^[0-9a-f]+$/iu.test(cleanId)) {\n throw new Error('Hex ID contains invalid characters');\n }\n\n idValue = BigInt(`0x${cleanId}`);\n maxValue = BigInt(`0x${'f'.repeat(cleanId.length)}`);\n }\n\n // Use BigInt division first, then convert to number to maintain precision\n return Number((idValue * BigInt(1_000_000)) / maxValue) / 1_000_000;\n}\n\n/**\n * Type guard to check if a value is a feature flag with scope.\n * Used to validate feature flag objects that contain scope-based configurations.\n *\n * @param featureFlag - The value to check if it's a feature flag with scope\n * @returns True if the value is a feature flag with scope, false otherwise\n */\nexport const isFeatureFlagWithScopeValue = (\n featureFlag: Json,\n): featureFlag is FeatureFlagScopeValue => {\n return (\n typeof featureFlag === 'object' &&\n featureFlag !== null &&\n 'scope' in featureFlag\n );\n};\n"]}
1
+ {"version":3,"file":"user-segmentation-utils.cjs","sourceRoot":"","sources":["../../src/utils/user-segmentation-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Json } from '@metamask/utils';\nimport { sha256, bytesToHex } from '@metamask/utils';\nimport { validate as uuidValidate, version as uuidVersion } from 'uuid';\n\nimport type { FeatureFlagScopeValue } from '../remote-feature-flag-controller-types.js';\n\n/**\n * Converts a UUID string to a BigInt by removing dashes and converting to hexadecimal.\n *\n * @param uuid - The UUID string to convert\n * @returns The UUID as a BigInt value\n */\nfunction uuidStringToBigInt(uuid: string): bigint {\n return BigInt(`0x${uuid.replace(/-/gu, '')}`);\n}\n\nconst MIN_UUID_V4 = '00000000-0000-4000-8000-000000000000';\nconst MAX_UUID_V4 = 'ffffffff-ffff-4fff-bfff-ffffffffffff';\nconst MIN_UUID_V4_BIGINT = uuidStringToBigInt(MIN_UUID_V4);\nconst MAX_UUID_V4_BIGINT = uuidStringToBigInt(MAX_UUID_V4);\nconst UUID_V4_VALUE_RANGE_BIGINT = MAX_UUID_V4_BIGINT - MIN_UUID_V4_BIGINT;\n\n/**\n * Calculates a deterministic threshold value between 0 and 1 for A/B testing.\n * This function hashes the segmentation ID combined with the feature flag name\n * to ensure consistent group assignment across sessions while varying across different flags.\n *\n * @param segmentationId - The identifier used for threshold segmentation (must be non-empty)\n * @param featureFlagName - The feature flag name to create unique threshold per flag\n * @returns A promise that resolves to a number between 0 and 1\n * @throws Error if segmentationId is empty\n */\nexport async function calculateThresholdForFlag(\n segmentationId: string,\n featureFlagName: string,\n): Promise<number> {\n if (!segmentationId) {\n throw new Error('Segmentation ID cannot be empty');\n }\n\n if (!featureFlagName) {\n throw new Error('Feature flag name cannot be empty');\n }\n\n const seed = segmentationId + featureFlagName;\n\n // Hash the combined seed\n const encoder = new TextEncoder();\n const hashBuffer = await sha256(encoder.encode(seed));\n\n // Convert hash bytes directly to 0-1 range\n const hash = bytesToHex(hashBuffer);\n const hashBigInt = BigInt(hash);\n const maxValue = BigInt(`0x${'f'.repeat(64)}`);\n\n // Use BigInt division first, then convert to number to maintain precision\n return Number((hashBigInt * BigInt(1_000_000)) / maxValue) / 1_000_000;\n}\n\n/**\n * Generates a deterministic random number between 0 and 1 based on a metaMetricsId.\n * This is useful for A/B testing and feature flag rollouts where we want\n * consistent group assignment for the same user.\n *\n * @param metaMetricsId - The unique identifier used to generate the deterministic random number. Must be either:\n * - A UUIDv4 string (e.g., '123e4567-e89b-12d3-a456-426614174000'\n * - A hex string with '0x' prefix (e.g., '0x86bacb9b2bf9a7e8d2b147eadb95ac9aaa26842327cd24afc8bd4b3c1d136420')\n * @returns A number between 0 and 1, deterministically generated from the input ID.\n * The same input will always produce the same output.\n */\nexport function generateDeterministicRandomNumber(\n metaMetricsId: string,\n): number {\n if (!metaMetricsId) {\n throw new Error('MetaMetrics ID cannot be empty');\n }\n\n let idValue: bigint;\n let maxValue: bigint;\n\n // uuidv4 format\n if (uuidValidate(metaMetricsId)) {\n if (uuidVersion(metaMetricsId) !== 4) {\n throw new Error(\n `Invalid UUID version. Expected v4, got v${uuidVersion(metaMetricsId)}`,\n );\n }\n idValue = uuidStringToBigInt(metaMetricsId) - MIN_UUID_V4_BIGINT;\n maxValue = UUID_V4_VALUE_RANGE_BIGINT;\n } else {\n // hex format with 0x prefix\n if (!metaMetricsId.startsWith('0x')) {\n throw new Error('Hex ID must start with 0x prefix');\n }\n\n const cleanId = metaMetricsId.slice(2);\n const EXPECTED_HEX_LENGTH = 64; // 32 bytes = 64 hex characters\n\n if (cleanId.length !== EXPECTED_HEX_LENGTH) {\n throw new Error(\n `Invalid hex ID length. Expected ${EXPECTED_HEX_LENGTH} characters, got ${cleanId.length}`,\n );\n }\n\n if (!/^[0-9a-f]+$/iu.test(cleanId)) {\n throw new Error('Hex ID contains invalid characters');\n }\n\n idValue = BigInt(`0x${cleanId}`);\n maxValue = BigInt(`0x${'f'.repeat(cleanId.length)}`);\n }\n\n // Use BigInt division first, then convert to number to maintain precision\n return Number((idValue * BigInt(1_000_000)) / maxValue) / 1_000_000;\n}\n\n/**\n * Type guard to check if a value is a feature flag with scope.\n * Used to validate feature flag objects that contain scope-based configurations.\n *\n * @param featureFlag - The value to check if it's a feature flag with scope\n * @returns True if the value is a feature flag with scope, false otherwise\n */\nexport const isFeatureFlagWithScopeValue = (\n featureFlag: Json,\n): featureFlag is FeatureFlagScopeValue => {\n return (\n typeof featureFlag === 'object' &&\n featureFlag !== null &&\n 'scope' in featureFlag\n );\n};\n"]}
@@ -2,15 +2,15 @@ import type { Json } from "@metamask/utils";
2
2
  import type { FeatureFlagScopeValue } from "../remote-feature-flag-controller-types.cjs";
3
3
  /**
4
4
  * Calculates a deterministic threshold value between 0 and 1 for A/B testing.
5
- * This function hashes the user's MetaMetrics ID combined with the feature flag name
5
+ * This function hashes the segmentation ID combined with the feature flag name
6
6
  * to ensure consistent group assignment across sessions while varying across different flags.
7
7
  *
8
- * @param metaMetricsId - The user's MetaMetrics ID (must be non-empty)
8
+ * @param segmentationId - The identifier used for threshold segmentation (must be non-empty)
9
9
  * @param featureFlagName - The feature flag name to create unique threshold per flag
10
10
  * @returns A promise that resolves to a number between 0 and 1
11
- * @throws Error if metaMetricsId is empty
11
+ * @throws Error if segmentationId is empty
12
12
  */
13
- export declare function calculateThresholdForFlag(metaMetricsId: string, featureFlagName: string): Promise<number>;
13
+ export declare function calculateThresholdForFlag(segmentationId: string, featureFlagName: string): Promise<number>;
14
14
  /**
15
15
  * Generates a deterministic random number between 0 and 1 based on a metaMetricsId.
16
16
  * This is useful for A/B testing and feature flag rollouts where we want
@@ -1 +1 @@
1
- {"version":3,"file":"user-segmentation-utils.d.cts","sourceRoot":"","sources":["../../src/utils/user-segmentation-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAI5C,OAAO,KAAK,EAAE,qBAAqB,EAAE,oDAAmD;AAkBxF;;;;;;;;;GASG;AACH,wBAAsB,yBAAyB,CAC7C,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,CAAC,CAsBjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iCAAiC,CAC/C,aAAa,EAAE,MAAM,GACpB,MAAM,CA0CR;AAED;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,gBACzB,IAAI,yCAOlB,CAAC"}
1
+ {"version":3,"file":"user-segmentation-utils.d.cts","sourceRoot":"","sources":["../../src/utils/user-segmentation-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAI5C,OAAO,KAAK,EAAE,qBAAqB,EAAE,oDAAmD;AAkBxF;;;;;;;;;GASG;AACH,wBAAsB,yBAAyB,CAC7C,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,CAAC,CAsBjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iCAAiC,CAC/C,aAAa,EAAE,MAAM,GACpB,MAAM,CA0CR;AAED;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,gBACzB,IAAI,yCAOlB,CAAC"}