{"version":3,"file":"SnapRegistryController.cjs","sourceRoot":"","sources":["../../../src/snaps/registry/SnapRegistryController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n  ControllerGetStateAction,\n  ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n  SnapsRegistryDatabase,\n  SignatureStruct,\n} from '@metamask/snaps-registry';\nimport { verify } from '@metamask/snaps-registry';\nimport { getTargetVersion } from '@metamask/snaps-utils';\nimport type { Infer } from '@metamask/superstruct';\nimport type { Hex, SemVerRange, SemVerVersion } from '@metamask/utils';\nimport {\n  assert,\n  assertIsSemVerRange,\n  Duration,\n  inMilliseconds,\n  satisfiesVersionRange,\n} from '@metamask/utils';\n\nimport type { SnapRegistryControllerMethodActions } from './SnapRegistryController-method-action-types';\nimport type {\n  SnapRegistryInfo,\n  SnapRegistryMetadata,\n  SnapRegistryRequest,\n  SnapRegistryResult,\n} from './types';\nimport { SnapRegistryStatus } from './types';\n\nconst SNAP_REGISTRY_URL =\n  'https://acl.execution.metamask.io/latest/registry.json';\n\nconst SNAP_REGISTRY_SIGNATURE_URL =\n  'https://acl.execution.metamask.io/latest/signature.json';\n\nconst DEFAULT_PUBLIC_KEY =\n  '0x025b65308f0f0fb8bc7f7ff87bfc296e0330eee5d3c1d1ee4a048b2fd6a86fa0a6';\n\ntype JsonSnapsRegistryUrl = {\n  registry: string;\n  signature: string;\n};\n\nexport type ClientConfig = {\n  type: 'extension' | 'mobile';\n  version: SemVerVersion;\n};\n\nexport type SnapRegistryControllerArgs = {\n  messenger: SnapRegistryControllerMessenger;\n  state?: SnapRegistryControllerState;\n  fetchFunction?: typeof fetch;\n  url?: JsonSnapsRegistryUrl;\n  recentFetchThreshold?: number;\n  refetchOnAllowlistMiss?: boolean;\n  publicKey?: Hex;\n  clientConfig: ClientConfig;\n};\n\nexport type SnapRegistryControllerGetStateAction = ControllerGetStateAction<\n  typeof controllerName,\n  SnapRegistryControllerState\n>;\n\nexport type SnapRegistryControllerActions =\n  | SnapRegistryControllerGetStateAction\n  | SnapRegistryControllerMethodActions;\n\nexport type SnapRegistryControllerStateChangeEvent = ControllerStateChangeEvent<\n  typeof controllerName,\n  SnapRegistryControllerState\n>;\n\nexport type SnapRegistryControllerRegistryUpdatedEvent = {\n  type: `${typeof controllerName}:registryUpdated`;\n  payload: [databaseUpdated: boolean];\n};\n\nexport type SnapRegistryControllerEvents =\n  | SnapRegistryControllerStateChangeEvent\n  | SnapRegistryControllerRegistryUpdatedEvent;\n\nexport type SnapRegistryControllerMessenger = Messenger<\n  typeof controllerName,\n  SnapRegistryControllerActions,\n  SnapRegistryControllerEvents\n>;\n\nexport type SnapRegistryControllerState = {\n  database: SnapsRegistryDatabase | null;\n  signature: string | null;\n  lastUpdated: number | null;\n  databaseUnavailable: boolean;\n};\n\nconst controllerName = 'SnapRegistryController';\n\nconst MESSENGER_EXPOSED_METHODS = [\n  'get',\n  'getMetadata',\n  'resolveVersion',\n  'requestUpdate',\n] as const;\n\nconst defaultState = {\n  database: null,\n  signature: null,\n  lastUpdated: null,\n  databaseUnavailable: false,\n};\n\nexport class SnapRegistryController extends BaseController<\n  typeof controllerName,\n  SnapRegistryControllerState,\n  SnapRegistryControllerMessenger\n> {\n  readonly #url: JsonSnapsRegistryUrl;\n\n  readonly #publicKey: Hex;\n\n  readonly #clientConfig: ClientConfig;\n\n  readonly #fetchFunction: typeof fetch;\n\n  readonly #recentFetchThreshold: number;\n\n  readonly #refetchOnAllowlistMiss: boolean;\n\n  #currentUpdate: Promise<void> | null;\n\n  constructor({\n    messenger,\n    state,\n    url = {\n      registry: SNAP_REGISTRY_URL,\n      signature: SNAP_REGISTRY_SIGNATURE_URL,\n    },\n    publicKey = DEFAULT_PUBLIC_KEY,\n    clientConfig,\n    fetchFunction = globalThis.fetch.bind(undefined),\n    recentFetchThreshold = inMilliseconds(5, Duration.Minute),\n    refetchOnAllowlistMiss = true,\n  }: SnapRegistryControllerArgs) {\n    super({\n      messenger,\n      metadata: {\n        database: {\n          includeInStateLogs: true,\n          persist: true,\n          includeInDebugSnapshot: false,\n          usedInUi: true,\n        },\n        signature: {\n          includeInStateLogs: true,\n          persist: true,\n          includeInDebugSnapshot: true,\n          usedInUi: false,\n        },\n        lastUpdated: {\n          includeInStateLogs: true,\n          persist: true,\n          includeInDebugSnapshot: true,\n          usedInUi: false,\n        },\n        databaseUnavailable: {\n          includeInStateLogs: true,\n          persist: true,\n          includeInDebugSnapshot: true,\n          usedInUi: false,\n        },\n      },\n      name: controllerName,\n      state: {\n        ...defaultState,\n        ...state,\n      },\n    });\n    this.#url = url;\n    this.#publicKey = publicKey;\n    this.#clientConfig = clientConfig;\n    this.#fetchFunction = fetchFunction;\n    this.#recentFetchThreshold = recentFetchThreshold;\n    this.#refetchOnAllowlistMiss = refetchOnAllowlistMiss;\n    this.#currentUpdate = null;\n\n    this.messenger.registerMethodActionHandlers(\n      this,\n      MESSENGER_EXPOSED_METHODS,\n    );\n  }\n\n  #wasRecentlyFetched() {\n    return (\n      this.state.lastUpdated &&\n      Date.now() - this.state.lastUpdated < this.#recentFetchThreshold\n    );\n  }\n\n  /**\n   * Triggers an update of the registry database.\n   *\n   * If an existing update is in progress this function will await that update.\n   */\n  async requestUpdate() {\n    // If an update is ongoing, wait for that.\n    if (this.#currentUpdate) {\n      await this.#currentUpdate;\n      return;\n    }\n    // If no update exists, create promise and store globally.\n    if (this.#currentUpdate === null) {\n      this.#currentUpdate = this.#update();\n    }\n    await this.#currentUpdate;\n    this.#currentUpdate = null;\n  }\n\n  /**\n   * Updates the registry database if the registry hasn't been updated recently.\n   *\n   * NOTE: SHOULD NOT be called directly, instead `triggerUpdate` should be used.\n   */\n  async #update() {\n    // No-op if we recently fetched the registry.\n    if (this.#wasRecentlyFetched()) {\n      this.messenger.publish('SnapRegistryController:registryUpdated', false);\n      return;\n    }\n\n    try {\n      const [database, signature] = await Promise.all([\n        this.#safeFetch(this.#url.registry),\n        this.#safeFetch(this.#url.signature),\n      ]);\n\n      const signatureJson = JSON.parse(signature);\n\n      // If the signature matches the existing state, we can skip verification and don't need to update the database.\n      if (signatureJson.signature === this.state.signature) {\n        this.update((state) => {\n          state.lastUpdated = Date.now();\n          state.databaseUnavailable = false;\n        });\n        this.messenger.publish('SnapRegistryController:registryUpdated', false);\n        return;\n      }\n\n      await this.#verifySignature(database, signatureJson);\n\n      this.update((state) => {\n        state.database = JSON.parse(database);\n        state.lastUpdated = Date.now();\n        state.databaseUnavailable = false;\n        state.signature = signatureJson.signature;\n      });\n\n      this.messenger.publish('SnapRegistryController:registryUpdated', true);\n    } catch {\n      // Ignore\n      this.update((state) => {\n        state.databaseUnavailable = true;\n      });\n    }\n  }\n\n  async #getDatabase(): Promise<SnapsRegistryDatabase | null> {\n    if (this.state.database === null) {\n      await this.requestUpdate();\n    }\n\n    return this.state.database;\n  }\n\n  async #getSingle(\n    snapId: string,\n    snapInfo: SnapRegistryInfo,\n    refetch = false,\n  ): Promise<SnapRegistryResult> {\n    const database = await this.#getDatabase();\n\n    const blockedEntry = database?.blockedSnaps.find((blocked) => {\n      if ('id' in blocked) {\n        return (\n          blocked.id === snapId &&\n          satisfiesVersionRange(snapInfo.version, blocked.versionRange)\n        );\n      }\n\n      return blocked.checksum === snapInfo.checksum;\n    });\n\n    if (blockedEntry) {\n      return {\n        status: SnapRegistryStatus.Blocked,\n        reason: blockedEntry.reason,\n      };\n    }\n\n    const verified = database?.verifiedSnaps[snapId];\n    const version = verified?.versions?.[snapInfo.version];\n    const clientRange = version?.clientVersions?.[this.#clientConfig.type];\n    const isCompatible =\n      !clientRange ||\n      satisfiesVersionRange(this.#clientConfig.version, clientRange);\n    if (version && version.checksum === snapInfo.checksum && isCompatible) {\n      return { status: SnapRegistryStatus.Verified };\n    }\n    // For now, if we have an allowlist miss, we can refetch once and try again.\n    if (this.#refetchOnAllowlistMiss && !refetch) {\n      await this.requestUpdate();\n      return this.#getSingle(snapId, snapInfo, true);\n    }\n    return {\n      status: this.state.databaseUnavailable\n        ? SnapRegistryStatus.Unavailable\n        : SnapRegistryStatus.Unverified,\n    };\n  }\n\n  async get(\n    snaps: SnapRegistryRequest,\n  ): Promise<Record<string, SnapRegistryResult>> {\n    return Object.entries(snaps).reduce<\n      Promise<Record<string, SnapRegistryResult>>\n    >(async (previousPromise, [snapId, snapInfo]) => {\n      const result = await this.#getSingle(snapId, snapInfo);\n      const acc = await previousPromise;\n      acc[snapId] = result;\n      return acc;\n    }, Promise.resolve({}));\n  }\n\n  /**\n   * Find an allowlisted version within a specified version range. Otherwise return the version range itself.\n   *\n   * @param snapId - The ID of the snap we are trying to resolve a version for.\n   * @param versionRange - The version range.\n   * @param refetch - An optional flag used to determine if we are refetching the registry.\n   * @returns An allowlisted version within the specified version range if available otherwise returns the input version range.\n   */\n  async resolveVersion(\n    snapId: string,\n    versionRange: SemVerRange,\n    refetch = false,\n  ): Promise<SemVerRange> {\n    const database = await this.#getDatabase();\n    const versions = database?.verifiedSnaps[snapId]?.versions ?? null;\n\n    if (!versions && this.#refetchOnAllowlistMiss && !refetch) {\n      await this.requestUpdate();\n      return this.resolveVersion(snapId, versionRange, true);\n    }\n\n    // If we cannot narrow down the version range we return the unaltered version range.\n    if (!versions) {\n      return versionRange;\n    }\n\n    const compatibleVersions = Object.entries(versions).reduce<SemVerVersion[]>(\n      (accumulator, [version, metadata]) => {\n        const clientRange = metadata.clientVersions?.[this.#clientConfig.type];\n        if (\n          !clientRange ||\n          satisfiesVersionRange(this.#clientConfig.version, clientRange)\n        ) {\n          accumulator.push(version as SemVerVersion);\n        }\n\n        return accumulator;\n      },\n      [],\n    );\n\n    const targetVersion = getTargetVersion(compatibleVersions, versionRange);\n\n    if (!targetVersion && this.#refetchOnAllowlistMiss && !refetch) {\n      await this.requestUpdate();\n      return this.resolveVersion(snapId, versionRange, true);\n    }\n\n    // If we cannot narrow down the version range we return the unaltered version range.\n    if (!targetVersion) {\n      return versionRange;\n    }\n\n    // A semver version is technically also a valid semver range.\n    assertIsSemVerRange(targetVersion);\n    return targetVersion;\n  }\n\n  /**\n   * Get metadata for the given snap ID, if available, without updating registry.\n   *\n   * @param snapId - The ID of the snap to get metadata for.\n   * @returns The metadata for the given snap ID, or `null` if the snap is not\n   * verified.\n   */\n  getMetadata(snapId: string): SnapRegistryMetadata | null {\n    return this.state?.database?.verifiedSnaps[snapId]?.metadata ?? null;\n  }\n\n  /**\n   * Verify the signature of the registry.\n   *\n   * @param database - The registry database.\n   * @param signature - The signature of the registry.\n   * @throws If the signature is invalid.\n   */\n  async #verifySignature(\n    database: string,\n    signature: Infer<typeof SignatureStruct>,\n  ) {\n    assert(this.#publicKey, 'No public key provided.');\n\n    const valid = await verify({\n      registry: database,\n      signature,\n      publicKey: this.#publicKey,\n    });\n\n    assert(valid, 'Invalid registry signature.');\n  }\n\n  /**\n   * Fetch the given URL, throwing if the response is not OK.\n   *\n   * @param url - The URL to fetch.\n   * @returns The response body.\n   * @private\n   */\n  async #safeFetch(url: string) {\n    const response = await this.#fetchFunction(url, { cache: 'no-cache' });\n    if (!response.ok) {\n      throw new Error(`Failed to fetch ${url}.`);\n    }\n\n    return await response.text();\n  }\n}\n"]}