@metamask/snaps-controllers 0.31.0 → 0.32.0

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,20 +1,39 @@
1
- import { SnapId } from '@metamask/snaps-utils';
2
- import { SnapsRegistry, SnapsRegistryMetadata, SnapsRegistryRequest, SnapsRegistryResult } from './registry';
1
+ import { BaseControllerV2 as BaseController, RestrictedControllerMessenger } from '@metamask/base-controller';
2
+ import { SnapsRegistryDatabase } from '@metamask/snaps-registry';
3
+ import { Hex } from '@metamask/utils';
4
+ import { SnapsRegistry } from './registry';
5
+ declare type JsonSnapsRegistryUrl = {
6
+ registry: string;
7
+ signature: string;
8
+ };
3
9
  export declare type JsonSnapsRegistryArgs = {
10
+ messenger: SnapsRegistryMessenger;
11
+ state?: SnapsRegistryState;
4
12
  fetchFunction?: typeof fetch;
5
- url?: string;
13
+ url?: JsonSnapsRegistryUrl;
14
+ recentFetchThreshold?: number;
15
+ refetchOnAllowlistMiss?: boolean;
6
16
  failOnUnavailableRegistry?: boolean;
17
+ publicKey?: Hex;
18
+ };
19
+ export declare type GetResult = {
20
+ type: `${typeof controllerName}:get`;
21
+ handler: SnapsRegistry['get'];
22
+ };
23
+ export declare type GetMetadata = {
24
+ type: `${typeof controllerName}:getMetadata`;
25
+ handler: SnapsRegistry['getMetadata'];
26
+ };
27
+ export declare type SnapsRegistryActions = GetResult | GetMetadata;
28
+ export declare type SnapsRegistryEvents = never;
29
+ export declare type SnapsRegistryMessenger = RestrictedControllerMessenger<'SnapsRegistry', SnapsRegistryActions, SnapsRegistryEvents, SnapsRegistryActions['type'], SnapsRegistryEvents['type']>;
30
+ export declare type SnapsRegistryState = {
31
+ database: SnapsRegistryDatabase | null;
32
+ lastUpdated: number | null;
7
33
  };
