{"version":3,"file":"utils.mjs","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;;;;AAEA,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,4BAA4B;AAC9D,OAAO,EACL,aAAa,EACb,YAAY,EACZ,6BAA6B,EAC7B,sBAAsB,EACtB,mBAAmB,EACnB,WAAW,EACZ,8BAA8B;AAE/B,OAAO,UAAS,wBAAwB;;AAGxC,OAAO,EAAE,KAAK,EAAE,0BAAsB;AAEtC;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,OAAO,CAGrB,OAAgB,EAAE,OAAgB;IAClC,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CACnC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QACpB,IAAI,CAAC,CAAC,GAAG,IAAI,OAAO,CAAC,EAAE,CAAC;YACtB,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACnB,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,EACD,EAAE,CACuB,CAAC;AAC9B,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAI7B,YAA0B,EAC1B,YAA0B;IAE1B,OAAO,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,MAAM,CAExC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QACtB,MAAM,UAAU,GAAG,GAAG,IAAI,YAAY,CAAC;QACvC,IACE,CAAC,UAAU;YACX,CAAC,UAAU;gBACT,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,EACnE,CAAC;YACD,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACnB,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,EAAE,EAAE,CAAqC,CAAC;AAC7C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,KAAK,CACnB,EAAU,EACV,MAAe;IAEf,OAAO,cAAc,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAY,EACZ,MAAe;IAEf,IAAI,UAAmC,CAAC;IACxC,MAAM,OAAO,GAAQ,IAAI,OAAO,CAAS,CAAC,OAAY,EAAE,MAAM,EAAE,EAAE;QAChE,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE;YACf,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QACH,UAAU,GAAG,MAAM,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE;QACpB,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAChC,KAAK,CAAC,MAAM,EAAE,CAAC;YACf,UAAU,CAAC,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAC;QACxD,CAAC;IACH,CAAC,CAAC;IACF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAC/B,sEAAsE,CACvE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAA8B,EAC9B,SAAyB;IAEzB,MAAM,KAAK,GACT,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACnE,MAAM,YAAY,GAAG,cAAc,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;IACrD,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,MAAM,EAAE,CAAC;IACxB,CAAC;AACH,CAAC;AA+FD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,QAAsB,EACtB,KAA4B;IAE5B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,MAAM,OAAO,CAAC,GAAG,CACtB,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CACxD,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAc,EAAE,QAAsB;IACpE,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,CAAC;QAE3C,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,KAAK,CACrC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAC7C,CAAC;QAEF,MAAM,CACJ,UAAU,CAAC,IAAI,GAAG,aAAa,EAC/B,8CAA8C,CAC/C,CAAC;QAEF,MAAM,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QACzD,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAEtE,MAAM,cAAc,GAAG,MAAM,YAAY,CACvC,QAAQ,EACR,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAC7B,CAAC;QAEF,sBAAsB,CAAC,cAAc,CAAC,CAAC;QAEvC,MAAM,OAAO,CAAC,GAAG,CACf,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;YAChC,4BAA4B;YAC5B,kDAAkD;YAClD,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;QAC9C,CAAC,CAAC,CACH,CAAC;QAEF,MAAM,iBAAiB,GAAG,MAAM,YAAY,CAC1C,QAAQ,EACR,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAC/B,CAAC;QAEF,MAAM,0BAA0B,GAC9B,6BAA6B,CAAC,iBAAiB,CAAC,CAAC;QAEnD,MAAM,KAAK,GAAG;YACZ,QAAQ;YACR,UAAU;YACV,OAAO;YACP,cAAc;YACd,iBAAiB,EAAE,0BAA0B;SAC9C,CAAC;QAEF,MAAM,mBAAmB,CAAC,KAAK,CAAC,CAAC;QAEjC,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,yBAAyB,MAAM,MAAM,eAAe,CAAC,KAAK,CAAC,GAAG,CAC/D,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,oBAAoB,CAClC,EAIS,EACT,OAAO,GAAG,IAAI;IAEd,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA0B,CAAC;IAEnD,OAAO,CACL,MAAc,EACd,YAAyC,EACzC,SAAkB,EACZ,EAAE;QACR,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,SAAS,EAAE,CAAC;QAErC,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACtB,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QAClC,CAAC;QAED,QAAQ,CAAC,GAAG,CACV,GAAG,EACH,UAAU,CAAC,GAAG,EAAE;YACd,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;YACpC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC,EAAE,OAAO,CAAC,CACZ,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,CAAC;IAC9C,WAAW,CAAC,UAAU;IACtB,WAAW,CAAC,SAAS;IACrB,WAAW,CAAC,YAAY;IACxB,WAAW,CAAC,YAAY;IACxB,WAAW,CAAC,WAAW;IACvB,WAAW,CAAC,aAAa;IACzB,WAAW,CAAC,QAAQ;CACZ,CAAC,CAAC;AAOZ;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,EAKS,EACT,OAAO,GAAG,KAAK;IAEf,MAAM,aAAa,GAAG,IAAI,GAAG,EAAkB,CAAC;IAChD,MAAM,YAAY,GAAG,IAAI,GAAG,EAMzB,CAAC;IAEJ,OAAO,CACL,MAAc,EACd,OAAyB,EACzB,OAAgB,EAChB,MAAc,EACR,EAAE;QACR,MAAM,GAAG,GAAG,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC;QACrD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC7C,MAAM,IAAI,GAAgD;YACxD,MAAM;YACN,OAAO;YACP,OAAO;YACP,MAAM;SACP,CAAC;QAEF,IAAI,GAAG,GAAG,QAAQ,IAAI,OAAO,EAAE,CAAC;YAC9B,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAC5B,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;YACZ,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;YACnB,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;QAED,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAE5B,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE;YACpB,IAAI;YACJ,KAAK,EAAE,UAAU,CAAC,GAAG,EAAE;gBACrB,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;gBACZ,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC3B,CAAC,EAAE,OAAO,CAAC;SACZ,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAoB;IAEpB,OAAO,kBAAkB,CAAC,QAAQ,CAAC,OAA2B,CAAC,CAAC;AAClE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AACrC,CAAC","sourcesContent":["import type { PermissionConstraint } from '@metamask/permission-controller';\nimport type { SnapId } from '@metamask/snaps-sdk';\nimport { assert, getErrorMessage } from '@metamask/snaps-sdk';\nimport {\n  MAX_FILE_SIZE,\n  encodeBase64,\n  getValidatedLocalizationFiles,\n  validateAuxiliaryFiles,\n  validateFetchedSnap,\n  HandlerType,\n} from '@metamask/snaps-utils';\nimport type { Json } from '@metamask/utils';\nimport deepEqual from 'fast-deep-equal';\n\nimport type { SnapLocation } from './snaps';\nimport { Timer } from './snaps/Timer';\n\n/**\n * Takes two objects and does a Set Difference of them.\n * Set Difference is generally defined as follows:\n * ```\n * 𝑥 ∈ A ∖ B ⟺ 𝑥 ∈ A ∧ 𝑥 ∉ B\n * ```\n * Meaning that the returned object contains all properties of A expect those that also\n * appear in B. Notice that properties that appear in B, but not in A, have no effect.\n *\n * @see [Set Difference]{@link https://proofwiki.org/wiki/Definition:Set_Difference}\n * @param objectA - The object on which the difference is being calculated.\n * @param objectB - The object whose properties will be removed from objectA.\n * @returns The objectA without properties from objectB.\n */\nexport function setDiff<\n  ObjectA extends Record<any, unknown>,\n  ObjectB extends Record<any, unknown>,\n>(objectA: ObjectA, objectB: ObjectB): Diff<ObjectA, ObjectB> {\n  return Object.entries(objectA).reduce<Record<any, unknown>>(\n    (acc, [key, value]) => {\n      if (!(key in objectB)) {\n        acc[key] = value;\n      }\n      return acc;\n    },\n    {},\n  ) as Diff<ObjectA, ObjectB>;\n}\n\n/**\n * Calculate a difference between two permissions objects.\n *\n * Similar to `setDiff` except for one additional condition:\n * Permissions in B should be removed from A if they exist in both and have differing caveats.\n *\n * @param permissionsA - An object containing one or more partial permissions.\n * @param permissionsB - An object containing one or more partial permissions to be subtracted from A.\n * @returns The permissions set A without properties from B.\n */\nexport function permissionsDiff<\n  PermissionsA extends Record<string, Pick<PermissionConstraint, 'caveats'>>,\n  PermissionsB extends Record<string, Pick<PermissionConstraint, 'caveats'>>,\n>(\n  permissionsA: PermissionsA,\n  permissionsB: PermissionsB,\n): Diff<PermissionsA, PermissionsB> {\n  return Object.entries(permissionsA).reduce<\n    Record<string, Pick<PermissionConstraint, 'caveats'>>\n  >((acc, [key, value]) => {\n    const isIncluded = key in permissionsB;\n    if (\n      !isIncluded ||\n      (isIncluded &&\n        !deepEqual(value.caveats ?? [], permissionsB[key].caveats ?? []))\n    ) {\n      acc[key] = value;\n    }\n    return acc;\n  }, {}) as Diff<PermissionsA, PermissionsB>;\n}\n\n/**\n * A Promise that delays its return for a given amount of milliseconds.\n *\n * @param ms - Milliseconds to delay the execution for.\n * @param result - The result to return from the Promise after delay.\n * @returns A promise that is void if no result provided, result otherwise.\n * @template Result - The `result`.\n */\nexport function delay<Result = void>(\n  ms: number,\n  result?: Result,\n): Promise<Result> & { cancel: () => void } {\n  return delayWithTimer(new Timer(ms), result);\n}\n\n/**\n * A Promise that delays it's return by using a pausable Timer.\n *\n * @param timer - Timer used to control the delay.\n * @param result - The result to return from the Promise after delay.\n * @returns A promise that is void if no result provided, result otherwise.\n * @template Result - The `result`.\n */\nexport function delayWithTimer<Result = void>(\n  timer: Timer,\n  result?: Result,\n): Promise<Result> & { cancel: () => void } {\n  let rejectFunc: (reason: Error) => void;\n  const promise: any = new Promise<Result>((resolve: any, reject) => {\n    timer.start(() => {\n      result === undefined ? resolve() : resolve(result);\n    });\n    rejectFunc = reject;\n  });\n\n  promise.cancel = () => {\n    if (timer.status !== 'finished') {\n      timer.cancel();\n      rejectFunc(new Error('The delay has been canceled.'));\n    }\n  };\n  return promise;\n}\n\n/*\n * We use a Symbol instead of rejecting the promise so that Errors thrown\n * by the main promise will propagate.\n */\nexport const hasTimedOut = Symbol(\n  'Used to check if the requested promise has timeout (see withTimeout)',\n);\n\n/**\n * Executes the given Promise, if the Timer expires before the Promise settles, we return earlier.\n *\n * NOTE:** The given Promise is not cancelled or interrupted, and will continue to execute uninterrupted. We will just discard its result if it does not complete before the timeout.\n *\n * @param promise - The promise that you want to execute.\n * @param timerOrMs - The timer controlling the timeout or a ms value.\n * @returns The resolved `PromiseValue`, or the hasTimedOut symbol if\n * returning early.\n * @template PromiseValue - The value of the Promise.\n */\nexport async function withTimeout<PromiseValue = void>(\n  promise: Promise<PromiseValue>,\n  timerOrMs: Timer | number,\n): Promise<PromiseValue | typeof hasTimedOut> {\n  const timer =\n    typeof timerOrMs === 'number' ? new Timer(timerOrMs) : timerOrMs;\n  const delayPromise = delayWithTimer(timer, hasTimedOut);\n  try {\n    return await Promise.race([promise, delayPromise]);\n  } finally {\n    delayPromise.cancel();\n  }\n}\n\n/**\n * Checks whether the type is composed of literal types\n *\n * @returns @type {true} if whole type is composed of literals, @type {false} if whole type is not literals, @type {boolean} if mixed\n * @example\n * ```\n * type t1 = IsLiteral<1 | 2 | \"asd\" | true>;\n * // t1 = true\n *\n * type t2 = IsLiteral<number | string>;\n * // t2 = false\n *\n * type t3 = IsLiteral<1 | string>;\n * // t3 = boolean\n *\n * const s = Symbol();\n * type t4 = IsLiteral<typeof s>;\n * // t4 = true\n *\n * type t5 = IsLiteral<symbol>\n * // t5 = false;\n * ```\n */\ntype IsLiteral<Type> = Type extends string | number | boolean | symbol\n  ? Extract<string | number | boolean | symbol, Type> extends never\n    ? true\n    : false\n  : false;\n\n/**\n * Returns all keys of an object, that are literal, as an union\n *\n * @example\n * ```\n * type t1 = _LiteralKeys<{a: number, b: 0, c: 'foo', d: string}>\n * // t1 = 'b' | 'c'\n * ```\n * @see [Literal types]{@link https://www.typescriptlang.org/docs/handbook/literal-types.html}\n */\ntype LiteralKeys<Type> = NonNullable<\n  {\n    [Key in keyof Type]: IsLiteral<Key> extends true ? Key : never;\n  }[keyof Type]\n>;\n\n/**\n * Returns all keys of an object, that are not literal, as an union\n *\n * @example\n * ```\n * type t1 = _NonLiteralKeys<{a: number, b: 0, c: 'foo', d: string}>\n * // t1 = 'a' | 'd'\n * ```\n * @see [Literal types]{@link https://www.typescriptlang.org/docs/handbook/literal-types.html}\n */\ntype NonLiteralKeys<Type> = NonNullable<\n  {\n    [Key in keyof Type]: IsLiteral<Key> extends false ? Key : never;\n  }[keyof Type]\n>;\n\n/**\n * A set difference of two objects based on their keys\n *\n * @example\n * ```\n * type t1 = Diff<{a: string, b: string}, {a: number}>\n * // t1 = {b: string};\n * type t2 = Diff<{a: string, 0: string}, Record<string, unknown>>;\n * // t2 = { a?: string, 0: string};\n * type t3 = Diff<{a: string, 0: string, 1: string}, Record<1 | string, unknown>>;\n * // t3 = {a?: string, 0: string}\n * ```\n * @see {@link setDiff} for the main use-case\n */\nexport type Diff<First, Second> = Omit<First, LiteralKeys<Second>> &\n  Partial<Pick<First, Extract<keyof First, NonLiteralKeys<Second>>>>;\n\n/**\n * Makes every specified property of the specified object type mutable.\n *\n * @template Type - The object whose readonly properties to make mutable.\n * @template TargetKey - The property key(s) to make mutable.\n */\nexport type Mutable<\n  Type extends Record<string, unknown>,\n  TargetKey extends string,\n> = {\n  -readonly [Key in keyof Pick<Type, TargetKey>]: Type[Key];\n} & {\n  [Key in keyof Omit<Type, TargetKey>]: Type[Key];\n};\n\n/**\n * Get all files in a Snap from an array of file paths.\n *\n * @param location - The location of the Snap.\n * @param files - The array of file paths.\n * @returns The array of files as {@link VirtualFile}s.\n */\nexport async function getSnapFiles(\n  location: SnapLocation,\n  files?: string[] | undefined,\n) {\n  if (!files || files.length === 0) {\n    return [];\n  }\n\n  return await Promise.all(\n    files.map(async (filePath) => location.fetch(filePath)),\n  );\n}\n\n/**\n * Fetch the Snap manifest, source code, and any other files from the given\n * location.\n *\n * @param snapId - The ID of the Snap to fetch.\n * @param location - The location of the Snap.\n * @returns The Snap files and location.\n * @throws If the Snap files are invalid, or if the Snap could not be fetched.\n */\nexport async function fetchSnap(snapId: SnapId, location: SnapLocation) {\n  try {\n    const manifest = await location.manifest();\n\n    const sourceCode = await location.fetch(\n      manifest.result.source.location.npm.filePath,\n    );\n\n    assert(\n      sourceCode.size < MAX_FILE_SIZE,\n      'Snap source code must be smaller than 64 MB.',\n    );\n\n    const { iconPath } = manifest.result.source.location.npm;\n    const svgIcon = iconPath ? await location.fetch(iconPath) : undefined;\n\n    const auxiliaryFiles = await getSnapFiles(\n      location,\n      manifest.result.source.files,\n    );\n\n    validateAuxiliaryFiles(auxiliaryFiles);\n\n    await Promise.all(\n      auxiliaryFiles.map(async (file) => {\n        // This should still be safe\n        // eslint-disable-next-line require-atomic-updates\n        file.data.base64 = await encodeBase64(file);\n      }),\n    );\n\n    const localizationFiles = await getSnapFiles(\n      location,\n      manifest.result.source.locales,\n    );\n\n    const validatedLocalizationFiles =\n      getValidatedLocalizationFiles(localizationFiles);\n\n    const files = {\n      manifest,\n      sourceCode,\n      svgIcon,\n      auxiliaryFiles,\n      localizationFiles: validatedLocalizationFiles,\n    };\n\n    await validateFetchedSnap(files);\n\n    return files;\n  } catch (error) {\n    throw new Error(\n      `Failed to fetch snap \"${snapId}\": ${getErrorMessage(error)}.`,\n    );\n  }\n}\n\n/**\n * Debounce persisting Snap state changes.\n *\n * @param fn - The function to debounce.\n * @param timeout - The timeout in milliseconds. Defaults to 1000.\n * @returns The debounced function.\n * @example\n * const originalUpdate = (snapId, newSnapState, encrypted) => {\n *   console.log(`Called with Snap ID: ${snapId} and state: ${newSnapState}`);\n * };\n *\n * const debouncedUpdate = debounce(originalUpdate);\n * debouncedFunction('npm:foo-snap', { foo: 'bar' }, false);\n */\nexport function debouncePersistState(\n  fn: (\n    snapId: SnapId,\n    newSnapState: Record<string, Json> | null,\n    encrypted: boolean,\n  ) => void,\n  timeout = 1000,\n) {\n  const timeouts = new Map<string, NodeJS.Timeout>();\n\n  return (\n    snapId: SnapId,\n    newSnapState: Record<string, Json> | null,\n    encrypted: boolean,\n  ): void => {\n    const key = `${snapId}-${encrypted}`;\n\n    if (timeouts.has(key)) {\n      clearTimeout(timeouts.get(key));\n    }\n\n    timeouts.set(\n      key,\n      setTimeout(() => {\n        fn(snapId, newSnapState, encrypted);\n        timeouts.delete(key);\n      }, timeout),\n    );\n  };\n}\n\n/**\n * Handlers allowed for tracking.\n */\nexport const TRACKABLE_HANDLERS = Object.freeze([\n  HandlerType.OnHomePage,\n  HandlerType.OnInstall,\n  HandlerType.OnNameLookup,\n  HandlerType.OnRpcRequest,\n  HandlerType.OnSignature,\n  HandlerType.OnTransaction,\n  HandlerType.OnUpdate,\n] as const);\n\n/**\n * A union type representing all possible trackable handler types.\n */\nexport type TrackableHandler = (typeof TRACKABLE_HANDLERS)[number];\n\n/**\n * Throttles event tracking calls per unique combination of parameters.\n *\n * @param fn - The tracking function to throttle.\n * @param timeout - The timeout in milliseconds. Defaults to 60000 (1 minute).\n * @returns The throttled function.\n */\nexport function throttleTracking(\n  fn: (\n    snapId: SnapId,\n    handler: TrackableHandler,\n    success: boolean,\n    origin: string,\n  ) => void,\n  timeout = 60000,\n) {\n  const previousCalls = new Map<string, number>();\n  const pendingCalls = new Map<\n    string,\n    {\n      args: [SnapId, TrackableHandler, boolean, string];\n      timer: ReturnType<typeof setTimeout> | null;\n    }\n  >();\n\n  return (\n    snapId: SnapId,\n    handler: TrackableHandler,\n    success: boolean,\n    origin: string,\n  ): void => {\n    const key = `${snapId}${handler}${success}${origin}`;\n    const now = Date.now();\n    const lastCall = previousCalls.get(key) ?? 0;\n    const args: [SnapId, TrackableHandler, boolean, string] = [\n      snapId,\n      handler,\n      success,\n      origin,\n    ];\n\n    if (now - lastCall >= timeout) {\n      previousCalls.set(key, now);\n      fn(...args);\n      return;\n    }\n\n    const pending = pendingCalls.get(key);\n    if (pending?.timer) {\n      clearTimeout(pending.timer);\n    }\n\n    previousCalls.set(key, now);\n\n    pendingCalls.set(key, {\n      args,\n      timer: setTimeout(() => {\n        fn(...args);\n        pendingCalls.delete(key);\n      }, timeout),\n    });\n  };\n}\n\n/**\n * Whether the handler type if allowed for tracking.\n *\n * @param handler Type of a handler.\n * @returns True if handler is allowed for tracking, false otherwise.\n */\nexport function isTrackableHandler(\n  handler: HandlerType,\n): handler is TrackableHandler {\n  return TRACKABLE_HANDLERS.includes(handler as TrackableHandler);\n}\n\n/**\n * Checks if the given Snap ID is a local Snap ID. This assumes the Snap ID is\n * validated before calling this function, as it only checks the prefix.\n *\n * @param snapId - The Snap ID to check.\n * @returns True if the Snap ID is a local Snap ID, false otherwise.\n */\nexport function isLocalSnapId(snapId: SnapId): boolean {\n  return snapId.startsWith('local:');\n}\n"]}