{"version":3,"file":"remote-feature-flag-controller.mjs","sourceRoot":"","sources":["../src/remote-feature-flag-controller.ts"],"names":[],"mappings":";;;;;;;;;;;;AAKA,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAQ3D,kBAAkB;AAElB,MAAM,CAAC,MAAM,cAAc,GAAG,6BAA6B,CAAC;AAC5D,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,QAAQ;AASnE,MAAM,mCAAmC,GAAG;IAC1C,kBAAkB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE;IACtD,cAAc,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE;CACnD,CAAC;AAwCF;;;;GAIG;AACH,MAAM,UAAU,0CAA0C;IACxD,OAAO;QACL,kBAAkB,EAAE,EAAE;QACtB,cAAc,EAAE,CAAC;KAClB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,2BAA4B,SAAQ,cAIhD;IASC;;;;;;;;;OASG;IACH,YAAY,EACV,SAAS,EACT,KAAK,EACL,sBAAsB,EACtB,aAAa,GAAG,sBAAsB,EACtC,QAAQ,GAAG,KAAK,GAOjB;QACC,KAAK,CAAC;YACJ,IAAI,EAAE,cAAc;YACpB,QAAQ,EAAE,mCAAmC;YAC7C,SAAS;YACT,KAAK,EAAE;gBACL,GAAG,0CAA0C,EAAE;gBAC/C,GAAG,KAAK;aACT;SACF,CAAC,CAAC;;QAvCI,6DAAuB;QAEhC,wDAAmB;QAEnB,sEAAwD;QAExD,oEAAiD;QAmC/C,uBAAA,IAAI,8CAAkB,aAAa,MAAA,CAAC;QACpC,uBAAA,IAAI,yCAAa,QAAQ,MAAA,CAAC;QAC1B,uBAAA,IAAI,uDAA2B,sBAAsB,MAAA,CAAC;IACxD,CAAC;IAYD;;;;;OAKG;IACH,KAAK,CAAC,wBAAwB;QAC5B,IAAI,uBAAA,IAAI,6CAAU,IAAI,CAAC,uBAAA,IAAI,2FAAgB,MAApB,IAAI,CAAkB,EAAE;YAC7C,OAAO;SACR;QAED,IAAI,UAAU,CAAC;QAEf,IAAI,uBAAA,IAAI,yDAAsB,EAAE;YAC9B,MAAM,uBAAA,IAAI,yDAAsB,CAAC;YACjC,OAAO;SACR;QAED,IAAI;YACF,uBAAA,IAAI,qDACF,uBAAA,IAAI,2DAAwB,CAAC,uBAAuB,EAAE,MAAA,CAAC;YAEzD,UAAU,GAAG,MAAM,uBAAA,IAAI,yDAAsB,CAAC;SAC/C;gBAAS;YACR,uBAAA,IAAI,qDAAyB,SAAS,MAAA,CAAC;SACxC;QAED,uBAAA,IAAI,wFAAa,MAAjB,IAAI,EAAc,UAAU,CAAC,kBAAkB,CAAC,CAAC;IACnD,CAAC;IAiBD;;OAEG;IACH,MAAM;QACJ,uBAAA,IAAI,yCAAa,KAAK,MAAA,CAAC;IACzB,CAAC;IAED;;OAEG;IACH,OAAO;QACL,uBAAA,IAAI,yCAAa,IAAI,MAAA,CAAC;IACxB,CAAC;CACF;;IA7DG,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,uBAAA,IAAI,kDAAe,CAAC;AACtE,CAAC,+FAsCY,kBAAgC;IAC3C,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;QACf,OAAO;YACL,kBAAkB;YAClB,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE;SAC3B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type {\n  ControllerGetStateAction,\n  ControllerStateChangeEvent,\n  RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\n\nimport type { AbstractClientConfigApiService } from './client-config-api-service/abstract-client-config-api-service';\nimport type {\n  FeatureFlags,\n  ServiceResponse,\n} from './remote-feature-flag-controller-types';\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  cacheTimestamp: number;\n};\n\nconst remoteFeatureFlagControllerMetadata = {\n  remoteFeatureFlags: { persist: true, anonymous: true },\n  cacheTimestamp: { persist: true, anonymous: true },\n};\n\n// === MESSENGER ===\n\nexport type RemoteFeatureFlagControllerGetStateAction =\n  ControllerGetStateAction<\n    typeof controllerName,\n    RemoteFeatureFlagControllerState\n  >;\n\nexport type RemoteFeatureFlagControllerGetRemoteFeatureFlagAction = {\n  type: `${typeof controllerName}:updateRemoteFeatureFlags`;\n  handler: RemoteFeatureFlagController['updateRemoteFeatureFlags'];\n};\n\nexport type RemoteFeatureFlagControllerActions =\n  RemoteFeatureFlagControllerGetStateAction;\n\nexport type AllowedActions = never;\n\nexport type RemoteFeatureFlagControllerStateChangeEvent =\n  ControllerStateChangeEvent<\n    typeof controllerName,\n    RemoteFeatureFlagControllerState\n  >;\n\nexport type RemoteFeatureFlagControllerEvents =\n  RemoteFeatureFlagControllerStateChangeEvent;\n\nexport type AllowedEvents = never;\n\nexport type RemoteFeatureFlagControllerMessenger =\n  RestrictedControllerMessenger<\n    typeof controllerName,\n    RemoteFeatureFlagControllerActions | AllowedActions,\n    RemoteFeatureFlagControllerEvents | AllowedEvents,\n    AllowedActions['type'],\n    AllowedEvents['type']\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    cacheTimestamp: 0,\n  };\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  #clientConfigApiService: AbstractClientConfigApiService;\n\n  #inProgressFlagUpdate?: Promise<ServiceResponse>;\n\n  /**\n   * Constructs a new RemoteFeatureFlagController instance.\n   *\n   * @param options - The controller options.\n   * @param options.messenger - The controller 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   */\n  constructor({\n    messenger,\n    state,\n    clientConfigApiService,\n    fetchInterval = DEFAULT_CACHE_DURATION,\n    disabled = false,\n  }: {\n    messenger: RemoteFeatureFlagControllerMessenger;\n    state?: Partial<RemoteFeatureFlagControllerState>;\n    clientConfigApiService: AbstractClientConfigApiService;\n    fetchInterval?: number;\n    disabled?: boolean;\n  }) {\n    super({\n      name: controllerName,\n      metadata: remoteFeatureFlagControllerMetadata,\n      messenger,\n      state: {\n        ...getDefaultRemoteFeatureFlagControllerState(),\n        ...state,\n      },\n    });\n\n    this.#fetchInterval = fetchInterval;\n    this.#disabled = disabled;\n    this.#clientConfigApiService = clientConfigApiService;\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   * @private\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    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   * @private\n   */\n  #updateCache(remoteFeatureFlags: FeatureFlags) {\n    this.update(() => {\n      return {\n        remoteFeatureFlags,\n        cacheTimestamp: Date.now(),\n      };\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"]}