8
- export declare class JsonSnapsRegistry implements SnapsRegistry {
34
+ declare const controllerName = "SnapsRegistry";
35
+ export declare class JsonSnapsRegistry extends BaseController<typeof controllerName, SnapsRegistryState, SnapsRegistryMessenger> {
9
36
  #private;
10
- constructor({ url, fetchFunction, failOnUnavailableRegistry, }?: JsonSnapsRegistryArgs);
11
- get(snaps: SnapsRegistryRequest): Promise<Record<SnapId, SnapsRegistryResult>>;
12
- /**
13
- * Get metadata for the given snap ID.
14
- *
15
- * @param snapId - The ID of the snap to get metadata for.
16
- * @returns The metadata for the given snap ID, or `null` if the snap is not
17
- * verified.
18
- */
19
- getMetadata(snapId: SnapId): Promise<SnapsRegistryMetadata | null>;
37
+ constructor({ messenger, state, url, publicKey, fetchFunction, recentFetchThreshold, failOnUnavailableRegistry, refetchOnAllowlistMiss, }: JsonSnapsRegistryArgs);
20
38
  }
39
+ export {};
@@ -10,65 +10,88 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  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");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _JsonSnapsRegistry_instances, _JsonSnapsRegistry_url, _JsonSnapsRegistry_database, _JsonSnapsRegistry_fetchFunction, _JsonSnapsRegistry_failOnUnavailableRegistry, _JsonSnapsRegistry_getDatabase, _JsonSnapsRegistry_getSingle;
13
+ var _JsonSnapsRegistry_instances, _JsonSnapsRegistry_url, _JsonSnapsRegistry_publicKey, _JsonSnapsRegistry_fetchFunction, _JsonSnapsRegistry_recentFetchThreshold, _JsonSnapsRegistry_refetchOnAllowlistMiss, _JsonSnapsRegistry_failOnUnavailableRegistry, _JsonSnapsRegistry_wasRecentlyFetched, _JsonSnapsRegistry_updateDatabase, _JsonSnapsRegistry_getDatabase, _JsonSnapsRegistry_getSingle, _JsonSnapsRegistry_get, _JsonSnapsRegistry_getMetadata, _JsonSnapsRegistry_verifySignature, _JsonSnapsRegistry_safeFetch;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.JsonSnapsRegistry = void 0;
16
+ const base_controller_1 = require("@metamask/base-controller");
17
+ const snaps_registry_1 = require("@metamask/snaps-registry");
16
18
  const utils_1 = require("@metamask/utils");
17
19
  const registry_1 = require("./registry");
18
20
  // TODO: Replace with a Codefi URL
19
- const SNAP_REGISTRY_URL = 'https://cdn.jsdelivr.net/gh/MetaMask/snaps-registry@main/src/registry.json';
20
- class JsonSnapsRegistry {
21
- constructor({ url = SNAP_REGISTRY_URL, fetchFunction = globalThis.fetch.bind(globalThis), failOnUnavailableRegistry = true, } = {}) {
21
+ const SNAP_REGISTRY_URL = 'https://cdn.jsdelivr.net/gh/MetaMask/snaps-registry@gh-pages/latest/registry.json';
22
+ const SNAP_REGISTRY_SIGNATURE_URL = 'https://cdn.jsdelivr.net/gh/MetaMask/snaps-registry@gh-pages/latest/signature.json';
23
+ const controllerName = 'SnapsRegistry';
24
+ const defaultState = {
25
+ database: null,
26
+ lastUpdated: null,
27
+ };
28
+ class JsonSnapsRegistry extends base_controller_1.BaseControllerV2 {
29
+ constructor({ messenger, state, url = {
30
+ registry: SNAP_REGISTRY_URL,
31
+ signature: SNAP_REGISTRY_SIGNATURE_URL,
32
+ }, publicKey, fetchFunction = globalThis.fetch.bind(globalThis), recentFetchThreshold = (0, utils_1.inMilliseconds)(5, utils_1.Duration.Minute), failOnUnavailableRegistry = true, refetchOnAllowlistMiss = true, }) {
33
+ super({
34
+ messenger,
35
+ metadata: {
36
+ database: { persist: true, anonymous: false },
37
+ lastUpdated: { persist: true, anonymous: false },
38
+ },
39
+ name: controllerName,
40
+ state: {
41
+ ...defaultState,
42
+ ...state,
43
+ },
44
+ });
22
45
  _JsonSnapsRegistry_instances.add(this);
23
46
  _JsonSnapsRegistry_url.set(this, void 0);
24
- _JsonSnapsRegistry_database.set(this, null);
47
+ _JsonSnapsRegistry_publicKey.set(this, void 0);
25
48
  _JsonSnapsRegistry_fetchFunction.set(this, void 0);
49
+ _JsonSnapsRegistry_recentFetchThreshold.set(this, void 0);
50
+ _JsonSnapsRegistry_refetchOnAllowlistMiss.set(this, void 0);
26
51
  _JsonSnapsRegistry_failOnUnavailableRegistry.set(this, void 0);
27
52
  __classPrivateFieldSet(this, _JsonSnapsRegistry_url, url, "f");
53
+ __classPrivateFieldSet(this, _JsonSnapsRegistry_publicKey, publicKey, "f");
28
54
  __classPrivateFieldSet(this, _JsonSnapsRegistry_fetchFunction, fetchFunction, "f");
55
+ __classPrivateFieldSet(this, _JsonSnapsRegistry_recentFetchThreshold, recentFetchThreshold, "f");
56
+ __classPrivateFieldSet(this, _JsonSnapsRegistry_refetchOnAllowlistMiss, refetchOnAllowlistMiss, "f");
29
57
  __classPrivateFieldSet(this, _JsonSnapsRegistry_failOnUnavailableRegistry, failOnUnavailableRegistry, "f");
30
- }
31
- async get(snaps) {
32
- return Object.entries(snaps).reduce(async (previousPromise, [snapId, snapInfo]) => {
33
- const result = await __classPrivateFieldGet(this, _JsonSnapsRegistry_instances, "m", _JsonSnapsRegistry_getSingle).call(this, snapId, snapInfo);
34
- const acc = await previousPromise;
35
- acc[snapId] = result;
36
- return acc;
37
- }, Promise.resolve({}));
38
- }
39
- /**
40
- * Get metadata for the given snap ID.
41
- *
42
- * @param snapId - The ID of the snap to get metadata for.
43
- * @returns The metadata for the given snap ID, or `null` if the snap is not
44
- * verified.
45
- */
46
- async getMetadata(snapId) {
47
- const database = await __classPrivateFieldGet(this, _JsonSnapsRegistry_instances, "m", _JsonSnapsRegistry_getDatabase).call(this);
48
- return database?.verifiedSnaps[snapId]?.metadata ?? null;
58
+ this.messagingSystem.registerActionHandler('SnapsRegistry:get', async (...args) => __classPrivateFieldGet(this, _JsonSnapsRegistry_instances, "m", _JsonSnapsRegistry_get).call(this, ...args));
59
+ this.messagingSystem.registerActionHandler('SnapsRegistry:getMetadata', async (...args) => __classPrivateFieldGet(this, _JsonSnapsRegistry_instances, "m", _JsonSnapsRegistry_getMetadata).call(this, ...args));
49
60
  }
50
61
  }
51
62
  exports.JsonSnapsRegistry = JsonSnapsRegistry;
52
- _JsonSnapsRegistry_url = new WeakMap(), _JsonSnapsRegistry_database = new WeakMap(), _JsonSnapsRegistry_fetchFunction = new WeakMap(), _JsonSnapsRegistry_failOnUnavailableRegistry = new WeakMap(), _JsonSnapsRegistry_instances = new WeakSet(), _JsonSnapsRegistry_getDatabase = async function _JsonSnapsRegistry_getDatabase() {
53
- if (__classPrivateFieldGet(this, _JsonSnapsRegistry_database, "f") === null) {
54
- // TODO: Decide if we should persist this between sessions
55
- try {
56
- const response = await __classPrivateFieldGet(this, _JsonSnapsRegistry_fetchFunction, "f").call(this, __classPrivateFieldGet(this, _JsonSnapsRegistry_url, "f"));
57
- if (!response.ok) {
58
- throw new Error('Failed to fetch Snaps registry.');
59
- }
60
- __classPrivateFieldSet(this, _JsonSnapsRegistry_database, await response.json(), "f");
61
- }
62
- catch {
63
- // Ignore
63
+ _JsonSnapsRegistry_url = new WeakMap(), _JsonSnapsRegistry_publicKey = new WeakMap(), _JsonSnapsRegistry_fetchFunction = new WeakMap(), _JsonSnapsRegistry_recentFetchThreshold = new WeakMap(), _JsonSnapsRegistry_refetchOnAllowlistMiss = new WeakMap(), _JsonSnapsRegistry_failOnUnavailableRegistry = new WeakMap(), _JsonSnapsRegistry_instances = new WeakSet(), _JsonSnapsRegistry_wasRecentlyFetched = function _JsonSnapsRegistry_wasRecentlyFetched() {
64
+ return (this.state.lastUpdated &&
65
+ Date.now() - this.state.lastUpdated < __classPrivateFieldGet(this, _JsonSnapsRegistry_recentFetchThreshold, "f"));
66
+ }, _JsonSnapsRegistry_updateDatabase = async function _JsonSnapsRegistry_updateDatabase() {
67
+ // No-op if we recently fetched the registry.
68
+ if (__classPrivateFieldGet(this, _JsonSnapsRegistry_instances, "m", _JsonSnapsRegistry_wasRecentlyFetched).call(this)) {
69
+ return;
70
+ }
71
+ try {
72
+ const database = await __classPrivateFieldGet(this, _JsonSnapsRegistry_instances, "m", _JsonSnapsRegistry_safeFetch).call(this, __classPrivateFieldGet(this, _JsonSnapsRegistry_url, "f").registry);
73
+ if (__classPrivateFieldGet(this, _JsonSnapsRegistry_publicKey, "f")) {
74
+ const signature = await __classPrivateFieldGet(this, _JsonSnapsRegistry_instances, "m", _JsonSnapsRegistry_safeFetch).call(this, __classPrivateFieldGet(this, _JsonSnapsRegistry_url, "f").signature);
75
+ await __classPrivateFieldGet(this, _JsonSnapsRegistry_instances, "m", _JsonSnapsRegistry_verifySignature).call(this, database, signature);
64
76
  }
77
+ this.update((state) => {
78
+ state.database = JSON.parse(database);
79
+ state.lastUpdated = Date.now();
80
+ });
81
+ }
82
+ catch {
83
+ // Ignore
84
+ }
85
+ }, _JsonSnapsRegistry_getDatabase = async function _JsonSnapsRegistry_getDatabase() {
86
+ if (this.state.database === null) {
87
+ await __classPrivateFieldGet(this, _JsonSnapsRegistry_instances, "m", _JsonSnapsRegistry_updateDatabase).call(this);
65
88
  }
66
89
  // If the database is still null and we require it, throw.
67
- if (__classPrivateFieldGet(this, _JsonSnapsRegistry_failOnUnavailableRegistry, "f") && __classPrivateFieldGet(this, _JsonSnapsRegistry_database, "f") === null) {
90
+ if (__classPrivateFieldGet(this, _JsonSnapsRegistry_failOnUnavailableRegistry, "f") && this.state.database === null) {
68
91
  throw new Error('Snaps registry is unavailable, installation blocked.');
69
92
  }
70
- return __classPrivateFieldGet(this, _JsonSnapsRegistry_database, "f");
71
- }, _JsonSnapsRegistry_getSingle = async function _JsonSnapsRegistry_getSingle(snapId, snapInfo) {
93
+ return this.state.database;
94
+ }, _JsonSnapsRegistry_getSingle = async function _JsonSnapsRegistry_getSingle(snapId, snapInfo, refetch = false) {
72
95
  const database = await __classPrivateFieldGet(this, _JsonSnapsRegistry_instances, "m", _JsonSnapsRegistry_getDatabase).call(this);
73
96
  const blockedEntry = database?.blockedSnaps.find((blocked) => {
74
97
  if ('id' in blocked) {
@@ -88,6 +111,60 @@ _JsonSnapsRegistry_url = new WeakMap(), _JsonSnapsRegistry_database = new WeakMa
88
111
  if (version && version.checksum === snapInfo.checksum) {
89
112
  return { status: registry_1.SnapsRegistryStatus.Verified };
90
113
  }
114
+ // For now, if we have an allowlist miss, we can refetch once and try again.
115
+ if (__classPrivateFieldGet(this, _JsonSnapsRegistry_refetchOnAllowlistMiss, "f") && !refetch) {
116
+ await __classPrivateFieldGet(this, _JsonSnapsRegistry_instances, "m", _JsonSnapsRegistry_updateDatabase).call(this);
117
+ return __classPrivateFieldGet(this, _JsonSnapsRegistry_instances, "m", _JsonSnapsRegistry_getSingle).call(this, snapId, snapInfo, true);
118
+ }
91
119
  return { status: registry_1.SnapsRegistryStatus.Unverified };
120
+ }, _JsonSnapsRegistry_get = async function _JsonSnapsRegistry_get(snaps) {
121
+ return Object.entries(snaps).reduce(async (previousPromise, [snapId, snapInfo]) => {
122
+ const result = await __classPrivateFieldGet(this, _JsonSnapsRegistry_instances, "m", _JsonSnapsRegistry_getSingle).call(this, snapId, snapInfo);
123
+ const acc = await previousPromise;
124
+ acc[snapId] = result;
125
+ return acc;
126
+ }, Promise.resolve({}));
127
+ }, _JsonSnapsRegistry_getMetadata =
128
+ /**
129
+ * Get metadata for the given snap ID.
130
+ *
131
+ * @param snapId - The ID of the snap to get metadata for.
132
+ * @returns The metadata for the given snap ID, or `null` if the snap is not
133
+ * verified.
134
+ */
135
+ async function _JsonSnapsRegistry_getMetadata(snapId) {
136
+ const database = await __classPrivateFieldGet(this, _JsonSnapsRegistry_instances, "m", _JsonSnapsRegistry_getDatabase).call(this);
137
+ return database?.verifiedSnaps[snapId]?.metadata ?? null;
138
+ }, _JsonSnapsRegistry_verifySignature =
139
+ /**
140
+ * Verify the signature of the registry.
141
+ *
142
+ * @param database - The registry database.
143
+ * @param signature - The signature of the registry.
144
+ * @throws If the signature is invalid.
145
+ * @private
146
+ */
147
+ async function _JsonSnapsRegistry_verifySignature(database, signature) {
148
+ (0, utils_1.assert)(__classPrivateFieldGet(this, _JsonSnapsRegistry_publicKey, "f"), 'No public key provided.');
149
+ const valid = (0, snaps_registry_1.verify)({
150
+ registry: database,
151
+ signature: JSON.parse(signature),
152
+ publicKey: __classPrivateFieldGet(this, _JsonSnapsRegistry_publicKey, "f"),
153
+ });
154
+ (0, utils_1.assert)(valid, 'Invalid registry signature.');
155
+ }, _JsonSnapsRegistry_safeFetch =
156
+ /**
157
+ * Fetch the given URL, throwing if the response is not OK.
158
+ *
159
+ * @param url - The URL to fetch.
160
+ * @returns The response body.
161
+ * @private
162
+ */
163
+ async function _JsonSnapsRegistry_safeFetch(url) {
164
+ const response = await __classPrivateFieldGet(this, _JsonSnapsRegistry_fetchFunction, "f").call(this, url);
165
+ if (!response.ok) {
166
+ throw new Error(`Failed to fetch ${url}.`);
167
+ }
168
+ return await response.text();
92
169
  };
93
170
  //# sourceMappingURL=json.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"json.js","sourceRoot":"","sources":["../../../src/snaps/registry/json.ts"],"names":[],"mappings":"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","sourcesContent":["import { SnapsRegistryDatabase } from '@metamask/snaps-registry';\nimport { SnapId } from '@metamask/snaps-utils';\nimport { satisfiesVersionRange } from '@metamask/utils';\n\nimport {\n SnapsRegistry,\n SnapsRegistryInfo,\n SnapsRegistryMetadata,\n SnapsRegistryRequest,\n SnapsRegistryResult,\n SnapsRegistryStatus,\n} from './registry';\n\n// TODO: Replace with a Codefi URL\nconst SNAP_REGISTRY_URL =\n 'https://cdn.jsdelivr.net/gh/MetaMask/snaps-registry@main/src/registry.json';\n\nexport type JsonSnapsRegistryArgs = {\n fetchFunction?: typeof fetch;\n url?: string;\n failOnUnavailableRegistry?: boolean;\n};\n\nexport class JsonSnapsRegistry implements SnapsRegistry {\n #url: string;\n\n #database: SnapsRegistryDatabase | null = null;\n\n #fetchFunction: typeof fetch;\n\n #failOnUnavailableRegistry: boolean;\n\n constructor({\n url = SNAP_REGISTRY_URL,\n fetchFunction = globalThis.fetch.bind(globalThis),\n failOnUnavailableRegistry = true,\n }: JsonSnapsRegistryArgs = {}) {\n this.#url = url;\n this.#fetchFunction = fetchFunction;\n this.#failOnUnavailableRegistry = failOnUnavailableRegistry;\n }\n\n async #getDatabase(): Promise<SnapsRegistryDatabase | null> {\n if (this.#database === null) {\n // TODO: Decide if we should persist this between sessions\n try {\n const response = await this.#fetchFunction(this.#url);\n if (!response.ok) {\n throw new Error('Failed to fetch Snaps registry.');\n }\n this.#database = await response.json();\n } catch {\n // Ignore\n }\n }\n // If the database is still null and we require it, throw.\n if (this.#failOnUnavailableRegistry && this.#database === null) {\n throw new Error('Snaps registry is unavailable, installation blocked.');\n }\n return this.#database;\n }\n\n async #getSingle(snapId: SnapId, snapInfo: SnapsRegistryInfo) {\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: SnapsRegistryStatus.Blocked,\n reason: blockedEntry.reason,\n };\n }\n\n const verified = database?.verifiedSnaps[snapId];\n const version = verified?.versions?.[snapInfo.version];\n if (version && version.checksum === snapInfo.checksum) {\n return { status: SnapsRegistryStatus.Verified };\n }\n return { status: SnapsRegistryStatus.Unverified };\n }\n\n public async get(\n snaps: SnapsRegistryRequest,\n ): Promise<Record<SnapId, SnapsRegistryResult>> {\n return Object.entries(snaps).reduce<\n Promise<Record<SnapId, SnapsRegistryResult>>\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 * Get metadata for the given snap ID.\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 public async getMetadata(\n snapId: SnapId,\n ): Promise<SnapsRegistryMetadata | null> {\n const database = await this.#getDatabase();\n return database?.verifiedSnaps[snapId]?.metadata ?? null;\n }\n}\n"]}
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../../src/snaps/registry/json.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BaseControllerV2 as BaseController,\n RestrictedControllerMessenger,\n} from '@metamask/base-controller';\nimport { SnapsRegistryDatabase, verify } from '@metamask/snaps-registry';\nimport { SnapId } from '@metamask/snaps-utils';\nimport {\n assert,\n Duration,\n Hex,\n inMilliseconds,\n satisfiesVersionRange,\n} from '@metamask/utils';\n\nimport {\n SnapsRegistry,\n SnapsRegistryInfo,\n SnapsRegistryMetadata,\n SnapsRegistryRequest,\n SnapsRegistryResult,\n SnapsRegistryStatus,\n} from './registry';\n\n// TODO: Replace with a Codefi URL\nconst SNAP_REGISTRY_URL =\n 'https://cdn.jsdelivr.net/gh/MetaMask/snaps-registry@gh-pages/latest/registry.json';\n\nconst SNAP_REGISTRY_SIGNATURE_URL =\n 'https://cdn.jsdelivr.net/gh/MetaMask/snaps-registry@gh-pages/latest/signature.json';\n\ntype JsonSnapsRegistryUrl = {\n registry: string;\n signature: string;\n};\n\nexport type JsonSnapsRegistryArgs = {\n messenger: SnapsRegistryMessenger;\n state?: SnapsRegistryState;\n fetchFunction?: typeof fetch;\n url?: JsonSnapsRegistryUrl;\n recentFetchThreshold?: number;\n refetchOnAllowlistMiss?: boolean;\n failOnUnavailableRegistry?: boolean;\n publicKey?: Hex;\n};\n\nexport type GetResult = {\n type: `${typeof controllerName}:get`;\n handler: SnapsRegistry['get'];\n};\n\nexport type GetMetadata = {\n type: `${typeof controllerName}:getMetadata`;\n handler: SnapsRegistry['getMetadata'];\n};\n\nexport type SnapsRegistryActions = GetResult | GetMetadata;\n\nexport type SnapsRegistryEvents = never;\n\nexport type SnapsRegistryMessenger = RestrictedControllerMessenger<\n 'SnapsRegistry',\n SnapsRegistryActions,\n SnapsRegistryEvents,\n SnapsRegistryActions['type'],\n SnapsRegistryEvents['type']\n>;\n\nexport type SnapsRegistryState = {\n database: SnapsRegistryDatabase | null;\n lastUpdated: number | null;\n};\n\nconst controllerName = 'SnapsRegistry';\n\nconst defaultState = {\n database: null,\n lastUpdated: null,\n};\n\nexport class JsonSnapsRegistry extends BaseController<\n typeof controllerName,\n SnapsRegistryState,\n SnapsRegistryMessenger\n> {\n #url: JsonSnapsRegistryUrl;\n\n #publicKey?: Hex;\n\n #fetchFunction: typeof fetch;\n\n #recentFetchThreshold: number;\n\n #refetchOnAllowlistMiss: boolean;\n\n #failOnUnavailableRegistry: boolean;\n\n constructor({\n messenger,\n state,\n url = {\n registry: SNAP_REGISTRY_URL,\n signature: SNAP_REGISTRY_SIGNATURE_URL,\n },\n publicKey,\n fetchFunction = globalThis.fetch.bind(globalThis),\n recentFetchThreshold = inMilliseconds(5, Duration.Minute),\n failOnUnavailableRegistry = true,\n refetchOnAllowlistMiss = true,\n }: JsonSnapsRegistryArgs) {\n super({\n messenger,\n metadata: {\n database: { persist: true, anonymous: false },\n lastUpdated: { persist: true, anonymous: false },\n },\n name: controllerName,\n state: {\n ...defaultState,\n ...state,\n },\n });\n this.#url = url;\n this.#publicKey = publicKey;\n this.#fetchFunction = fetchFunction;\n this.#recentFetchThreshold = recentFetchThreshold;\n this.#refetchOnAllowlistMiss = refetchOnAllowlistMiss;\n this.#failOnUnavailableRegistry = failOnUnavailableRegistry;\n\n this.messagingSystem.registerActionHandler(\n 'SnapsRegistry:get',\n async (...args) => this.#get(...args),\n );\n this.messagingSystem.registerActionHandler(\n 'SnapsRegistry:getMetadata',\n async (...args) => this.#getMetadata(...args),\n );\n }\n\n #wasRecentlyFetched() {\n return (\n this.state.lastUpdated &&\n Date.now() - this.state.lastUpdated < this.#recentFetchThreshold\n );\n }\n\n async #updateDatabase() {\n // No-op if we recently fetched the registry.\n if (this.#wasRecentlyFetched()) {\n return;\n }\n\n try {\n const database = await this.#safeFetch(this.#url.registry);\n\n if (this.#publicKey) {\n const signature = await this.#safeFetch(this.#url.signature);\n await this.#verifySignature(database, signature);\n }\n\n this.update((state) => {\n state.database = JSON.parse(database);\n state.lastUpdated = Date.now();\n });\n } catch {\n // Ignore\n }\n }\n\n async #getDatabase(): Promise<SnapsRegistryDatabase | null> {\n if (this.state.database === null) {\n await this.#updateDatabase();\n }\n\n // If the database is still null and we require it, throw.\n if (this.#failOnUnavailableRegistry && this.state.database === null) {\n throw new Error('Snaps registry is unavailable, installation blocked.');\n }\n return this.state.database;\n }\n\n async #getSingle(\n snapId: SnapId,\n snapInfo: SnapsRegistryInfo,\n refetch = false,\n ): Promise<SnapsRegistryResult> {\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: SnapsRegistryStatus.Blocked,\n reason: blockedEntry.reason,\n };\n }\n\n const verified = database?.verifiedSnaps[snapId];\n const version = verified?.versions?.[snapInfo.version];\n if (version && version.checksum === snapInfo.checksum) {\n return { status: SnapsRegistryStatus.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.#updateDatabase();\n return this.#getSingle(snapId, snapInfo, true);\n }\n return { status: SnapsRegistryStatus.Unverified };\n }\n\n async #get(\n snaps: SnapsRegistryRequest,\n ): Promise<Record<SnapId, SnapsRegistryResult>> {\n return Object.entries(snaps).reduce<\n Promise<Record<SnapId, SnapsRegistryResult>>\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 * Get metadata for the given snap ID.\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 async #getMetadata(snapId: SnapId): Promise<SnapsRegistryMetadata | null> {\n const database = await this.#getDatabase();\n return 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 * @private\n */\n async #verifySignature(database: string, signature: string) {\n assert(this.#publicKey, 'No public key provided.');\n\n const valid = verify({\n registry: database,\n signature: JSON.parse(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);\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}.`);\n }\n\n return await response.text();\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask/snaps-controllers",
3
- "version": "0.31.0",
3
+ "version": "0.32.0",
4
4
  "description": "Controllers for MetaMask Snaps.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,12 +37,12 @@
37
37
  "@metamask/approval-controller": "^2.0.0",
38
38
  "@metamask/base-controller": "^2.0.0",
39
39
  "@metamask/object-multiplex": "^1.1.0",
40
- "@metamask/permission-controller": "^3.0.0",
41
- "@metamask/post-message-stream": "^6.1.0",
42
- "@metamask/rpc-methods": "^0.31.0",
43
- "@metamask/snaps-execution-environments": "^0.31.0",
44
- "@metamask/snaps-registry": "^1.1.1",
45
- "@metamask/snaps-utils": "^0.31.0",
40
+ "@metamask/permission-controller": "^3.1.0",
41
+ "@metamask/post-message-stream": "^6.1.1",
42
+ "@metamask/rpc-methods": "^0.32.0",
43
+ "@metamask/snaps-execution-environments": "^0.32.0",
44
+ "@metamask/snaps-registry": "^1.2.0",
45
+ "@metamask/snaps-utils": "^0.32.0",
46
46
  "@metamask/subject-metadata-controller": "^2.0.0",
47
47
  "@metamask/utils": "^5.0.0",
48
48
  "@xstate/fsm": "^2.0.0",