@metamask/snaps-controllers 9.7.0 → 9.9.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.
Files changed (90) hide show
  1. package/CHANGELOG.md +142 -1
  2. package/dist/cronjob/CronjobController.cjs +21 -34
  3. package/dist/cronjob/CronjobController.cjs.map +1 -1
  4. package/dist/cronjob/CronjobController.mjs +21 -34
  5. package/dist/cronjob/CronjobController.mjs.map +1 -1
  6. package/dist/insights/SnapInsightsController.cjs +194 -144
  7. package/dist/insights/SnapInsightsController.cjs.map +1 -1
  8. package/dist/insights/SnapInsightsController.mjs +193 -143
  9. package/dist/insights/SnapInsightsController.mjs.map +1 -1
  10. package/dist/interface/SnapInterfaceController.cjs +90 -65
  11. package/dist/interface/SnapInterfaceController.cjs.map +1 -1
  12. package/dist/interface/SnapInterfaceController.d.cts +2 -1
  13. package/dist/interface/SnapInterfaceController.d.cts.map +1 -1
  14. package/dist/interface/SnapInterfaceController.d.mts +2 -1
  15. package/dist/interface/SnapInterfaceController.d.mts.map +1 -1
  16. package/dist/interface/SnapInterfaceController.mjs +90 -65
  17. package/dist/interface/SnapInterfaceController.mjs.map +1 -1
  18. package/dist/services/AbstractExecutionService.cjs +71 -77
  19. package/dist/services/AbstractExecutionService.cjs.map +1 -1
  20. package/dist/services/AbstractExecutionService.mjs +71 -77
  21. package/dist/services/AbstractExecutionService.mjs.map +1 -1
  22. package/dist/services/ProxyPostMessageStream.cjs +19 -26
  23. package/dist/services/ProxyPostMessageStream.cjs.map +1 -1
  24. package/dist/services/ProxyPostMessageStream.mjs +19 -26
  25. package/dist/services/ProxyPostMessageStream.mjs.map +1 -1
  26. package/dist/services/iframe/IframeExecutionService.cjs +1 -0
  27. package/dist/services/iframe/IframeExecutionService.cjs.map +1 -1
  28. package/dist/services/iframe/IframeExecutionService.mjs +1 -0
  29. package/dist/services/iframe/IframeExecutionService.mjs.map +1 -1
  30. package/dist/services/offscreen/OffscreenExecutionService.cjs +3 -16
  31. package/dist/services/offscreen/OffscreenExecutionService.cjs.map +1 -1
  32. package/dist/services/offscreen/OffscreenExecutionService.mjs +3 -16
  33. package/dist/services/offscreen/OffscreenExecutionService.mjs.map +1 -1
  34. package/dist/services/proxy/ProxyExecutionService.cjs +4 -17
  35. package/dist/services/proxy/ProxyExecutionService.cjs.map +1 -1
  36. package/dist/services/proxy/ProxyExecutionService.mjs +4 -17
  37. package/dist/services/proxy/ProxyExecutionService.mjs.map +1 -1
  38. package/dist/services/webview/WebViewExecutionService.cjs +9 -23
  39. package/dist/services/webview/WebViewExecutionService.cjs.map +1 -1
  40. package/dist/services/webview/WebViewExecutionService.mjs +9 -23
  41. package/dist/services/webview/WebViewExecutionService.mjs.map +1 -1
  42. package/dist/services/webview/WebViewMessageStream.cjs +12 -25
  43. package/dist/services/webview/WebViewMessageStream.cjs.map +1 -1
  44. package/dist/services/webview/WebViewMessageStream.d.cts +1 -2
  45. package/dist/services/webview/WebViewMessageStream.d.cts.map +1 -1
  46. package/dist/services/webview/WebViewMessageStream.d.mts +1 -2
  47. package/dist/services/webview/WebViewMessageStream.d.mts.map +1 -1
  48. package/dist/services/webview/WebViewMessageStream.mjs +12 -25
  49. package/dist/services/webview/WebViewMessageStream.mjs.map +1 -1
  50. package/dist/services/webview/index.cjs +1 -0
  51. package/dist/services/webview/index.cjs.map +1 -1
  52. package/dist/services/webview/index.d.cts +1 -0
  53. package/dist/services/webview/index.d.cts.map +1 -1
  54. package/dist/services/webview/index.d.mts +1 -0
  55. package/dist/services/webview/index.d.mts.map +1 -1
  56. package/dist/services/webview/index.mjs +1 -0
  57. package/dist/services/webview/index.mjs.map +1 -1
  58. package/dist/services/webworker/WebWorkerExecutionService.cjs +10 -23
  59. package/dist/services/webworker/WebWorkerExecutionService.cjs.map +1 -1
  60. package/dist/services/webworker/WebWorkerExecutionService.mjs +10 -23
  61. package/dist/services/webworker/WebWorkerExecutionService.mjs.map +1 -1
  62. package/dist/snaps/RequestQueue.cjs +2 -0
  63. package/dist/snaps/RequestQueue.cjs.map +1 -1
  64. package/dist/snaps/RequestQueue.mjs +2 -0
  65. package/dist/snaps/RequestQueue.mjs.map +1 -1
  66. package/dist/snaps/SnapController.cjs +1141 -1001
  67. package/dist/snaps/SnapController.cjs.map +1 -1
  68. package/dist/snaps/SnapController.mjs +1140 -1000
  69. package/dist/snaps/SnapController.mjs.map +1 -1
  70. package/dist/snaps/Timer.cjs +1 -0
  71. package/dist/snaps/Timer.cjs.map +1 -1
  72. package/dist/snaps/Timer.mjs +1 -0
  73. package/dist/snaps/Timer.mjs.map +1 -1
  74. package/dist/snaps/location/http.cjs +11 -7
  75. package/dist/snaps/location/http.cjs.map +1 -1
  76. package/dist/snaps/location/http.mjs +11 -7
  77. package/dist/snaps/location/http.mjs.map +1 -1
  78. package/dist/snaps/location/local.cjs +4 -17
  79. package/dist/snaps/location/local.cjs.map +1 -1
  80. package/dist/snaps/location/local.mjs +4 -17
  81. package/dist/snaps/location/local.mjs.map +1 -1
  82. package/dist/snaps/location/npm.cjs +25 -37
  83. package/dist/snaps/location/npm.cjs.map +1 -1
  84. package/dist/snaps/location/npm.mjs +25 -37
  85. package/dist/snaps/location/npm.mjs.map +1 -1
  86. package/dist/snaps/registry/json.cjs +172 -173
  87. package/dist/snaps/registry/json.cjs.map +1 -1
  88. package/dist/snaps/registry/json.mjs +171 -172
  89. package/dist/snaps/registry/json.mjs.map +1 -1
  90. package/package.json +38 -23
@@ -1,16 +1,4 @@
1
1
  "use strict";
2
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
- if (kind === "m") throw new TypeError("Private method is not writable");
4
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
- };
8
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
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
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
- };
13
- var _SnapController_instances, _SnapController_closeAllConnections, _SnapController_dynamicPermissions, _SnapController_environmentEndowmentPermissions, _SnapController_excludedPermissions, _SnapController_featureFlags, _SnapController_fetchFunction, _SnapController_idleTimeCheckInterval, _SnapController_maxIdleTime, _SnapController_encryptor, _SnapController_getMnemonic, _SnapController_getFeatureFlags, _SnapController_detectSnapLocation, _SnapController_snapsRuntimeData, _SnapController_rollbackSnapshots, _SnapController_timeoutForLastRequestStatus, _SnapController_statusMachine, _SnapController_preinstalledSnaps, _SnapController_initializeStateMachine, _SnapController_registerMessageHandlers, _SnapController_handlePreinstalledSnaps, _SnapController_pollForLastRequestStatus, _SnapController_blockSnap, _SnapController_unblockSnap, _SnapController_assertIsInstallAllowed, _SnapController_assertCanInstallSnaps, _SnapController_assertCanUsePlatform, _SnapController_stopSnapsLastRequestPastMax, _SnapController_transition, _SnapController_terminateSnap, _SnapController_getSnapEncryptionKey, _SnapController_decryptSnapState, _SnapController_encryptSnapState, _SnapController_handleInitialConnections, _SnapController_addSnapToSubject, _SnapController_removeSnapFromSubjects, _SnapController_revokeAllSnapPermissions, _SnapController_createApproval, _SnapController_updateApproval, _SnapController_resolveAllowlistVersion, _SnapController_add, _SnapController_startSnap, _SnapController_getEndowments, _SnapController_set, _SnapController_validateSnapPermissions, _SnapController_getExecutionTimeout, _SnapController_getRpcRequestHandler, _SnapController_createInterface, _SnapController_assertInterfaceExists, _SnapController_transformSnapRpcRequestResult, _SnapController_assertSnapRpcRequestResult, _SnapController_recordSnapRpcRequestStart, _SnapController_recordSnapRpcRequestFinish, _SnapController_getRollbackSnapshot, _SnapController_createRollbackSnapshot, _SnapController_rollbackSnap, _SnapController_rollbackSnaps, _SnapController_getRuntime, _SnapController_getRuntimeExpect, _SnapController_setupRuntime, _SnapController_calculatePermissionsChange, _SnapController_isSubjectConnectedToSnap, _SnapController_calculateConnectionsChange, _SnapController_updatePermissions, _SnapController_isValidUpdate, _SnapController_callLifecycleHook;
14
2
  Object.defineProperty(exports, "__esModule", { value: true });
15
3
  exports.SnapController = exports.SNAP_APPROVAL_RESULT = exports.SNAP_APPROVAL_UPDATE = exports.SNAP_APPROVAL_INSTALL = exports.controllerName = void 0;
16
4
  const base_controller_1 = require("@metamask/base-controller");
@@ -71,6 +59,25 @@ const name = 'SnapController';
71
59
  * - Start: Initializes the snap in its SES realm with the authorized permissions.
72
60
  */
73
61
  class SnapController extends base_controller_1.BaseController {
62
+ #closeAllConnections;
63
+ #dynamicPermissions;
64
+ #environmentEndowmentPermissions;
65
+ #excludedPermissions;
66
+ #featureFlags;
67
+ #fetchFunction;
68
+ #idleTimeCheckInterval;
69
+ #maxIdleTime;
70
+ // This property cannot be hash private yet because of tests.
71
+ maxRequestTime;
72
+ #encryptor;
73
+ #getMnemonic;
74
+ #getFeatureFlags;
75
+ #detectSnapLocation;
76
+ #snapsRuntimeData;
77
+ #rollbackSnapshots;
78
+ #timeoutForLastRequestStatus;
79
+ #statusMachine;
80
+ #preinstalledSnaps;
74
81
  constructor({ closeAllConnections, messenger, state, dynamicPermissions = ['eth_accounts'], environmentEndowmentPermissions = [], excludedPermissions = {}, idleTimeCheckInterval = (0, utils_1.inMilliseconds)(5, utils_1.Duration.Second), maxIdleTime = (0, utils_1.inMilliseconds)(30, utils_1.Duration.Second), maxRequestTime = (0, utils_1.inMilliseconds)(60, utils_1.Duration.Second), fetchFunction = globalThis.fetch.bind(undefined), featureFlags = {}, detectSnapLocation: detectSnapLocationFunction = location_1.detectSnapLocation, preinstalledSnaps = null, encryptor, getMnemonic, getFeatureFlags = () => ({}), }) {
75
82
  super({
76
83
  messenger,
@@ -110,65 +117,202 @@ class SnapController extends base_controller_1.BaseController {
110
117
  ...state,
111
118
  },
112
119
  });
113
- _SnapController_instances.add(this);
114
- _SnapController_closeAllConnections.set(this, void 0);
115
- _SnapController_dynamicPermissions.set(this, void 0);
116
- _SnapController_environmentEndowmentPermissions.set(this, void 0);
117
- _SnapController_excludedPermissions.set(this, void 0);
118
- _SnapController_featureFlags.set(this, void 0);
119
- _SnapController_fetchFunction.set(this, void 0);
120
- _SnapController_idleTimeCheckInterval.set(this, void 0);
121
- _SnapController_maxIdleTime.set(this, void 0);
122
- _SnapController_encryptor.set(this, void 0);
123
- _SnapController_getMnemonic.set(this, void 0);
124
- _SnapController_getFeatureFlags.set(this, void 0);
125
- _SnapController_detectSnapLocation.set(this, void 0);
126
- _SnapController_snapsRuntimeData.set(this, void 0);
127
- _SnapController_rollbackSnapshots.set(this, void 0);
128
- _SnapController_timeoutForLastRequestStatus.set(this, void 0);
129
- _SnapController_statusMachine.set(this, void 0);
130
- _SnapController_preinstalledSnaps.set(this, void 0);
131
- __classPrivateFieldSet(this, _SnapController_closeAllConnections, closeAllConnections, "f");
132
- __classPrivateFieldSet(this, _SnapController_dynamicPermissions, dynamicPermissions, "f");
133
- __classPrivateFieldSet(this, _SnapController_environmentEndowmentPermissions, environmentEndowmentPermissions, "f");
134
- __classPrivateFieldSet(this, _SnapController_excludedPermissions, excludedPermissions, "f");
135
- __classPrivateFieldSet(this, _SnapController_featureFlags, featureFlags, "f");
136
- __classPrivateFieldSet(this, _SnapController_fetchFunction, fetchFunction, "f");
137
- __classPrivateFieldSet(this, _SnapController_idleTimeCheckInterval, idleTimeCheckInterval, "f");
138
- __classPrivateFieldSet(this, _SnapController_maxIdleTime, maxIdleTime, "f");
120
+ this.#closeAllConnections = closeAllConnections;
121
+ this.#dynamicPermissions = dynamicPermissions;
122
+ this.#environmentEndowmentPermissions = environmentEndowmentPermissions;
123
+ this.#excludedPermissions = excludedPermissions;
124
+ this.#featureFlags = featureFlags;
125
+ this.#fetchFunction = fetchFunction;
126
+ this.#idleTimeCheckInterval = idleTimeCheckInterval;
127
+ this.#maxIdleTime = maxIdleTime;
139
128
  this.maxRequestTime = maxRequestTime;
140
- __classPrivateFieldSet(this, _SnapController_detectSnapLocation, detectSnapLocationFunction, "f");
141
- __classPrivateFieldSet(this, _SnapController_encryptor, encryptor, "f");
142
- __classPrivateFieldSet(this, _SnapController_getMnemonic, getMnemonic, "f");
143
- __classPrivateFieldSet(this, _SnapController_getFeatureFlags, getFeatureFlags, "f");
144
- __classPrivateFieldSet(this, _SnapController_preinstalledSnaps, preinstalledSnaps, "f");
129
+ this.#detectSnapLocation = detectSnapLocationFunction;
130
+ this.#encryptor = encryptor;
131
+ this.#getMnemonic = getMnemonic;
132
+ this.#getFeatureFlags = getFeatureFlags;
133
+ this.#preinstalledSnaps = preinstalledSnaps;
145
134
  this._onUnhandledSnapError = this._onUnhandledSnapError.bind(this);
146
135
  this._onOutboundRequest = this._onOutboundRequest.bind(this);
147
136
  this._onOutboundResponse = this._onOutboundResponse.bind(this);
148
- __classPrivateFieldSet(this, _SnapController_rollbackSnapshots, new Map(), "f");
149
- __classPrivateFieldSet(this, _SnapController_snapsRuntimeData, new Map(), "f");
150
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_pollForLastRequestStatus).call(this);
137
+ this.#rollbackSnapshots = new Map();
138
+ this.#snapsRuntimeData = new Map();
139
+ this.#pollForLastRequestStatus();
151
140
  /* eslint-disable @typescript-eslint/unbound-method */
152
141
  this.messagingSystem.subscribe('ExecutionService:unhandledError', this._onUnhandledSnapError);
153
142
  this.messagingSystem.subscribe('ExecutionService:outboundRequest', this._onOutboundRequest);
154
143
  this.messagingSystem.subscribe('ExecutionService:outboundResponse', this._onOutboundResponse);
155
144
  /* eslint-enable @typescript-eslint/unbound-method */
156
145
  this.messagingSystem.subscribe('SnapController:snapInstalled', ({ id }, origin) => {
157
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_callLifecycleHook).call(this, origin, id, snaps_utils_1.HandlerType.OnInstall).catch((error) => {
146
+ this.#callLifecycleHook(origin, id, snaps_utils_1.HandlerType.OnInstall).catch((error) => {
158
147
  (0, snaps_utils_1.logError)(`Error when calling \`onInstall\` lifecycle hook for snap "${id}": ${(0, snaps_sdk_1.getErrorMessage)(error)}`);
159
148
  });
160
149
  });
161
150
  this.messagingSystem.subscribe('SnapController:snapUpdated', ({ id }, _oldVersion, origin) => {
162
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_callLifecycleHook).call(this, origin, id, snaps_utils_1.HandlerType.OnUpdate).catch((error) => {
151
+ this.#callLifecycleHook(origin, id, snaps_utils_1.HandlerType.OnUpdate).catch((error) => {
163
152
  (0, snaps_utils_1.logError)(`Error when calling \`onUpdate\` lifecycle hook for snap "${id}": ${(0, snaps_sdk_1.getErrorMessage)(error)}`);
164
153
  });
165
154
  });
166
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_initializeStateMachine).call(this);
167
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_registerMessageHandlers).call(this);
168
- if (__classPrivateFieldGet(this, _SnapController_preinstalledSnaps, "f")) {
169
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_handlePreinstalledSnaps).call(this, __classPrivateFieldGet(this, _SnapController_preinstalledSnaps, "f"));
155
+ this.#initializeStateMachine();
156
+ this.#registerMessageHandlers();
157
+ if (this.#preinstalledSnaps) {
158
+ this.#handlePreinstalledSnaps(this.#preinstalledSnaps);
159
+ }
160
+ Object.values(this.state?.snaps ?? {}).forEach((snap) => this.#setupRuntime(snap.id));
161
+ }
162
+ /**
163
+ * We track status of a Snap using a finite-state-machine.
164
+ * It keeps track of whether the snap is started / stopped / etc.
165
+ *
166
+ * @see {@link SnapController.transition} for interacting with the machine.
167
+ */
168
+ // We initialize the machine in the instance because the status is currently tightly coupled
169
+ // with the SnapController - the guard checks for enabled status inside the SnapController state.
170
+ // In the future, side-effects could be added to the machine during transitions.
171
+ #initializeStateMachine() {
172
+ const disableGuard = ({ snapId }) => {
173
+ return this.getExpect(snapId).enabled;
174
+ };
175
+ const statusConfig = {
176
+ initial: snaps_utils_1.SnapStatus.Installing,
177
+ states: {
178
+ [snaps_utils_1.SnapStatus.Installing]: {
179
+ on: {
180
+ [snaps_utils_1.SnapStatusEvents.Start]: {
181
+ target: snaps_utils_1.SnapStatus.Running,
182
+ cond: disableGuard,
183
+ },
184
+ },
185
+ },
186
+ [snaps_utils_1.SnapStatus.Updating]: {
187
+ on: {
188
+ [snaps_utils_1.SnapStatusEvents.Start]: {
189
+ target: snaps_utils_1.SnapStatus.Running,
190
+ cond: disableGuard,
191
+ },
192
+ [snaps_utils_1.SnapStatusEvents.Stop]: snaps_utils_1.SnapStatus.Stopped,
193
+ },
194
+ },
195
+ [snaps_utils_1.SnapStatus.Running]: {
196
+ on: {
197
+ [snaps_utils_1.SnapStatusEvents.Stop]: snaps_utils_1.SnapStatus.Stopped,
198
+ [snaps_utils_1.SnapStatusEvents.Crash]: snaps_utils_1.SnapStatus.Crashed,
199
+ },
200
+ },
201
+ [snaps_utils_1.SnapStatus.Stopped]: {
202
+ on: {
203
+ [snaps_utils_1.SnapStatusEvents.Start]: {
204
+ target: snaps_utils_1.SnapStatus.Running,
205
+ cond: disableGuard,
206
+ },
207
+ [snaps_utils_1.SnapStatusEvents.Update]: snaps_utils_1.SnapStatus.Updating,
208
+ },
209
+ },
210
+ [snaps_utils_1.SnapStatus.Crashed]: {
211
+ on: {
212
+ [snaps_utils_1.SnapStatusEvents.Start]: {
213
+ target: snaps_utils_1.SnapStatus.Running,
214
+ cond: disableGuard,
215
+ },
216
+ [snaps_utils_1.SnapStatusEvents.Update]: snaps_utils_1.SnapStatus.Updating,
217
+ },
218
+ },
219
+ },
220
+ };
221
+ this.#statusMachine = (0, fsm_1.createMachine)(statusConfig);
222
+ (0, fsm_2.validateMachine)(this.#statusMachine);
223
+ }
224
+ /**
225
+ * Constructor helper for registering the controller's messaging system
226
+ * actions.
227
+ */
228
+ #registerMessageHandlers() {
229
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:clearSnapState`, (...args) => this.clearSnapState(...args));
230
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:get`, (...args) => this.get(...args));
231
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:getSnapState`, async (...args) => this.getSnapState(...args));
232
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:handleRequest`, async (...args) => this.handleRequest(...args));
233
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:has`, (...args) => this.has(...args));
234
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:updateBlockedSnaps`, async () => this.updateBlockedSnaps());
235
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:updateSnapState`, async (...args) => this.updateSnapState(...args));
236
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:enable`, (...args) => this.enableSnap(...args));
237
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:disable`, async (...args) => this.disableSnap(...args));
238
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:remove`, async (...args) => this.removeSnap(...args));
239
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:getPermitted`, (...args) => this.getPermittedSnaps(...args));
240
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:install`, async (...args) => this.installSnaps(...args));
241
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:getAll`, (...args) => this.getAllSnaps(...args));
242
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:incrementActiveReferences`, (...args) => this.incrementActiveReferences(...args));
243
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:decrementActiveReferences`, (...args) => this.decrementActiveReferences(...args));
244
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:getRegistryMetadata`, async (...args) => this.getRegistryMetadata(...args));
245
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:disconnectOrigin`, (...args) => this.removeSnapFromSubject(...args));
246
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:revokeDynamicPermissions`, (...args) => this.revokeDynamicSnapPermissions(...args));
247
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:getFile`, async (...args) => this.getSnapFile(...args));
248
+ this.messagingSystem.registerActionHandler(`${exports.controllerName}:stopAllSnaps`, async (...args) => this.stopAllSnaps(...args));
249
+ }
250
+ #handlePreinstalledSnaps(preinstalledSnaps) {
251
+ for (const { snapId, manifest, files, removable, hidden, hideSnapBranding, } of preinstalledSnaps) {
252
+ const existingSnap = this.get(snapId);
253
+ const isAlreadyInstalled = existingSnap !== undefined;
254
+ const isUpdate = isAlreadyInstalled && (0, utils_1.gtVersion)(manifest.version, existingSnap.version);
255
+ // Disallow downgrades and overwriting non preinstalled snaps
256
+ if (isAlreadyInstalled &&
257
+ (!isUpdate || existingSnap.preinstalled !== true)) {
258
+ continue;
259
+ }
260
+ const manifestFile = new snaps_utils_1.VirtualFile({
261
+ path: snaps_utils_1.NpmSnapFileNames.Manifest,
262
+ value: JSON.stringify(manifest),
263
+ result: manifest,
264
+ });
265
+ const virtualFiles = files.map(({ path, value }) => new snaps_utils_1.VirtualFile({ value, path }));
266
+ const { filePath, iconPath } = manifest.source.location.npm;
267
+ const sourceCode = virtualFiles.find((file) => file.path === filePath);
268
+ const svgIcon = iconPath
269
+ ? virtualFiles.find((file) => file.path === iconPath)
270
+ : undefined;
271
+ (0, utils_1.assert)(sourceCode, 'Source code not provided for preinstalled snap.');
272
+ (0, utils_1.assert)(!iconPath || (iconPath && svgIcon), 'Icon not provided for preinstalled snap.');
273
+ (0, utils_1.assert)(manifest.source.files === undefined, 'Auxiliary files are not currently supported for preinstalled snaps.');
274
+ const localizationFiles = manifest.source.locales?.map((path) => virtualFiles.find((file) => file.path === path)) ?? [];
275
+ const validatedLocalizationFiles = (0, snaps_utils_1.getValidatedLocalizationFiles)(localizationFiles.filter(Boolean));
276
+ (0, utils_1.assert)(localizationFiles.length === validatedLocalizationFiles.length, 'Missing localization files for preinstalled snap.');
277
+ const filesObject = {
278
+ manifest: manifestFile,
279
+ sourceCode,
280
+ svgIcon,
281
+ auxiliaryFiles: [],
282
+ localizationFiles: validatedLocalizationFiles,
283
+ };
284
+ // Add snap to the SnapController state
285
+ this.#set({
286
+ id: snapId,
287
+ origin: 'metamask',
288
+ files: filesObject,
289
+ removable,
290
+ hidden,
291
+ hideSnapBranding,
292
+ preinstalled: true,
293
+ });
294
+ // Setup permissions
295
+ const processedPermissions = (0, snaps_rpc_methods_1.processSnapPermissions)(manifest.initialPermissions);
296
+ this.#validateSnapPermissions(processedPermissions);
297
+ const { newPermissions, unusedPermissions } = this.#calculatePermissionsChange(snapId, processedPermissions);
298
+ this.#updatePermissions({ snapId, newPermissions, unusedPermissions });
299
+ if (manifest.initialConnections) {
300
+ this.#handleInitialConnections(snapId, existingSnap?.initialConnections ?? null, manifest.initialConnections);
301
+ }
302
+ // Set status
303
+ this.update((state) => {
304
+ state.snaps[snapId].status = snaps_utils_1.SnapStatus.Stopped;
305
+ });
170
306
  }
171
- Object.values(this.state?.snaps ?? {}).forEach((snap) => __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_setupRuntime).call(this, snap.id));
307
+ }
308
+ #pollForLastRequestStatus() {
309
+ this.#timeoutForLastRequestStatus = setTimeout(() => {
310
+ this.#stopSnapsLastRequestPastMax().catch((error) => {
311
+ // TODO: Decide how to handle errors.
312
+ (0, snaps_utils_1.logError)(error);
313
+ });
314
+ this.#pollForLastRequestStatus();
315
+ }, this.#idleTimeCheckInterval);
172
316
  }
173
317
  /**
174
318
  * Checks all installed snaps against the block list and
@@ -176,7 +320,7 @@ class SnapController extends base_controller_1.BaseController {
176
320
  * for more information.
177
321
  */
178
322
  async updateBlockedSnaps() {
179
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertCanUsePlatform).call(this);
323
+ this.#assertCanUsePlatform();
180
324
  await this.messagingSystem.call('SnapsRegistry:update');
181
325
  const blockedSnaps = await this.messagingSystem.call('SnapsRegistry:get', Object.values(this.state.snaps).reduce((blockListArg, snap) => {
182
326
  blockListArg[snap.id] = {
@@ -187,11 +331,91 @@ class SnapController extends base_controller_1.BaseController {
187
331
  }, {}));
188
332
  await Promise.all(Object.entries(blockedSnaps).map(async ([snapId, { status, reason }]) => {
189
333
  if (status === registry_1.SnapsRegistryStatus.Blocked) {
190
- return __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_blockSnap).call(this, snapId, reason);
334
+ return this.#blockSnap(snapId, reason);
191
335
  }
192
- return __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_unblockSnap).call(this, snapId);
336
+ return this.#unblockSnap(snapId);
193
337
  }));
194
338
  }
339
+ /**
340
+ * Blocks an installed snap and prevents it from being started again. Emits
341
+ * {@link SnapBlocked}. Does nothing if the snap is not installed.
342
+ *
343
+ * @param snapId - The snap to block.
344
+ * @param blockedSnapInfo - Information detailing why the snap is blocked.
345
+ */
346
+ async #blockSnap(snapId, blockedSnapInfo) {
347
+ if (!this.has(snapId)) {
348
+ return;
349
+ }
350
+ try {
351
+ this.update((state) => {
352
+ state.snaps[snapId].blocked = true;
353
+ state.snaps[snapId].blockInformation = blockedSnapInfo;
354
+ });
355
+ await this.disableSnap(snapId);
356
+ }
357
+ catch (error) {
358
+ (0, snaps_utils_1.logError)(`Encountered error when stopping blocked snap "${snapId}".`, error);
359
+ }
360
+ this.messagingSystem.publish(`${exports.controllerName}:snapBlocked`, snapId, blockedSnapInfo);
361
+ }
362
+ /**
363
+ * Unblocks a snap so that it can be enabled and started again. Emits
364
+ * {@link SnapUnblocked}. Does nothing if the snap is not installed or already
365
+ * unblocked.
366
+ *
367
+ * @param snapId - The id of the snap to unblock.
368
+ */
369
+ #unblockSnap(snapId) {
370
+ if (!this.has(snapId) || !this.state.snaps[snapId].blocked) {
371
+ return;
372
+ }
373
+ this.update((state) => {
374
+ state.snaps[snapId].blocked = false;
375
+ delete state.snaps[snapId].blockInformation;
376
+ });
377
+ this.messagingSystem.publish(`${exports.controllerName}:snapUnblocked`, snapId);
378
+ }
379
+ async #assertIsInstallAllowed(snapId, snapInfo) {
380
+ const results = await this.messagingSystem.call('SnapsRegistry:get', {
381
+ [snapId]: snapInfo,
382
+ });
383
+ const result = results[snapId];
384
+ if (result.status === registry_1.SnapsRegistryStatus.Blocked) {
385
+ throw new Error(`Cannot install version "${snapInfo.version}" of snap "${snapId}": The version is blocked. ${result.reason?.explanation ?? ''}`);
386
+ }
387
+ const isAllowlistingRequired = Object.keys(snapInfo.permissions).some((permission) => !constants_1.ALLOWED_PERMISSIONS.includes(permission));
388
+ if (this.#featureFlags.requireAllowlist &&
389
+ isAllowlistingRequired &&
390
+ result.status !== registry_1.SnapsRegistryStatus.Verified) {
391
+ throw new Error(`Cannot install version "${snapInfo.version}" of snap "${snapId}": ${result.status === registry_1.SnapsRegistryStatus.Unavailable
392
+ ? 'The registry is temporarily unavailable.'
393
+ : 'The snap is not on the allowlist.'}`);
394
+ }
395
+ }
396
+ /**
397
+ * Asserts whether new Snaps are allowed to be installed.
398
+ */
399
+ #assertCanInstallSnaps() {
400
+ (0, utils_1.assert)(this.#featureFlags.disableSnapInstallation !== true, 'Installing Snaps is currently disabled in this version of MetaMask.');
401
+ }
402
+ /**
403
+ * Asserts whether the Snaps platform is allowed to run.
404
+ */
405
+ #assertCanUsePlatform() {
406
+ const flags = this.#getFeatureFlags();
407
+ (0, utils_1.assert)(flags.disableSnaps !== true, 'The Snaps platform requires basic functionality to be used. Enable basic functionality in the settings to use the Snaps platform.');
408
+ }
409
+ async #stopSnapsLastRequestPastMax() {
410
+ const entries = [...this.#snapsRuntimeData.entries()];
411
+ return Promise.all(entries
412
+ .filter(([_snapId, runtime]) => runtime.activeReferences === 0 &&
413
+ runtime.pendingInboundRequests.length === 0 &&
414
+ runtime.lastRequest &&
415
+ this.#maxIdleTime &&
416
+ (0, utils_1.timeSince)(runtime.lastRequest) > this.#maxIdleTime)
417
+ .map(async ([snapId]) => this.stopSnap(snapId, snaps_utils_1.SnapStatusEvents.Stop)));
418
+ }
195
419
  _onUnhandledSnapError(snapId, _error) {
196
420
  this.stopSnap(snapId, snaps_utils_1.SnapStatusEvents.Crash).catch((stopSnapError) => {
197
421
  // TODO: Decide how to handle errors.
@@ -199,7 +423,7 @@ class SnapController extends base_controller_1.BaseController {
199
423
  });
200
424
  }
201
425
  _onOutboundRequest(snapId) {
202
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
426
+ const runtime = this.#getRuntimeExpect(snapId);
203
427
  // Ideally we would only pause the pending request that is making the outbound request
204
428
  // but right now we don't have a way to know which request initiated the outbound request
205
429
  runtime.pendingInboundRequests
@@ -208,7 +432,7 @@ class SnapController extends base_controller_1.BaseController {
208
432
  runtime.pendingOutboundRequests += 1;
209
433
  }
210
434
  _onOutboundResponse(snapId) {
211
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
435
+ const runtime = this.#getRuntimeExpect(snapId);
212
436
  runtime.pendingOutboundRequests -= 1;
213
437
  if (runtime.pendingOutboundRequests === 0) {
214
438
  runtime.pendingInboundRequests
@@ -216,6 +440,25 @@ class SnapController extends base_controller_1.BaseController {
216
440
  .forEach((pendingRequest) => pendingRequest.timer.resume());
217
441
  }
218
442
  }
443
+ /**
444
+ * Transitions between states using `snapStatusStateMachineConfig` as the template to figure out
445
+ * the next state. This transition function uses a very minimal subset of XState conventions:
446
+ * - supports initial state
447
+ * - .on supports raw event target string
448
+ * - .on supports {target, cond} object
449
+ * - the arguments for `cond` is the `SerializedSnap` instead of Xstate convention of `(event,
450
+ * context) => boolean`
451
+ *
452
+ * @param snapId - The id of the snap to transition.
453
+ * @param event - The event enum to use to transition.
454
+ */
455
+ #transition(snapId, event) {
456
+ const { interpreter } = this.#getRuntimeExpect(snapId);
457
+ interpreter.send(event);
458
+ this.update((state) => {
459
+ state.snaps[snapId].status = interpreter.state.value;
460
+ });
461
+ }
219
462
  /**
220
463
  * Starts the given snap. Throws an error if no such snap exists
221
464
  * or if it is already running.
@@ -223,12 +466,12 @@ class SnapController extends base_controller_1.BaseController {
223
466
  * @param snapId - The id of the Snap to start.
224
467
  */
225
468
  async startSnap(snapId) {
226
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertCanUsePlatform).call(this);
469
+ this.#assertCanUsePlatform();
227
470
  const snap = this.state.snaps[snapId];
228
471
  if (snap.enabled === false) {
229
472
  throw new Error(`Snap "${snapId}" is disabled.`);
230
473
  }
231
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_startSnap).call(this, {
474
+ await this.#startSnap({
232
475
  snapId,
233
476
  sourceCode: snap.sourceCode,
234
477
  });
@@ -276,7 +519,7 @@ class SnapController extends base_controller_1.BaseController {
276
519
  * stopped.
277
520
  */
278
521
  async stopSnap(snapId, statusEvent = snaps_utils_1.SnapStatusEvents.Stop) {
279
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntime).call(this, snapId);
522
+ const runtime = this.#getRuntime(snapId);
280
523
  if (!runtime) {
281
524
  throw new Error(`The snap "${snapId}" is not running.`);
282
525
  }
@@ -289,8 +532,8 @@ class SnapController extends base_controller_1.BaseController {
289
532
  runtime.stopping = true;
290
533
  try {
291
534
  if (this.isRunning(snapId)) {
292
- __classPrivateFieldGet(this, _SnapController_closeAllConnections, "f")?.call(this, snapId);
293
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_terminateSnap).call(this, snapId);
535
+ this.#closeAllConnections?.(snapId);
536
+ await this.#terminateSnap(snapId);
294
537
  }
295
538
  }
296
539
  finally {
@@ -300,7 +543,7 @@ class SnapController extends base_controller_1.BaseController {
300
543
  runtime.pendingOutboundRequests = 0;
301
544
  runtime.stopping = false;
302
545
  if (this.isRunning(snapId)) {
303
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_transition).call(this, snapId, statusEvent);
546
+ this.#transition(snapId, statusEvent);
304
547
  }
305
548
  }
306
549
  }
@@ -316,6 +559,24 @@ class SnapController extends base_controller_1.BaseController {
316
559
  const promises = snaps.map(async (snap) => this.stopSnap(snap.id, statusEvent));
317
560
  await Promise.allSettled(promises);
318
561
  }
562
+ /**
563
+ * Terminates the specified snap and emits the `snapTerminated` event.
564
+ *
565
+ * @param snapId - The snap to terminate.
566
+ */
567
+ async #terminateSnap(snapId) {
568
+ await this.messagingSystem.call('ExecutionService:terminateSnap', snapId);
569
+ // Hack to give up execution for a bit to let gracefully terminating Snaps return.
570
+ await new Promise((resolve) => setTimeout(resolve, 1));
571
+ const runtime = this.#getRuntimeExpect(snapId);
572
+ // Unresponsive requests may still be timed, time them out.
573
+ runtime.pendingInboundRequests
574
+ .filter((pendingRequest) => pendingRequest.timer.status !== 'finished')
575
+ .forEach((pendingRequest) => pendingRequest.timer.finish());
576
+ // Hack to give up execution for a bit to let timed out requests return.
577
+ await new Promise((resolve) => setTimeout(resolve, 1));
578
+ this.messagingSystem.publish('SnapController:snapTerminated', this.getTruncatedExpect(snapId));
579
+ }
319
580
  /**
320
581
  * Returns whether the given snap is running.
321
582
  * Throws an error if the snap doesn't exist.
@@ -383,6 +644,86 @@ class SnapController extends base_controller_1.BaseController {
383
644
  getTruncatedExpect(snapId) {
384
645
  return truncateSnap(this.getExpect(snapId));
385
646
  }
647
+ /**
648
+ * Generate an encryption key to be used for state encryption for a given Snap.
649
+ *
650
+ * @param options - An options bag.
651
+ * @param options.snapId - The Snap ID.
652
+ * @param options.salt - A salt to be used for the encryption key.
653
+ * @param options.useCache - Whether to use caching or not.
654
+ * @param options.keyMetadata - Optional metadata about how to derive the encryption key.
655
+ * @returns An encryption key.
656
+ */
657
+ async #getSnapEncryptionKey({ snapId, salt: passedSalt, useCache, keyMetadata, }) {
658
+ const runtime = this.#getRuntimeExpect(snapId);
659
+ if (runtime.encryptionKey && runtime.encryptionSalt && useCache) {
660
+ return {
661
+ key: await this.#encryptor.importKey(runtime.encryptionKey),
662
+ salt: runtime.encryptionSalt,
663
+ };
664
+ }
665
+ const salt = passedSalt ?? this.#encryptor.generateSalt();
666
+ const mnemonicPhrase = await this.#getMnemonic();
667
+ const entropy = await (0, snaps_rpc_methods_1.getEncryptionEntropy)({ snapId, mnemonicPhrase });
668
+ const encryptionKey = await this.#encryptor.keyFromPassword(entropy, salt, true, keyMetadata);
669
+ const exportedKey = await this.#encryptor.exportKey(encryptionKey);
670
+ // Cache exported encryption key in runtime
671
+ if (useCache) {
672
+ runtime.encryptionKey = exportedKey;
673
+ runtime.encryptionSalt = salt;
674
+ }
675
+ return { key: encryptionKey, salt };
676
+ }
677
+ /**
678
+ * Decrypt the encrypted state for a given Snap.
679
+ *
680
+ * @param snapId - The Snap ID.
681
+ * @param state - The encrypted state as a string.
682
+ * @returns A valid JSON object derived from the encrypted state.
683
+ * @throws If the decryption fails or the decrypted state is not valid JSON.
684
+ */
685
+ async #decryptSnapState(snapId, state) {
686
+ try {
687
+ const parsed = (0, snaps_utils_1.parseJson)(state);
688
+ const { salt, keyMetadata } = parsed;
689
+ const useCache = this.#encryptor.isVaultUpdated(state);
690
+ const { key } = await this.#getSnapEncryptionKey({
691
+ snapId,
692
+ salt,
693
+ useCache,
694
+ // When decrypting state we expect key metadata to be present.
695
+ // If it isn't present, we assume that the Snap state we are decrypting is old enough to use the legacy encryption params.
696
+ keyMetadata: keyMetadata ?? constants_1.LEGACY_ENCRYPTION_KEY_DERIVATION_OPTIONS,
697
+ });
698
+ const decryptedState = await this.#encryptor.decryptWithKey(key, parsed);
699
+ (0, utils_1.assert)((0, utils_1.isValidJson)(decryptedState));
700
+ return decryptedState;
701
+ }
702
+ catch {
703
+ throw rpc_errors_1.rpcErrors.internal({
704
+ message: 'Failed to decrypt snap state, the state must be corrupted.',
705
+ });
706
+ }
707
+ }
708
+ /**
709
+ * Encrypt a JSON state object for a given Snap.
710
+ *
711
+ * Note: This function does not assert the validity of the object,
712
+ * please ensure only valid JSON is passed to it.
713
+ *
714
+ * @param snapId - The Snap ID.
715
+ * @param state - The state object.
716
+ * @returns A string containing the encrypted JSON object.
717
+ */
718
+ async #encryptSnapState(snapId, state) {
719
+ const { key, salt } = await this.#getSnapEncryptionKey({
720
+ snapId,
721
+ useCache: true,
722
+ });
723
+ const encryptedState = await this.#encryptor.encryptWithKey(key, state);
724
+ encryptedState.salt = salt;
725
+ return JSON.stringify(encryptedState);
726
+ }
386
727
  /**
387
728
  * Updates the own state of the snap with the given id.
388
729
  * This is distinct from the state MetaMask uses to manage snaps.
@@ -393,7 +734,7 @@ class SnapController extends base_controller_1.BaseController {
393
734
  */
394
735
  async updateSnapState(snapId, newSnapState, encrypted) {
395
736
  if (encrypted) {
396
- const encryptedState = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_encryptSnapState).call(this, snapId, newSnapState);
737
+ const encryptedState = await this.#encryptSnapState(snapId, newSnapState);
397
738
  this.update((state) => {
398
739
  state.snapStates[snapId] = encryptedState;
399
740
  });
@@ -439,7 +780,7 @@ class SnapController extends base_controller_1.BaseController {
439
780
  if (!encrypted) {
440
781
  return (0, snaps_utils_1.parseJson)(state);
441
782
  }
442
- const decrypted = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_decryptSnapState).call(this, snapId, state);
783
+ const decrypted = await this.#decryptSnapState(snapId, state);
443
784
  return decrypted;
444
785
  }
445
786
  /**
@@ -467,22 +808,22 @@ class SnapController extends base_controller_1.BaseController {
467
808
  */
468
809
  async clearState() {
469
810
  const snapIds = Object.keys(this.state.snaps);
470
- if (__classPrivateFieldGet(this, _SnapController_closeAllConnections, "f")) {
811
+ if (this.#closeAllConnections) {
471
812
  snapIds.forEach((snapId) => {
472
- __classPrivateFieldGet(this, _SnapController_closeAllConnections, "f")?.call(this, snapId);
813
+ this.#closeAllConnections?.(snapId);
473
814
  });
474
815
  }
475
816
  await this.messagingSystem.call('ExecutionService:terminateAllSnaps');
476
- snapIds.forEach((snapId) => __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_revokeAllSnapPermissions).call(this, snapId));
817
+ snapIds.forEach((snapId) => this.#revokeAllSnapPermissions(snapId));
477
818
  this.update((state) => {
478
819
  state.snaps = {};
479
820
  state.snapStates = {};
480
821
  });
481
- __classPrivateFieldGet(this, _SnapController_snapsRuntimeData, "f").clear();
822
+ this.#snapsRuntimeData.clear();
482
823
  // We want to remove all snaps & permissions, except for preinstalled snaps
483
- if (__classPrivateFieldGet(this, _SnapController_preinstalledSnaps, "f")) {
484
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_handlePreinstalledSnaps).call(this, __classPrivateFieldGet(this, _SnapController_preinstalledSnaps, "f"));
485
- Object.values(this.state?.snaps).forEach((snap) => __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_setupRuntime).call(this, snap.id));
824
+ if (this.#preinstalledSnaps) {
825
+ this.#handlePreinstalledSnaps(this.#preinstalledSnaps);
826
+ Object.values(this.state?.snaps).forEach((snap) => this.#setupRuntime(snap.id));
486
827
  }
487
828
  }
488
829
  /**
@@ -516,9 +857,9 @@ class SnapController extends base_controller_1.BaseController {
516
857
  // it. This ensures that the snap will not be restarted or otherwise
517
858
  // affect the host environment while we are deleting it.
518
859
  await this.disableSnap(snapId);
519
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_revokeAllSnapPermissions).call(this, snapId);
520
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_removeSnapFromSubjects).call(this, snapId);
521
- __classPrivateFieldGet(this, _SnapController_snapsRuntimeData, "f").delete(snapId);
860
+ this.#revokeAllSnapPermissions(snapId);
861
+ this.#removeSnapFromSubjects(snapId);
862
+ this.#snapsRuntimeData.delete(snapId);
522
863
  this.update((state) => {
523
864
  delete state.snaps[snapId];
524
865
  delete state.snapStates[snapId];
@@ -530,6 +871,47 @@ class SnapController extends base_controller_1.BaseController {
530
871
  }
531
872
  }));
532
873
  }
874
+ #handleInitialConnections(snapId, previousInitialConnections, initialConnections) {
875
+ if (previousInitialConnections) {
876
+ const revokedInitialConnections = (0, utils_2.setDiff)(previousInitialConnections, initialConnections);
877
+ for (const origin of Object.keys(revokedInitialConnections)) {
878
+ this.removeSnapFromSubject(origin, snapId);
879
+ }
880
+ }
881
+ for (const origin of Object.keys(initialConnections)) {
882
+ this.#addSnapToSubject(origin, snapId);
883
+ }
884
+ }
885
+ #addSnapToSubject(origin, snapId) {
886
+ const subjectPermissions = this.messagingSystem.call('PermissionController:getPermissions', origin);
887
+ const existingCaveat = subjectPermissions?.[snaps_rpc_methods_1.WALLET_SNAP_PERMISSION_KEY]?.caveats?.find((caveat) => caveat.type === snaps_utils_1.SnapCaveatType.SnapIds);
888
+ const subjectHasSnap = Boolean(existingCaveat?.value?.[snapId]);
889
+ // If the subject is already connected to the snap, this is a no-op.
890
+ if (subjectHasSnap) {
891
+ return;
892
+ }
893
+ // If an existing caveat exists, we add the snap to that.
894
+ if (existingCaveat) {
895
+ this.messagingSystem.call('PermissionController:updateCaveat', origin, snaps_rpc_methods_1.WALLET_SNAP_PERMISSION_KEY, snaps_utils_1.SnapCaveatType.SnapIds, { ...existingCaveat.value, [snapId]: {} });
896
+ return;
897
+ }
898
+ const approvedPermissions = {
899
+ [snaps_rpc_methods_1.WALLET_SNAP_PERMISSION_KEY]: {
900
+ caveats: [
901
+ {
902
+ type: snaps_utils_1.SnapCaveatType.SnapIds,
903
+ value: {
904
+ [snapId]: {},
905
+ },
906
+ },
907
+ ],
908
+ },
909
+ };
910
+ this.messagingSystem.call('PermissionController:grantPermissions', {
911
+ approvedPermissions,
912
+ subject: { origin },
913
+ });
914
+ }
533
915
  /**
534
916
  * Removes a snap's permission (caveat) from the specified subject.
535
917
  *
@@ -566,18 +948,39 @@ class SnapController extends base_controller_1.BaseController {
566
948
  * @throws If non-dynamic permissions are passed.
567
949
  */
568
950
  revokeDynamicSnapPermissions(snapId, permissionNames) {
569
- (0, utils_1.assert)(permissionNames.every((permissionName) => __classPrivateFieldGet(this, _SnapController_dynamicPermissions, "f").includes(permissionName)), 'Non-dynamic permissions cannot be revoked');
951
+ (0, utils_1.assert)(permissionNames.every((permissionName) => this.#dynamicPermissions.includes(permissionName)), 'Non-dynamic permissions cannot be revoked');
570
952
  this.messagingSystem.call('PermissionController:revokePermissions', {
571
953
  [snapId]: permissionNames,
572
954
  });
573
955
  }
956
+ /**
957
+ * Removes a snap's permission (caveat) from all subjects.
958
+ *
959
+ * @param snapId - The id of the Snap.
960
+ */
961
+ #removeSnapFromSubjects(snapId) {
962
+ const subjects = this.messagingSystem.call('PermissionController:getSubjectNames');
963
+ for (const subject of subjects) {
964
+ this.removeSnapFromSubject(subject, snapId);
965
+ }
966
+ }
967
+ /**
968
+ * Safely revokes all permissions granted to a Snap.
969
+ *
970
+ * @param snapId - The snap ID.
971
+ */
972
+ #revokeAllSnapPermissions(snapId) {
973
+ if (this.messagingSystem.call('PermissionController:hasPermissions', snapId)) {
974
+ this.messagingSystem.call('PermissionController:revokeAllPermissions', snapId);
975
+ }
976
+ }
574
977
  /**
575
978
  * Handles incrementing the activeReferences counter.
576
979
  *
577
980
  * @param snapId - The snap id of the snap that was referenced.
578
981
  */
579
982
  incrementActiveReferences(snapId) {
580
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
983
+ const runtime = this.#getRuntimeExpect(snapId);
581
984
  runtime.activeReferences += 1;
582
985
  }
583
986
  /**
@@ -586,7 +989,7 @@ class SnapController extends base_controller_1.BaseController {
586
989
  * @param snapId - The snap id of the snap that was referenced..
587
990
  */
588
991
  decrementActiveReferences(snapId) {
589
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
992
+ const runtime = this.#getRuntimeExpect(snapId);
590
993
  (0, utils_1.assert)(runtime.activeReferences > 0, 'SnapController reference management is in an invalid state.');
591
994
  runtime.activeReferences -= 1;
592
995
  }
@@ -627,7 +1030,7 @@ class SnapController extends base_controller_1.BaseController {
627
1030
  * snap couldn't be installed.
628
1031
  */
629
1032
  async installSnaps(origin, requestedSnaps) {
630
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertCanUsePlatform).call(this);
1033
+ this.#assertCanUsePlatform();
631
1034
  const result = {};
632
1035
  const snapIds = Object.keys(requestedSnaps);
633
1036
  const pendingUpdates = [];
@@ -639,23 +1042,23 @@ class SnapController extends base_controller_1.BaseController {
639
1042
  if (error) {
640
1043
  throw rpc_errors_1.rpcErrors.invalidParams(`The "version" field must be a valid SemVer version range if specified. Received: "${rawVersion}".`);
641
1044
  }
642
- const location = __classPrivateFieldGet(this, _SnapController_detectSnapLocation, "f").call(this, snapId, {
1045
+ const location = this.#detectSnapLocation(snapId, {
643
1046
  versionRange: version,
644
- fetch: __classPrivateFieldGet(this, _SnapController_fetchFunction, "f"),
645
- allowLocal: __classPrivateFieldGet(this, _SnapController_featureFlags, "f").allowLocalSnaps,
646
- resolveVersion: async (range) => __classPrivateFieldGet(this, _SnapController_featureFlags, "f").requireAllowlist
647
- ? await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_resolveAllowlistVersion).call(this, snapId, range)
1047
+ fetch: this.#fetchFunction,
1048
+ allowLocal: this.#featureFlags.allowLocalSnaps,
1049
+ resolveVersion: async (range) => this.#featureFlags.requireAllowlist
1050
+ ? await this.#resolveAllowlistVersion(snapId, range)
648
1051
  : range,
649
1052
  });
650
1053
  // Existing snaps may need to be updated, unless they should be re-installed (e.g. local snaps)
651
1054
  // Everything else is treated as an install
652
1055
  const isUpdate = this.has(snapId) && !location.shouldAlwaysReload;
653
- if (isUpdate && __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_isValidUpdate).call(this, snapId, version)) {
1056
+ if (isUpdate && this.#isValidUpdate(snapId, version)) {
654
1057
  const existingSnap = this.getExpect(snapId);
655
1058
  pendingUpdates.push({ snapId, oldVersion: existingSnap.version });
656
- let rollbackSnapshot = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRollbackSnapshot).call(this, snapId);
1059
+ let rollbackSnapshot = this.#getRollbackSnapshot(snapId);
657
1060
  if (rollbackSnapshot === undefined) {
658
- rollbackSnapshot = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_createRollbackSnapshot).call(this, snapId);
1061
+ rollbackSnapshot = this.#createRollbackSnapshot(snapId);
659
1062
  rollbackSnapshot.newVersion = version;
660
1063
  }
661
1064
  else {
@@ -670,16 +1073,16 @@ class SnapController extends base_controller_1.BaseController {
670
1073
  // Once we finish all installs / updates, emit events.
671
1074
  pendingInstalls.forEach((snapId) => this.messagingSystem.publish(`SnapController:snapInstalled`, this.getTruncatedExpect(snapId), origin));
672
1075
  pendingUpdates.forEach(({ snapId, oldVersion }) => this.messagingSystem.publish(`SnapController:snapUpdated`, this.getTruncatedExpect(snapId), oldVersion, origin));
673
- snapIds.forEach((snapId) => __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").delete(snapId));
1076
+ snapIds.forEach((snapId) => this.#rollbackSnapshots.delete(snapId));
674
1077
  }
675
1078
  catch (error) {
676
1079
  const installed = pendingInstalls.filter((snapId) => this.has(snapId));
677
1080
  await this.removeSnaps(installed);
678
- const snapshottedSnaps = [...__classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").keys()];
1081
+ const snapshottedSnaps = [...this.#rollbackSnapshots.keys()];
679
1082
  const snapsToRollback = pendingUpdates
680
1083
  .map(({ snapId }) => snapId)
681
1084
  .filter((snapId) => snapshottedSnaps.includes(snapId));
682
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_rollbackSnaps).call(this, snapsToRollback);
1085
+ await this.#rollbackSnaps(snapsToRollback);
683
1086
  throw error;
684
1087
  }
685
1088
  return result;
@@ -709,8 +1112,8 @@ class SnapController extends base_controller_1.BaseController {
709
1112
  // and we don't want to emit events prematurely.
710
1113
  false);
711
1114
  }
712
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertCanInstallSnaps).call(this);
713
- let pendingApproval = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_createApproval).call(this, {
1115
+ this.#assertCanInstallSnaps();
1116
+ let pendingApproval = this.#createApproval({
714
1117
  origin,
715
1118
  snapId,
716
1119
  type: exports.SNAP_APPROVAL_INSTALL,
@@ -722,27 +1125,27 @@ class SnapController extends base_controller_1.BaseController {
722
1125
  }
723
1126
  // Existing snaps that should be re-installed should not maintain their existing permissions
724
1127
  if (existingSnap && location.shouldAlwaysReload) {
725
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_revokeAllSnapPermissions).call(this, snapId);
1128
+ this.#revokeAllSnapPermissions(snapId);
726
1129
  }
727
1130
  try {
728
- const { sourceCode } = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_add).call(this, {
1131
+ const { sourceCode } = await this.#add({
729
1132
  origin,
730
1133
  id: snapId,
731
1134
  location,
732
1135
  versionRange,
733
1136
  });
734
1137
  await this.authorize(snapId, pendingApproval);
735
- pendingApproval = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_createApproval).call(this, {
1138
+ pendingApproval = this.#createApproval({
736
1139
  origin,
737
1140
  snapId,
738
1141
  type: exports.SNAP_APPROVAL_RESULT,
739
1142
  });
740
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_startSnap).call(this, {
1143
+ await this.#startSnap({
741
1144
  snapId,
742
1145
  sourceCode,
743
1146
  });
744
1147
  const truncated = this.getTruncatedExpect(snapId);
745
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updateApproval).call(this, pendingApproval.id, {
1148
+ this.#updateApproval(pendingApproval.id, {
746
1149
  loading: false,
747
1150
  type: exports.SNAP_APPROVAL_INSTALL,
748
1151
  });
@@ -751,7 +1154,7 @@ class SnapController extends base_controller_1.BaseController {
751
1154
  catch (error) {
752
1155
  (0, snaps_utils_1.logError)(`Error when adding ${snapId}.`, error);
753
1156
  const errorString = error instanceof Error ? error.message : error.toString();
754
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updateApproval).call(this, pendingApproval.id, {
1157
+ this.#updateApproval(pendingApproval.id, {
755
1158
  loading: false,
756
1159
  type: exports.SNAP_APPROVAL_INSTALL,
757
1160
  error: errorString,
@@ -760,6 +1163,34 @@ class SnapController extends base_controller_1.BaseController {
760
1163
  throw error;
761
1164
  }
762
1165
  }
1166
+ #createApproval({ origin, snapId, type, }) {
1167
+ const id = (0, nanoid_1.nanoid)();
1168
+ const promise = this.messagingSystem.call('ApprovalController:addRequest', {
1169
+ origin,
1170
+ id,
1171
+ type,
1172
+ requestData: {
1173
+ // Mirror previous installation metadata
1174
+ metadata: { id, origin: snapId, dappOrigin: origin },
1175
+ snapId,
1176
+ },
1177
+ requestState: {
1178
+ loading: true,
1179
+ },
1180
+ }, true);
1181
+ return { id, promise };
1182
+ }
1183
+ #updateApproval(id, requestState) {
1184
+ try {
1185
+ this.messagingSystem.call('ApprovalController:updateRequestState', {
1186
+ id,
1187
+ requestState,
1188
+ });
1189
+ }
1190
+ catch {
1191
+ // Do nothing
1192
+ }
1193
+ }
763
1194
  /**
764
1195
  * Updates an installed snap. The flow is similar to
765
1196
  * {@link SnapController.installSnaps}. The user will be asked if they want
@@ -780,12 +1211,12 @@ class SnapController extends base_controller_1.BaseController {
780
1211
  * @returns The snap metadata if updated, `null` otherwise.
781
1212
  */
782
1213
  async updateSnap(origin, snapId, location, newVersionRange = snaps_utils_1.DEFAULT_REQUESTED_SNAP_VERSION, emitEvent = true) {
783
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertCanInstallSnaps).call(this);
784
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertCanUsePlatform).call(this);
1214
+ this.#assertCanInstallSnaps();
1215
+ this.#assertCanUsePlatform();
785
1216
  if (!(0, utils_1.isValidSemVerRange)(newVersionRange)) {
786
1217
  throw new Error(`Received invalid snap version range: "${newVersionRange}".`);
787
1218
  }
788
- let pendingApproval = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_createApproval).call(this, {
1219
+ let pendingApproval = this.#createApproval({
789
1220
  origin,
790
1221
  snapId,
791
1222
  type: exports.SNAP_APPROVAL_UPDATE,
@@ -804,16 +1235,16 @@ class SnapController extends base_controller_1.BaseController {
804
1235
  if (!(0, utils_1.satisfiesVersionRange)(newVersion, newVersionRange)) {
805
1236
  throw new Error(`Version mismatch. Manifest for "${snapId}" specifies version "${newVersion}" which doesn't satisfy requested version range "${newVersionRange}".`);
806
1237
  }
807
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertIsInstallAllowed).call(this, snapId, {
1238
+ await this.#assertIsInstallAllowed(snapId, {
808
1239
  version: newVersion,
809
1240
  checksum: manifest.source.shasum,
810
1241
  permissions: manifest.initialPermissions,
811
1242
  });
812
1243
  const processedPermissions = (0, snaps_rpc_methods_1.processSnapPermissions)(manifest.initialPermissions);
813
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_validateSnapPermissions).call(this, processedPermissions);
814
- const { newPermissions, unusedPermissions, approvedPermissions } = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_calculatePermissionsChange).call(this, snapId, processedPermissions);
815
- const { newConnections, unusedConnections, approvedConnections } = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_calculateConnectionsChange).call(this, snapId, oldManifest.initialConnections ?? {}, manifest.initialConnections ?? {});
816
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updateApproval).call(this, pendingApproval.id, {
1244
+ this.#validateSnapPermissions(processedPermissions);
1245
+ const { newPermissions, unusedPermissions, approvedPermissions } = this.#calculatePermissionsChange(snapId, processedPermissions);
1246
+ const { newConnections, unusedConnections, approvedConnections } = this.#calculateConnectionsChange(snapId, oldManifest.initialConnections ?? {}, manifest.initialConnections ?? {});
1247
+ this.#updateApproval(pendingApproval.id, {
817
1248
  permissions: newPermissions,
818
1249
  newVersion: manifest.version,
819
1250
  newPermissions,
@@ -825,7 +1256,7 @@ class SnapController extends base_controller_1.BaseController {
825
1256
  loading: false,
826
1257
  });
827
1258
  const { permissions: approvedNewPermissions, ...requestData } = (await pendingApproval.promise);
828
- pendingApproval = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_createApproval).call(this, {
1259
+ pendingApproval = this.#createApproval({
829
1260
  origin,
830
1261
  snapId,
831
1262
  type: exports.SNAP_APPROVAL_RESULT,
@@ -833,23 +1264,23 @@ class SnapController extends base_controller_1.BaseController {
833
1264
  if (this.isRunning(snapId)) {
834
1265
  await this.stopSnap(snapId, snaps_utils_1.SnapStatusEvents.Stop);
835
1266
  }
836
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_transition).call(this, snapId, snaps_utils_1.SnapStatusEvents.Update);
837
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_set).call(this, {
1267
+ this.#transition(snapId, snaps_utils_1.SnapStatusEvents.Update);
1268
+ this.#set({
838
1269
  origin,
839
1270
  id: snapId,
840
1271
  files: newSnap,
841
1272
  isUpdate: true,
842
1273
  });
843
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updatePermissions).call(this, {
1274
+ this.#updatePermissions({
844
1275
  snapId,
845
1276
  unusedPermissions,
846
1277
  newPermissions: approvedNewPermissions,
847
1278
  requestData,
848
1279
  });
849
1280
  if (manifest.initialConnections) {
850
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_handleInitialConnections).call(this, snapId, oldManifest.initialConnections ?? null, manifest.initialConnections);
1281
+ this.#handleInitialConnections(snapId, oldManifest.initialConnections ?? null, manifest.initialConnections);
851
1282
  }
852
- const rollbackSnapshot = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRollbackSnapshot).call(this, snapId);
1283
+ const rollbackSnapshot = this.#getRollbackSnapshot(snapId);
853
1284
  if (rollbackSnapshot !== undefined) {
854
1285
  rollbackSnapshot.permissions.revoked = unusedPermissions;
855
1286
  rollbackSnapshot.permissions.granted = approvedNewPermissions;
@@ -858,7 +1289,7 @@ class SnapController extends base_controller_1.BaseController {
858
1289
  const sourceCode = sourceCodeFile.toString();
859
1290
  (0, utils_1.assert)(typeof sourceCode === 'string' && sourceCode.length > 0, `Invalid source code for snap "${snapId}".`);
860
1291
  try {
861
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_startSnap).call(this, { snapId, sourceCode });
1292
+ await this.#startSnap({ snapId, sourceCode });
862
1293
  }
863
1294
  catch {
864
1295
  throw new Error(`Snap ${snapId} crashed with updated source code.`);
@@ -867,7 +1298,7 @@ class SnapController extends base_controller_1.BaseController {
867
1298
  if (emitEvent) {
868
1299
  this.messagingSystem.publish('SnapController:snapUpdated', truncatedSnap, snap.version, origin);
869
1300
  }
870
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updateApproval).call(this, pendingApproval.id, {
1301
+ this.#updateApproval(pendingApproval.id, {
871
1302
  loading: false,
872
1303
  type: exports.SNAP_APPROVAL_UPDATE,
873
1304
  });
@@ -876,7 +1307,7 @@ class SnapController extends base_controller_1.BaseController {
876
1307
  catch (error) {
877
1308
  (0, snaps_utils_1.logError)(`Error when updating ${snapId},`, error);
878
1309
  const errorString = error instanceof Error ? error.message : error.toString();
879
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updateApproval).call(this, pendingApproval.id, {
1310
+ this.#updateApproval(pendingApproval.id, {
880
1311
  loading: false,
881
1312
  error: errorString,
882
1313
  type: exports.SNAP_APPROVAL_UPDATE,
@@ -885,6 +1316,9 @@ class SnapController extends base_controller_1.BaseController {
885
1316
  throw error;
886
1317
  }
887
1318
  }
1319
+ async #resolveAllowlistVersion(snapId, versionRange) {
1320
+ return await this.messagingSystem.call('SnapsRegistry:resolveVersion', snapId, versionRange);
1321
+ }
888
1322
  /**
889
1323
  * Get metadata for the given snap ID.
890
1324
  *
@@ -893,9 +1327,215 @@ class SnapController extends base_controller_1.BaseController {
893
1327
  * verified.
894
1328
  */
895
1329
  async getRegistryMetadata(snapId) {
896
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertCanUsePlatform).call(this);
1330
+ this.#assertCanUsePlatform();
897
1331
  return await this.messagingSystem.call('SnapsRegistry:getMetadata', snapId);
898
1332
  }
1333
+ /**
1334
+ * Returns a promise representing the complete installation of the requested snap.
1335
+ * If the snap is already being installed, the previously pending promise will be returned.
1336
+ *
1337
+ * @param args - Object containing the snap id and either the URL of the snap's manifest,
1338
+ * or the snap's manifest and source code. The object may also optionally contain a target
1339
+ * version.
1340
+ * @returns The resulting snap object.
1341
+ */
1342
+ async #add(args) {
1343
+ const { id: snapId, location, versionRange } = args;
1344
+ this.#setupRuntime(snapId);
1345
+ const runtime = this.#getRuntimeExpect(snapId);
1346
+ if (!runtime.installPromise) {
1347
+ (0, logging_1.log)(`Adding snap: ${snapId}`);
1348
+ // If fetching and setting the snap succeeds, this property will be set
1349
+ // to null in the authorize() method.
1350
+ runtime.installPromise = (async () => {
1351
+ const fetchedSnap = await (0, utils_2.fetchSnap)(snapId, location);
1352
+ const manifest = fetchedSnap.manifest.result;
1353
+ if (!(0, utils_1.satisfiesVersionRange)(manifest.version, versionRange)) {
1354
+ throw new Error(`Version mismatch. Manifest for "${snapId}" specifies version "${manifest.version}" which doesn't satisfy requested version range "${versionRange}".`);
1355
+ }
1356
+ await this.#assertIsInstallAllowed(snapId, {
1357
+ version: manifest.version,
1358
+ checksum: manifest.source.shasum,
1359
+ permissions: manifest.initialPermissions,
1360
+ });
1361
+ return this.#set({
1362
+ ...args,
1363
+ files: fetchedSnap,
1364
+ id: snapId,
1365
+ });
1366
+ })();
1367
+ }
1368
+ try {
1369
+ return await runtime.installPromise;
1370
+ }
1371
+ catch (error) {
1372
+ // Reset promise so users can retry installation in case the problem is
1373
+ // temporary.
1374
+ runtime.installPromise = null;
1375
+ throw error;
1376
+ }
1377
+ }
1378
+ async #startSnap(snapData) {
1379
+ const { snapId } = snapData;
1380
+ if (this.isRunning(snapId)) {
1381
+ throw new Error(`Snap "${snapId}" is already started.`);
1382
+ }
1383
+ try {
1384
+ const runtime = this.#getRuntimeExpect(snapId);
1385
+ const result = await this.messagingSystem.call('ExecutionService:executeSnap', {
1386
+ ...snapData,
1387
+ endowments: await this.#getEndowments(snapId),
1388
+ });
1389
+ this.#transition(snapId, snaps_utils_1.SnapStatusEvents.Start);
1390
+ // We treat the initialization of the snap as the first request, for idle timing purposes.
1391
+ runtime.lastRequest = Date.now();
1392
+ return result;
1393
+ }
1394
+ catch (error) {
1395
+ await this.#terminateSnap(snapId);
1396
+ throw error;
1397
+ }
1398
+ }
1399
+ /**
1400
+ * Gets the names of all endowments that will be added to the Snap's
1401
+ * Compartment when it executes. These should be the names of global
1402
+ * JavaScript APIs accessible in the root realm of the execution environment.
1403
+ *
1404
+ * Throws an error if the endowment getter for a permission returns a truthy
1405
+ * value that is not an array of strings.
1406
+ *
1407
+ * @param snapId - The id of the snap whose SES endowments to get.
1408
+ * @returns An array of the names of the endowments.
1409
+ */
1410
+ async #getEndowments(snapId) {
1411
+ let allEndowments = [];
1412
+ for (const permissionName of this.#environmentEndowmentPermissions) {
1413
+ if (this.messagingSystem.call('PermissionController:hasPermission', snapId, permissionName)) {
1414
+ const endowments = await this.messagingSystem.call('PermissionController:getEndowments', snapId, permissionName);
1415
+ if (endowments) {
1416
+ // We don't have any guarantees about the type of the endowments
1417
+ // value, so we have to guard at runtime.
1418
+ if (!Array.isArray(endowments) ||
1419
+ endowments.some((value) => typeof value !== 'string')) {
1420
+ throw new Error('Expected an array of string endowment names.');
1421
+ }
1422
+ allEndowments = allEndowments.concat(endowments);
1423
+ }
1424
+ }
1425
+ }
1426
+ const dedupedEndowments = [
1427
+ ...new Set([...snaps_utils_1.DEFAULT_ENDOWMENTS, ...allEndowments]),
1428
+ ];
1429
+ if (dedupedEndowments.length <
1430
+ // This is a bug in TypeScript: https://github.com/microsoft/TypeScript/issues/48313
1431
+ // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
1432
+ snaps_utils_1.DEFAULT_ENDOWMENTS.length + allEndowments.length) {
1433
+ (0, snaps_utils_1.logError)(`Duplicate endowments found for ${snapId}. Default endowments should not be requested.`, allEndowments);
1434
+ }
1435
+ return dedupedEndowments;
1436
+ }
1437
+ /**
1438
+ * Sets a snap in state. Called when a snap is installed or updated. Performs
1439
+ * various validation checks on the received arguments, and will throw if
1440
+ * validation fails.
1441
+ *
1442
+ * The snap will be enabled and unblocked by the time this method returns,
1443
+ * regardless of its previous state.
1444
+ *
1445
+ * See {@link SnapController.add} and {@link SnapController.updateSnap} for
1446
+ * usage.
1447
+ *
1448
+ * @param args - The add snap args.
1449
+ * @returns The resulting snap object.
1450
+ */
1451
+ #set(args) {
1452
+ const { id: snapId, origin, files, isUpdate = false, removable, preinstalled, hidden, hideSnapBranding, } = args;
1453
+ const { manifest, sourceCode: sourceCodeFile, svgIcon, auxiliaryFiles: rawAuxiliaryFiles, localizationFiles, } = files;
1454
+ (0, snaps_utils_1.assertIsSnapManifest)(manifest.result);
1455
+ const { version } = manifest.result;
1456
+ const sourceCode = sourceCodeFile.toString();
1457
+ (0, utils_1.assert)(typeof sourceCode === 'string' && sourceCode.length > 0, `Invalid source code for snap "${snapId}".`);
1458
+ const auxiliaryFiles = rawAuxiliaryFiles.map((file) => {
1459
+ (0, utils_1.assert)(typeof file.data.base64 === 'string');
1460
+ return {
1461
+ path: file.path,
1462
+ value: file.data.base64,
1463
+ };
1464
+ });
1465
+ const snapsState = this.state.snaps;
1466
+ const existingSnap = snapsState[snapId];
1467
+ const previousVersionHistory = existingSnap?.versionHistory ?? [];
1468
+ const versionHistory = [
1469
+ ...previousVersionHistory,
1470
+ {
1471
+ version,
1472
+ date: Date.now(),
1473
+ origin,
1474
+ },
1475
+ ];
1476
+ const localizedFiles = localizationFiles.map((file) => file.result);
1477
+ const snap = {
1478
+ // Restore relevant snap state if it exists
1479
+ ...existingSnap,
1480
+ // Note that the snap will be unblocked and enabled, regardless of its
1481
+ // previous state.
1482
+ blocked: false,
1483
+ enabled: true,
1484
+ removable,
1485
+ preinstalled,
1486
+ hidden,
1487
+ hideSnapBranding,
1488
+ id: snapId,
1489
+ initialConnections: manifest.result.initialConnections,
1490
+ initialPermissions: manifest.result.initialPermissions,
1491
+ manifest: manifest.result,
1492
+ status: this.#statusMachine.config.initial,
1493
+ sourceCode,
1494
+ version,
1495
+ versionHistory,
1496
+ auxiliaryFiles,
1497
+ localizationFiles: localizedFiles,
1498
+ };
1499
+ // If the snap was blocked, it isn't any longer
1500
+ delete snap.blockInformation;
1501
+ // store the snap back in state
1502
+ const { inversePatches } = this.update((state) => {
1503
+ state.snaps[snapId] = snap;
1504
+ });
1505
+ // checking for isUpdate here as this function is also used in
1506
+ // the install flow, we do not care to create snapshots for installs
1507
+ if (isUpdate) {
1508
+ const rollbackSnapshot = this.#getRollbackSnapshot(snapId);
1509
+ if (rollbackSnapshot !== undefined) {
1510
+ rollbackSnapshot.statePatches = inversePatches;
1511
+ }
1512
+ }
1513
+ // In case the Snap uses a localized manifest, we need to get the
1514
+ // proposed name from the localized manifest.
1515
+ const { proposedName } = (0, snaps_utils_1.getLocalizedSnapManifest)(manifest.result, 'en', localizedFiles);
1516
+ this.messagingSystem.call('SubjectMetadataController:addSubjectMetadata', {
1517
+ subjectType: permission_controller_1.SubjectType.Snap,
1518
+ name: proposedName,
1519
+ origin: snap.id,
1520
+ version,
1521
+ svgIcon: svgIcon?.toString() ?? null,
1522
+ });
1523
+ return { ...snap, sourceCode };
1524
+ }
1525
+ #validateSnapPermissions(processedPermissions) {
1526
+ const permissionKeys = Object.keys(processedPermissions);
1527
+ const handlerPermissions = Array.from(new Set(Object.values(snaps_rpc_methods_1.handlerEndowments)));
1528
+ (0, utils_1.assert)(permissionKeys.some((key) => handlerPermissions.includes(key)), `A snap must request at least one of the following permissions: ${handlerPermissions
1529
+ .filter((handler) => handler !== null)
1530
+ .join(', ')}.`);
1531
+ const excludedPermissionErrors = permissionKeys.reduce((errors, permission) => {
1532
+ if ((0, utils_1.hasProperty)(this.#excludedPermissions, permission)) {
1533
+ errors.push(this.#excludedPermissions[permission]);
1534
+ }
1535
+ return errors;
1536
+ }, []);
1537
+ (0, utils_1.assert)(excludedPermissionErrors.length === 0, `One or more permissions are not allowed:\n${excludedPermissionErrors.join('\n')}`);
1538
+ }
899
1539
  /**
900
1540
  * Initiates a request for the given snap's initial permissions.
901
1541
  * Must be called in order. See processRequestedSnap.
@@ -913,31 +1553,31 @@ class SnapController extends base_controller_1.BaseController {
913
1553
  const { initialPermissions, initialConnections } = snap;
914
1554
  try {
915
1555
  const processedPermissions = (0, snaps_rpc_methods_1.processSnapPermissions)(initialPermissions);
916
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_validateSnapPermissions).call(this, processedPermissions);
917
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updateApproval).call(this, pendingApproval.id, {
1556
+ this.#validateSnapPermissions(processedPermissions);
1557
+ this.#updateApproval(pendingApproval.id, {
918
1558
  loading: false,
919
1559
  connections: initialConnections ?? {},
920
1560
  permissions: processedPermissions,
921
1561
  });
922
1562
  const { permissions: approvedPermissions, ...requestData } = (await pendingApproval.promise);
923
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updatePermissions).call(this, {
1563
+ this.#updatePermissions({
924
1564
  snapId,
925
1565
  newPermissions: approvedPermissions,
926
1566
  requestData,
927
1567
  });
928
1568
  if (snap.manifest.initialConnections) {
929
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_handleInitialConnections).call(this, snapId, null, snap.manifest.initialConnections);
1569
+ this.#handleInitialConnections(snapId, null, snap.manifest.initialConnections);
930
1570
  }
931
1571
  }
932
1572
  finally {
933
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1573
+ const runtime = this.#getRuntimeExpect(snapId);
934
1574
  runtime.installPromise = null;
935
1575
  }
936
1576
  }
937
1577
  destroy() {
938
1578
  super.destroy();
939
- if (__classPrivateFieldGet(this, _SnapController_timeoutForLastRequestStatus, "f")) {
940
- clearTimeout(__classPrivateFieldGet(this, _SnapController_timeoutForLastRequestStatus, "f"));
1579
+ if (this.#timeoutForLastRequestStatus) {
1580
+ clearTimeout(this.#timeoutForLastRequestStatus);
941
1581
  }
942
1582
  /* eslint-disable @typescript-eslint/unbound-method */
943
1583
  this.messagingSystem.unsubscribe('ExecutionService:unhandledError', this._onUnhandledSnapError);
@@ -958,7 +1598,7 @@ class SnapController extends base_controller_1.BaseController {
958
1598
  * @returns The result of the JSON-RPC request.
959
1599
  */
960
1600
  async handleRequest({ snapId, origin, handler: handlerType, request: rawRequest, }) {
961
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertCanUsePlatform).call(this);
1601
+ this.#assertCanUsePlatform();
962
1602
  const request = {
963
1603
  jsonrpc: '2.0',
964
1604
  id: (0, nanoid_1.nanoid)(),
@@ -988,911 +1628,411 @@ class SnapController extends base_controller_1.BaseController {
988
1628
  throw new Error(`Snap "${snapId}" is not permitted to handle requests from "${origin}".`);
989
1629
  }
990
1630
  }
991
- const handler = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRpcRequestHandler).call(this, snapId);
1631
+ const handler = this.#getRpcRequestHandler(snapId);
992
1632
  if (!handler) {
993
1633
  throw new Error(`Snap RPC message handler not found for snap "${snapId}".`);
994
1634
  }
995
- const timeout = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getExecutionTimeout).call(this, handlerPermissions);
1635
+ const timeout = this.#getExecutionTimeout(handlerPermissions);
996
1636
  return handler({ origin, handler: handlerType, request, timeout });
997
1637
  }
998
- }
999
- exports.SnapController = SnapController;
1000
- _SnapController_closeAllConnections = new WeakMap(), _SnapController_dynamicPermissions = new WeakMap(), _SnapController_environmentEndowmentPermissions = new WeakMap(), _SnapController_excludedPermissions = new WeakMap(), _SnapController_featureFlags = new WeakMap(), _SnapController_fetchFunction = new WeakMap(), _SnapController_idleTimeCheckInterval = new WeakMap(), _SnapController_maxIdleTime = new WeakMap(), _SnapController_encryptor = new WeakMap(), _SnapController_getMnemonic = new WeakMap(), _SnapController_getFeatureFlags = new WeakMap(), _SnapController_detectSnapLocation = new WeakMap(), _SnapController_snapsRuntimeData = new WeakMap(), _SnapController_rollbackSnapshots = new WeakMap(), _SnapController_timeoutForLastRequestStatus = new WeakMap(), _SnapController_statusMachine = new WeakMap(), _SnapController_preinstalledSnaps = new WeakMap(), _SnapController_instances = new WeakSet(), _SnapController_initializeStateMachine = function _SnapController_initializeStateMachine() {
1001
- const disableGuard = ({ snapId }) => {
1002
- return this.getExpect(snapId).enabled;
1003
- };
1004
- const statusConfig = {
1005
- initial: snaps_utils_1.SnapStatus.Installing,
1006
- states: {
1007
- [snaps_utils_1.SnapStatus.Installing]: {
1008
- on: {
1009
- [snaps_utils_1.SnapStatusEvents.Start]: {
1010
- target: snaps_utils_1.SnapStatus.Running,
1011
- cond: disableGuard,
1012
- },
1013
- },
1014
- },
1015
- [snaps_utils_1.SnapStatus.Updating]: {
1016
- on: {
1017
- [snaps_utils_1.SnapStatusEvents.Start]: {
1018
- target: snaps_utils_1.SnapStatus.Running,
1019
- cond: disableGuard,
1020
- },
1021
- [snaps_utils_1.SnapStatusEvents.Stop]: snaps_utils_1.SnapStatus.Stopped,
1022
- },
1023
- },
1024
- [snaps_utils_1.SnapStatus.Running]: {
1025
- on: {
1026
- [snaps_utils_1.SnapStatusEvents.Stop]: snaps_utils_1.SnapStatus.Stopped,
1027
- [snaps_utils_1.SnapStatusEvents.Crash]: snaps_utils_1.SnapStatus.Crashed,
1028
- },
1029
- },
1030
- [snaps_utils_1.SnapStatus.Stopped]: {
1031
- on: {
1032
- [snaps_utils_1.SnapStatusEvents.Start]: {
1033
- target: snaps_utils_1.SnapStatus.Running,
1034
- cond: disableGuard,
1035
- },
1036
- [snaps_utils_1.SnapStatusEvents.Update]: snaps_utils_1.SnapStatus.Updating,
1037
- },
1038
- },
1039
- [snaps_utils_1.SnapStatus.Crashed]: {
1040
- on: {
1041
- [snaps_utils_1.SnapStatusEvents.Start]: {
1042
- target: snaps_utils_1.SnapStatus.Running,
1043
- cond: disableGuard,
1044
- },
1045
- [snaps_utils_1.SnapStatusEvents.Update]: snaps_utils_1.SnapStatus.Updating,
1046
- },
1047
- },
1048
- },
1049
- };
1050
- __classPrivateFieldSet(this, _SnapController_statusMachine, (0, fsm_1.createMachine)(statusConfig), "f");
1051
- (0, fsm_2.validateMachine)(__classPrivateFieldGet(this, _SnapController_statusMachine, "f"));
1052
- }, _SnapController_registerMessageHandlers = function _SnapController_registerMessageHandlers() {
1053
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:clearSnapState`, (...args) => this.clearSnapState(...args));
1054
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:get`, (...args) => this.get(...args));
1055
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:getSnapState`, async (...args) => this.getSnapState(...args));
1056
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:handleRequest`, async (...args) => this.handleRequest(...args));
1057
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:has`, (...args) => this.has(...args));
1058
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:updateBlockedSnaps`, async () => this.updateBlockedSnaps());
1059
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:updateSnapState`, async (...args) => this.updateSnapState(...args));
1060
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:enable`, (...args) => this.enableSnap(...args));
1061
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:disable`, async (...args) => this.disableSnap(...args));
1062
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:remove`, async (...args) => this.removeSnap(...args));
1063
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:getPermitted`, (...args) => this.getPermittedSnaps(...args));
1064
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:install`, async (...args) => this.installSnaps(...args));
1065
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:getAll`, (...args) => this.getAllSnaps(...args));
1066
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:incrementActiveReferences`, (...args) => this.incrementActiveReferences(...args));
1067
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:decrementActiveReferences`, (...args) => this.decrementActiveReferences(...args));
1068
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:getRegistryMetadata`, async (...args) => this.getRegistryMetadata(...args));
1069
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:disconnectOrigin`, (...args) => this.removeSnapFromSubject(...args));
1070
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:revokeDynamicPermissions`, (...args) => this.revokeDynamicSnapPermissions(...args));
1071
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:getFile`, async (...args) => this.getSnapFile(...args));
1072
- this.messagingSystem.registerActionHandler(`${exports.controllerName}:stopAllSnaps`, async (...args) => this.stopAllSnaps(...args));
1073
- }, _SnapController_handlePreinstalledSnaps = function _SnapController_handlePreinstalledSnaps(preinstalledSnaps) {
1074
- for (const { snapId, manifest, files, removable, hidden, hideSnapBranding, } of preinstalledSnaps) {
1075
- const existingSnap = this.get(snapId);
1076
- const isAlreadyInstalled = existingSnap !== undefined;
1077
- const isUpdate = isAlreadyInstalled && (0, utils_1.gtVersion)(manifest.version, existingSnap.version);
1078
- // Disallow downgrades and overwriting non preinstalled snaps
1079
- if (isAlreadyInstalled &&
1080
- (!isUpdate || existingSnap.preinstalled !== true)) {
1081
- continue;
1082
- }
1083
- const manifestFile = new snaps_utils_1.VirtualFile({
1084
- path: snaps_utils_1.NpmSnapFileNames.Manifest,
1085
- value: JSON.stringify(manifest),
1086
- result: manifest,
1087
- });
1088
- const virtualFiles = files.map(({ path, value }) => new snaps_utils_1.VirtualFile({ value, path }));
1089
- const { filePath, iconPath } = manifest.source.location.npm;
1090
- const sourceCode = virtualFiles.find((file) => file.path === filePath);
1091
- const svgIcon = iconPath
1092
- ? virtualFiles.find((file) => file.path === iconPath)
1093
- : undefined;
1094
- (0, utils_1.assert)(sourceCode, 'Source code not provided for preinstalled snap.');
1095
- (0, utils_1.assert)(!iconPath || (iconPath && svgIcon), 'Icon not provided for preinstalled snap.');
1096
- (0, utils_1.assert)(manifest.source.files === undefined, 'Auxiliary files are not currently supported for preinstalled snaps.');
1097
- const localizationFiles = manifest.source.locales?.map((path) => virtualFiles.find((file) => file.path === path)) ?? [];
1098
- const validatedLocalizationFiles = (0, snaps_utils_1.getValidatedLocalizationFiles)(localizationFiles.filter(Boolean));
1099
- (0, utils_1.assert)(localizationFiles.length === validatedLocalizationFiles.length, 'Missing localization files for preinstalled snap.');
1100
- const filesObject = {
1101
- manifest: manifestFile,
1102
- sourceCode,
1103
- svgIcon,
1104
- auxiliaryFiles: [],
1105
- localizationFiles: validatedLocalizationFiles,
1106
- };
1107
- // Add snap to the SnapController state
1108
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_set).call(this, {
1109
- id: snapId,
1110
- origin: 'metamask',
1111
- files: filesObject,
1112
- removable,
1113
- hidden,
1114
- hideSnapBranding,
1115
- preinstalled: true,
1116
- });
1117
- // Setup permissions
1118
- const processedPermissions = (0, snaps_rpc_methods_1.processSnapPermissions)(manifest.initialPermissions);
1119
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_validateSnapPermissions).call(this, processedPermissions);
1120
- const { newPermissions, unusedPermissions } = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_calculatePermissionsChange).call(this, snapId, processedPermissions);
1121
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updatePermissions).call(this, { snapId, newPermissions, unusedPermissions });
1122
- if (manifest.initialConnections) {
1123
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_handleInitialConnections).call(this, snapId, existingSnap?.initialConnections ?? null, manifest.initialConnections);
1124
- }
1125
- // Set status
1126
- this.update((state) => {
1127
- state.snaps[snapId].status = snaps_utils_1.SnapStatus.Stopped;
1128
- });
1129
- }
1130
- }, _SnapController_pollForLastRequestStatus = function _SnapController_pollForLastRequestStatus() {
1131
- __classPrivateFieldSet(this, _SnapController_timeoutForLastRequestStatus, setTimeout(() => {
1132
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_stopSnapsLastRequestPastMax).call(this).catch((error) => {
1133
- // TODO: Decide how to handle errors.
1134
- (0, snaps_utils_1.logError)(error);
1135
- });
1136
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_pollForLastRequestStatus).call(this);
1137
- }, __classPrivateFieldGet(this, _SnapController_idleTimeCheckInterval, "f")), "f");
1138
- }, _SnapController_blockSnap =
1139
- /**
1140
- * Blocks an installed snap and prevents it from being started again. Emits
1141
- * {@link SnapBlocked}. Does nothing if the snap is not installed.
1142
- *
1143
- * @param snapId - The snap to block.
1144
- * @param blockedSnapInfo - Information detailing why the snap is blocked.
1145
- */
1146
- async function _SnapController_blockSnap(snapId, blockedSnapInfo) {
1147
- if (!this.has(snapId)) {
1148
- return;
1638
+ /**
1639
+ * Determine the execution timeout for a given handler permission.
1640
+ *
1641
+ * If no permission is specified or the permission itself has no execution timeout defined
1642
+ * the constructor argument `maxRequestTime` will be used.
1643
+ *
1644
+ * @param permission - An optional permission constraint for the handler being called.
1645
+ * @returns The execution timeout for the given handler.
1646
+ */
1647
+ #getExecutionTimeout(permission) {
1648
+ return (0, snaps_rpc_methods_1.getMaxRequestTimeCaveat)(permission) ?? this.maxRequestTime;
1149
1649
  }
1150
- try {
1151
- this.update((state) => {
1152
- state.snaps[snapId].blocked = true;
1153
- state.snaps[snapId].blockInformation = blockedSnapInfo;
1154
- });
1155
- await this.disableSnap(snapId);
1156
- }
1157
- catch (error) {
1158
- (0, snaps_utils_1.logError)(`Encountered error when stopping blocked snap "${snapId}".`, error);
1159
- }
1160
- this.messagingSystem.publish(`${exports.controllerName}:snapBlocked`, snapId, blockedSnapInfo);
1161
- }, _SnapController_unblockSnap = function _SnapController_unblockSnap(snapId) {
1162
- if (!this.has(snapId) || !this.state.snaps[snapId].blocked) {
1163
- return;
1164
- }
1165
- this.update((state) => {
1166
- state.snaps[snapId].blocked = false;
1167
- delete state.snaps[snapId].blockInformation;
1168
- });
1169
- this.messagingSystem.publish(`${exports.controllerName}:snapUnblocked`, snapId);
1170
- }, _SnapController_assertIsInstallAllowed = async function _SnapController_assertIsInstallAllowed(snapId, snapInfo) {
1171
- const results = await this.messagingSystem.call('SnapsRegistry:get', {
1172
- [snapId]: snapInfo,
1173
- });
1174
- const result = results[snapId];
1175
- if (result.status === registry_1.SnapsRegistryStatus.Blocked) {
1176
- throw new Error(`Cannot install version "${snapInfo.version}" of snap "${snapId}": The version is blocked. ${result.reason?.explanation ?? ''}`);
1177
- }
1178
- const isAllowlistingRequired = Object.keys(snapInfo.permissions).some((permission) => !constants_1.ALLOWED_PERMISSIONS.includes(permission));
1179
- if (__classPrivateFieldGet(this, _SnapController_featureFlags, "f").requireAllowlist &&
1180
- isAllowlistingRequired &&
1181
- result.status !== registry_1.SnapsRegistryStatus.Verified) {
1182
- throw new Error(`Cannot install version "${snapInfo.version}" of snap "${snapId}": ${result.status === registry_1.SnapsRegistryStatus.Unavailable
1183
- ? 'The registry is temporarily unavailable.'
1184
- : 'The snap is not on the allowlist.'}`);
1185
- }
1186
- }, _SnapController_assertCanInstallSnaps = function _SnapController_assertCanInstallSnaps() {
1187
- (0, utils_1.assert)(__classPrivateFieldGet(this, _SnapController_featureFlags, "f").disableSnapInstallation !== true, 'Installing Snaps is currently disabled in this version of MetaMask.');
1188
- }, _SnapController_assertCanUsePlatform = function _SnapController_assertCanUsePlatform() {
1189
- const flags = __classPrivateFieldGet(this, _SnapController_getFeatureFlags, "f").call(this);
1190
- (0, utils_1.assert)(flags.disableSnaps !== true, 'The Snaps platform requires basic functionality to be used. Enable basic functionality in the settings to use the Snaps platform.');
1191
- }, _SnapController_stopSnapsLastRequestPastMax = async function _SnapController_stopSnapsLastRequestPastMax() {
1192
- const entries = [...__classPrivateFieldGet(this, _SnapController_snapsRuntimeData, "f").entries()];
1193
- return Promise.all(entries
1194
- .filter(([_snapId, runtime]) => runtime.activeReferences === 0 &&
1195
- runtime.pendingInboundRequests.length === 0 &&
1196
- runtime.lastRequest &&
1197
- __classPrivateFieldGet(this, _SnapController_maxIdleTime, "f") &&
1198
- (0, utils_1.timeSince)(runtime.lastRequest) > __classPrivateFieldGet(this, _SnapController_maxIdleTime, "f"))
1199
- .map(async ([snapId]) => this.stopSnap(snapId, snaps_utils_1.SnapStatusEvents.Stop)));
1200
- }, _SnapController_transition = function _SnapController_transition(snapId, event) {
1201
- const { interpreter } = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1202
- interpreter.send(event);
1203
- this.update((state) => {
1204
- state.snaps[snapId].status = interpreter.state.value;
1205
- });
1206
- }, _SnapController_terminateSnap =
1207
- /**
1208
- * Terminates the specified snap and emits the `snapTerminated` event.
1209
- *
1210
- * @param snapId - The snap to terminate.
1211
- */
1212
- async function _SnapController_terminateSnap(snapId) {
1213
- await this.messagingSystem.call('ExecutionService:terminateSnap', snapId);
1214
- // Hack to give up execution for a bit to let gracefully terminating Snaps return.
1215
- await new Promise((resolve) => setTimeout(resolve, 1));
1216
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1217
- // Unresponsive requests may still be timed, time them out.
1218
- runtime.pendingInboundRequests
1219
- .filter((pendingRequest) => pendingRequest.timer.status !== 'finished')
1220
- .forEach((pendingRequest) => pendingRequest.timer.finish());
1221
- // Hack to give up execution for a bit to let timed out requests return.
1222
- await new Promise((resolve) => setTimeout(resolve, 1));
1223
- this.messagingSystem.publish('SnapController:snapTerminated', this.getTruncatedExpect(snapId));
1224
- }, _SnapController_getSnapEncryptionKey =
1225
- /**
1226
- * Generate an encryption key to be used for state encryption for a given Snap.
1227
- *
1228
- * @param options - An options bag.
1229
- * @param options.snapId - The Snap ID.
1230
- * @param options.salt - A salt to be used for the encryption key.
1231
- * @param options.useCache - Whether to use caching or not.
1232
- * @param options.keyMetadata - Optional metadata about how to derive the encryption key.
1233
- * @returns An encryption key.
1234
- */
1235
- async function _SnapController_getSnapEncryptionKey({ snapId, salt: passedSalt, useCache, keyMetadata, }) {
1236
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1237
- if (runtime.encryptionKey && runtime.encryptionSalt && useCache) {
1238
- return {
1239
- key: await __classPrivateFieldGet(this, _SnapController_encryptor, "f").importKey(runtime.encryptionKey),
1240
- salt: runtime.encryptionSalt,
1650
+ /**
1651
+ * Gets the RPC message handler for the given snap.
1652
+ *
1653
+ * @param snapId - The id of the Snap whose message handler to get.
1654
+ * @returns The RPC handler for the given snap.
1655
+ */
1656
+ #getRpcRequestHandler(snapId) {
1657
+ const runtime = this.#getRuntimeExpect(snapId);
1658
+ const existingHandler = runtime.rpcHandler;
1659
+ if (existingHandler) {
1660
+ return existingHandler;
1661
+ }
1662
+ const requestQueue = new RequestQueue_1.RequestQueue(5);
1663
+ // We need to set up this promise map to map snapIds to their respective startPromises,
1664
+ // because otherwise we would lose context on the correct startPromise.
1665
+ const startPromises = new Map();
1666
+ const rpcHandler = async ({ origin, handler: handlerType, request, timeout, }) => {
1667
+ if (this.state.snaps[snapId].enabled === false) {
1668
+ throw new Error(`Snap "${snapId}" is disabled.`);
1669
+ }
1670
+ if (this.state.snaps[snapId].status === snaps_utils_1.SnapStatus.Installing) {
1671
+ throw new Error(`Snap "${snapId}" is currently being installed. Please try again later.`);
1672
+ }
1673
+ if (!this.isRunning(snapId)) {
1674
+ let localStartPromise = startPromises.get(snapId);
1675
+ if (!localStartPromise) {
1676
+ localStartPromise = this.startSnap(snapId);
1677
+ startPromises.set(snapId, localStartPromise);
1678
+ }
1679
+ else if (requestQueue.get(origin) >= requestQueue.maxQueueSize) {
1680
+ throw new Error('Exceeds maximum number of requests waiting to be resolved, please try again.');
1681
+ }
1682
+ requestQueue.increment(origin);
1683
+ try {
1684
+ await localStartPromise;
1685
+ }
1686
+ finally {
1687
+ requestQueue.decrement(origin);
1688
+ // Only delete startPromise for a snap if its value hasn't changed
1689
+ if (startPromises.get(snapId) === localStartPromise) {
1690
+ startPromises.delete(snapId);
1691
+ }
1692
+ }
1693
+ }
1694
+ const timer = new Timer_1.Timer(timeout);
1695
+ this.#recordSnapRpcRequestStart(snapId, request.id, timer);
1696
+ const handleRpcRequestPromise = this.messagingSystem.call('ExecutionService:handleRpcRequest', snapId, { origin, handler: handlerType, request });
1697
+ // This will either get the result or reject due to the timeout.
1698
+ try {
1699
+ const result = await (0, utils_2.withTimeout)(handleRpcRequestPromise, timer);
1700
+ if (result === utils_2.hasTimedOut) {
1701
+ throw new Error(`${snapId} failed to respond to the request in time.`);
1702
+ }
1703
+ await this.#assertSnapRpcRequestResult(snapId, handlerType, result);
1704
+ const transformedResult = await this.#transformSnapRpcRequestResult(snapId, handlerType, result);
1705
+ this.#recordSnapRpcRequestFinish(snapId, request.id);
1706
+ return transformedResult;
1707
+ }
1708
+ catch (error) {
1709
+ // We flag the RPC request as finished early since termination may affect pending requests
1710
+ this.#recordSnapRpcRequestFinish(snapId, request.id);
1711
+ const [jsonRpcError, handled] = (0, snaps_utils_1.unwrapError)(error);
1712
+ if (!handled) {
1713
+ await this.stopSnap(snapId, snaps_utils_1.SnapStatusEvents.Crash);
1714
+ }
1715
+ throw jsonRpcError;
1716
+ }
1241
1717
  };
1718
+ runtime.rpcHandler = rpcHandler;
1719
+ return rpcHandler;
1242
1720
  }
1243
- const salt = passedSalt ?? __classPrivateFieldGet(this, _SnapController_encryptor, "f").generateSalt();
1244
- const mnemonicPhrase = await __classPrivateFieldGet(this, _SnapController_getMnemonic, "f").call(this);
1245
- const entropy = await (0, snaps_rpc_methods_1.getEncryptionEntropy)({ snapId, mnemonicPhrase });
1246
- const encryptionKey = await __classPrivateFieldGet(this, _SnapController_encryptor, "f").keyFromPassword(entropy, salt, true, keyMetadata);
1247
- const exportedKey = await __classPrivateFieldGet(this, _SnapController_encryptor, "f").exportKey(encryptionKey);
1248
- // Cache exported encryption key in runtime
1249
- if (useCache) {
1250
- runtime.encryptionKey = exportedKey;
1251
- runtime.encryptionSalt = salt;
1252
- }
1253
- return { key: encryptionKey, salt };
1254
- }, _SnapController_decryptSnapState =
1255
- /**
1256
- * Decrypt the encrypted state for a given Snap.
1257
- *
1258
- * @param snapId - The Snap ID.
1259
- * @param state - The encrypted state as a string.
1260
- * @returns A valid JSON object derived from the encrypted state.
1261
- * @throws If the decryption fails or the decrypted state is not valid JSON.
1262
- */
1263
- async function _SnapController_decryptSnapState(snapId, state) {
1264
- try {
1265
- const parsed = (0, snaps_utils_1.parseJson)(state);
1266
- const { salt, keyMetadata } = parsed;
1267
- const useCache = __classPrivateFieldGet(this, _SnapController_encryptor, "f").isVaultUpdated(state);
1268
- const { key } = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getSnapEncryptionKey).call(this, {
1269
- snapId,
1270
- salt,
1271
- useCache,
1272
- // When decrypting state we expect key metadata to be present.
1273
- // If it isn't present, we assume that the Snap state we are decrypting is old enough to use the legacy encryption params.
1274
- keyMetadata: keyMetadata ?? constants_1.LEGACY_ENCRYPTION_KEY_DERIVATION_OPTIONS,
1275
- });
1276
- const decryptedState = await __classPrivateFieldGet(this, _SnapController_encryptor, "f").decryptWithKey(key, parsed);
1277
- (0, utils_1.assert)((0, utils_1.isValidJson)(decryptedState));
1278
- return decryptedState;
1279
- }
1280
- catch {
1281
- throw rpc_errors_1.rpcErrors.internal({
1282
- message: 'Failed to decrypt snap state, the state must be corrupted.',
1283
- });
1284
- }
1285
- }, _SnapController_encryptSnapState =
1286
- /**
1287
- * Encrypt a JSON state object for a given Snap.
1288
- *
1289
- * Note: This function does not assert the validity of the object,
1290
- * please ensure only valid JSON is passed to it.
1291
- *
1292
- * @param snapId - The Snap ID.
1293
- * @param state - The state object.
1294
- * @returns A string containing the encrypted JSON object.
1295
- */
1296
- async function _SnapController_encryptSnapState(snapId, state) {
1297
- const { key, salt } = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getSnapEncryptionKey).call(this, {
1298
- snapId,
1299
- useCache: true,
1300
- });
1301
- const encryptedState = await __classPrivateFieldGet(this, _SnapController_encryptor, "f").encryptWithKey(key, state);
1302
- encryptedState.salt = salt;
1303
- return JSON.stringify(encryptedState);
1304
- }, _SnapController_handleInitialConnections = function _SnapController_handleInitialConnections(snapId, previousInitialConnections, initialConnections) {
1305
- if (previousInitialConnections) {
1306
- const revokedInitialConnections = (0, utils_2.setDiff)(previousInitialConnections, initialConnections);
1307
- for (const origin of Object.keys(revokedInitialConnections)) {
1308
- this.removeSnapFromSubject(origin, snapId);
1309
- }
1310
- }
1311
- for (const origin of Object.keys(initialConnections)) {
1312
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_addSnapToSubject).call(this, origin, snapId);
1313
- }
1314
- }, _SnapController_addSnapToSubject = function _SnapController_addSnapToSubject(origin, snapId) {
1315
- const subjectPermissions = this.messagingSystem.call('PermissionController:getPermissions', origin);
1316
- const existingCaveat = subjectPermissions?.[snaps_rpc_methods_1.WALLET_SNAP_PERMISSION_KEY]?.caveats?.find((caveat) => caveat.type === snaps_utils_1.SnapCaveatType.SnapIds);
1317
- const subjectHasSnap = Boolean(existingCaveat?.value?.[snapId]);
1318
- // If the subject is already connected to the snap, this is a no-op.
1319
- if (subjectHasSnap) {
1320
- return;
1321
- }
1322
- // If an existing caveat exists, we add the snap to that.
1323
- if (existingCaveat) {
1324
- this.messagingSystem.call('PermissionController:updateCaveat', origin, snaps_rpc_methods_1.WALLET_SNAP_PERMISSION_KEY, snaps_utils_1.SnapCaveatType.SnapIds, { ...existingCaveat.value, [snapId]: {} });
1325
- return;
1326
- }
1327
- const approvedPermissions = {
1328
- [snaps_rpc_methods_1.WALLET_SNAP_PERMISSION_KEY]: {
1329
- caveats: [
1330
- {
1331
- type: snaps_utils_1.SnapCaveatType.SnapIds,
1332
- value: {
1333
- [snapId]: {},
1334
- },
1335
- },
1336
- ],
1337
- },
1338
- };
1339
- this.messagingSystem.call('PermissionController:grantPermissions', {
1340
- approvedPermissions,
1341
- subject: { origin },
1342
- });
1343
- }, _SnapController_removeSnapFromSubjects = function _SnapController_removeSnapFromSubjects(snapId) {
1344
- const subjects = this.messagingSystem.call('PermissionController:getSubjectNames');
1345
- for (const subject of subjects) {
1346
- this.removeSnapFromSubject(subject, snapId);
1347
- }
1348
- }, _SnapController_revokeAllSnapPermissions = function _SnapController_revokeAllSnapPermissions(snapId) {
1349
- if (this.messagingSystem.call('PermissionController:hasPermissions', snapId)) {
1350
- this.messagingSystem.call('PermissionController:revokeAllPermissions', snapId);
1351
- }
1352
- }, _SnapController_createApproval = function _SnapController_createApproval({ origin, snapId, type, }) {
1353
- const id = (0, nanoid_1.nanoid)();
1354
- const promise = this.messagingSystem.call('ApprovalController:addRequest', {
1355
- origin,
1356
- id,
1357
- type,
1358
- requestData: {
1359
- // Mirror previous installation metadata
1360
- metadata: { id, origin: snapId, dappOrigin: origin },
1361
- snapId,
1362
- },
1363
- requestState: {
1364
- loading: true,
1365
- },
1366
- }, true);
1367
- return { id, promise };
1368
- }, _SnapController_updateApproval = function _SnapController_updateApproval(id, requestState) {
1369
- try {
1370
- this.messagingSystem.call('ApprovalController:updateRequestState', {
1371
- id,
1372
- requestState,
1373
- });
1374
- }
1375
- catch {
1376
- // Do nothing
1377
- }
1378
- }, _SnapController_resolveAllowlistVersion = async function _SnapController_resolveAllowlistVersion(snapId, versionRange) {
1379
- return await this.messagingSystem.call('SnapsRegistry:resolveVersion', snapId, versionRange);
1380
- }, _SnapController_add =
1381
- /**
1382
- * Returns a promise representing the complete installation of the requested snap.
1383
- * If the snap is already being installed, the previously pending promise will be returned.
1384
- *
1385
- * @param args - Object containing the snap id and either the URL of the snap's manifest,
1386
- * or the snap's manifest and source code. The object may also optionally contain a target
1387
- * version.
1388
- * @returns The resulting snap object.
1389
- */
1390
- async function _SnapController_add(args) {
1391
- const { id: snapId, location, versionRange } = args;
1392
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_setupRuntime).call(this, snapId);
1393
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1394
- if (!runtime.installPromise) {
1395
- (0, logging_1.log)(`Adding snap: ${snapId}`);
1396
- // If fetching and setting the snap succeeds, this property will be set
1397
- // to null in the authorize() method.
1398
- runtime.installPromise = (async () => {
1399
- const fetchedSnap = await (0, utils_2.fetchSnap)(snapId, location);
1400
- const manifest = fetchedSnap.manifest.result;
1401
- if (!(0, utils_1.satisfiesVersionRange)(manifest.version, versionRange)) {
1402
- throw new Error(`Version mismatch. Manifest for "${snapId}" specifies version "${manifest.version}" which doesn't satisfy requested version range "${versionRange}".`);
1403
- }
1404
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertIsInstallAllowed).call(this, snapId, {
1405
- version: manifest.version,
1406
- checksum: manifest.source.shasum,
1407
- permissions: manifest.initialPermissions,
1408
- });
1409
- return __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_set).call(this, {
1410
- ...args,
1411
- files: fetchedSnap,
1412
- id: snapId,
1413
- });
1414
- })();
1415
- }
1416
- try {
1417
- return await runtime.installPromise;
1418
- }
1419
- catch (error) {
1420
- // Reset promise so users can retry installation in case the problem is
1421
- // temporary.
1422
- runtime.installPromise = null;
1423
- throw error;
1424
- }
1425
- }, _SnapController_startSnap = async function _SnapController_startSnap(snapData) {
1426
- const { snapId } = snapData;
1427
- if (this.isRunning(snapId)) {
1428
- throw new Error(`Snap "${snapId}" is already started.`);
1429
- }
1430
- try {
1431
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1432
- const result = await this.messagingSystem.call('ExecutionService:executeSnap', {
1433
- ...snapData,
1434
- endowments: await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getEndowments).call(this, snapId),
1435
- });
1436
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_transition).call(this, snapId, snaps_utils_1.SnapStatusEvents.Start);
1437
- // We treat the initialization of the snap as the first request, for idle timing purposes.
1438
- runtime.lastRequest = Date.now();
1439
- return result;
1721
+ /**
1722
+ * Create a dynamic interface in the SnapInterfaceController.
1723
+ *
1724
+ * @param snapId - The snap ID.
1725
+ * @param content - The initial interface content.
1726
+ * @returns An identifier that can be used to identify the interface.
1727
+ */
1728
+ async #createInterface(snapId, content) {
1729
+ return this.messagingSystem.call('SnapInterfaceController:createInterface', snapId, content);
1440
1730
  }
1441
- catch (error) {
1442
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_terminateSnap).call(this, snapId);
1443
- throw error;
1731
+ #assertInterfaceExists(snapId, id) {
1732
+ // This will throw if the interface isn't accessible, but we assert nevertheless.
1733
+ (0, utils_1.assert)(this.messagingSystem.call('SnapInterfaceController:getInterface', snapId, id));
1444
1734
  }
1445
- }, _SnapController_getEndowments =
1446
- /**
1447
- * Gets the names of all endowments that will be added to the Snap's
1448
- * Compartment when it executes. These should be the names of global
1449
- * JavaScript APIs accessible in the root realm of the execution environment.
1450
- *
1451
- * Throws an error if the endowment getter for a permission returns a truthy
1452
- * value that is not an array of strings.
1453
- *
1454
- * @param snapId - The id of the snap whose SES endowments to get.
1455
- * @returns An array of the names of the endowments.
1456
- */
1457
- async function _SnapController_getEndowments(snapId) {
1458
- let allEndowments = [];
1459
- for (const permissionName of __classPrivateFieldGet(this, _SnapController_environmentEndowmentPermissions, "f")) {
1460
- if (this.messagingSystem.call('PermissionController:hasPermission', snapId, permissionName)) {
1461
- const endowments = await this.messagingSystem.call('PermissionController:getEndowments', snapId, permissionName);
1462
- if (endowments) {
1463
- // We don't have any guarantees about the type of the endowments
1464
- // value, so we have to guard at runtime.
1465
- if (!Array.isArray(endowments) ||
1466
- endowments.some((value) => typeof value !== 'string')) {
1467
- throw new Error('Expected an array of string endowment names.');
1735
+ /**
1736
+ * Transform a RPC request result if necessary.
1737
+ *
1738
+ * @param snapId - The snap ID of the snap that produced the result.
1739
+ * @param handlerType - The handler type that produced the result.
1740
+ * @param result - The result.
1741
+ * @returns The transformed result if applicable, otherwise the original result.
1742
+ */
1743
+ async #transformSnapRpcRequestResult(snapId, handlerType, result) {
1744
+ switch (handlerType) {
1745
+ case snaps_utils_1.HandlerType.OnTransaction:
1746
+ case snaps_utils_1.HandlerType.OnSignature:
1747
+ case snaps_utils_1.HandlerType.OnHomePage: {
1748
+ // Since this type has been asserted earlier we can cast
1749
+ const castResult = result;
1750
+ // If a handler returns static content, we turn it into a dynamic UI
1751
+ if (castResult && (0, utils_1.hasProperty)(castResult, 'content')) {
1752
+ const { content, ...rest } = castResult;
1753
+ const id = await this.#createInterface(snapId, content);
1754
+ return { ...rest, id };
1468
1755
  }
1469
- allEndowments = allEndowments.concat(endowments);
1756
+ return result;
1470
1757
  }
1758
+ default:
1759
+ return result;
1471
1760
  }
1472
1761
  }
1473
- const dedupedEndowments = [
1474
- ...new Set([...snaps_utils_1.DEFAULT_ENDOWMENTS, ...allEndowments]),
1475
- ];
1476
- if (dedupedEndowments.length <
1477
- // This is a bug in TypeScript: https://github.com/microsoft/TypeScript/issues/48313
1478
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
1479
- snaps_utils_1.DEFAULT_ENDOWMENTS.length + allEndowments.length) {
1480
- (0, snaps_utils_1.logError)(`Duplicate endowments found for ${snapId}. Default endowments should not be requested.`, allEndowments);
1481
- }
1482
- return dedupedEndowments;
1483
- }, _SnapController_set = function _SnapController_set(args) {
1484
- const { id: snapId, origin, files, isUpdate = false, removable, preinstalled, hidden, hideSnapBranding, } = args;
1485
- const { manifest, sourceCode: sourceCodeFile, svgIcon, auxiliaryFiles: rawAuxiliaryFiles, localizationFiles, } = files;
1486
- (0, snaps_utils_1.assertIsSnapManifest)(manifest.result);
1487
- const { version } = manifest.result;
1488
- const sourceCode = sourceCodeFile.toString();
1489
- (0, utils_1.assert)(typeof sourceCode === 'string' && sourceCode.length > 0, `Invalid source code for snap "${snapId}".`);
1490
- const auxiliaryFiles = rawAuxiliaryFiles.map((file) => {
1491
- (0, utils_1.assert)(typeof file.data.base64 === 'string');
1492
- return {
1493
- path: file.path,
1494
- value: file.data.base64,
1495
- };
1496
- });
1497
- const snapsState = this.state.snaps;
1498
- const existingSnap = snapsState[snapId];
1499
- const previousVersionHistory = existingSnap?.versionHistory ?? [];
1500
- const versionHistory = [
1501
- ...previousVersionHistory,
1502
- {
1503
- version,
1504
- date: Date.now(),
1505
- origin,
1506
- },
1507
- ];
1508
- const localizedFiles = localizationFiles.map((file) => file.result);
1509
- const snap = {
1510
- // Restore relevant snap state if it exists
1511
- ...existingSnap,
1512
- // Note that the snap will be unblocked and enabled, regardless of its
1513
- // previous state.
1514
- blocked: false,
1515
- enabled: true,
1516
- removable,
1517
- preinstalled,
1518
- hidden,
1519
- hideSnapBranding,
1520
- id: snapId,
1521
- initialConnections: manifest.result.initialConnections,
1522
- initialPermissions: manifest.result.initialPermissions,
1523
- manifest: manifest.result,
1524
- status: __classPrivateFieldGet(this, _SnapController_statusMachine, "f").config.initial,
1525
- sourceCode,
1526
- version,
1527
- versionHistory,
1528
- auxiliaryFiles,
1529
- localizationFiles: localizedFiles,
1530
- };
1531
- // If the snap was blocked, it isn't any longer
1532
- delete snap.blockInformation;
1533
- // store the snap back in state
1534
- const { inversePatches } = this.update((state) => {
1535
- state.snaps[snapId] = snap;
1536
- });
1537
- // checking for isUpdate here as this function is also used in
1538
- // the install flow, we do not care to create snapshots for installs
1539
- if (isUpdate) {
1540
- const rollbackSnapshot = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRollbackSnapshot).call(this, snapId);
1541
- if (rollbackSnapshot !== undefined) {
1542
- rollbackSnapshot.statePatches = inversePatches;
1543
- }
1544
- }
1545
- // In case the Snap uses a localized manifest, we need to get the
1546
- // proposed name from the localized manifest.
1547
- const { proposedName } = (0, snaps_utils_1.getLocalizedSnapManifest)(manifest.result, 'en', localizedFiles);
1548
- this.messagingSystem.call('SubjectMetadataController:addSubjectMetadata', {
1549
- subjectType: permission_controller_1.SubjectType.Snap,
1550
- name: proposedName,
1551
- origin: snap.id,
1552
- version,
1553
- svgIcon: svgIcon?.toString() ?? null,
1554
- });
1555
- return { ...snap, sourceCode };
1556
- }, _SnapController_validateSnapPermissions = function _SnapController_validateSnapPermissions(processedPermissions) {
1557
- const permissionKeys = Object.keys(processedPermissions);
1558
- const handlerPermissions = Array.from(new Set(Object.values(snaps_rpc_methods_1.handlerEndowments)));
1559
- (0, utils_1.assert)(permissionKeys.some((key) => handlerPermissions.includes(key)), `A snap must request at least one of the following permissions: ${handlerPermissions
1560
- .filter((handler) => handler !== null)
1561
- .join(', ')}.`);
1562
- const excludedPermissionErrors = permissionKeys.reduce((errors, permission) => {
1563
- if ((0, utils_1.hasProperty)(__classPrivateFieldGet(this, _SnapController_excludedPermissions, "f"), permission)) {
1564
- errors.push(__classPrivateFieldGet(this, _SnapController_excludedPermissions, "f")[permission]);
1565
- }
1566
- return errors;
1567
- }, []);
1568
- (0, utils_1.assert)(excludedPermissionErrors.length === 0, `One or more permissions are not allowed:\n${excludedPermissionErrors.join('\n')}`);
1569
- }, _SnapController_getExecutionTimeout = function _SnapController_getExecutionTimeout(permission) {
1570
- return (0, snaps_rpc_methods_1.getMaxRequestTimeCaveat)(permission) ?? this.maxRequestTime;
1571
- }, _SnapController_getRpcRequestHandler = function _SnapController_getRpcRequestHandler(snapId) {
1572
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1573
- const existingHandler = runtime.rpcHandler;
1574
- if (existingHandler) {
1575
- return existingHandler;
1576
- }
1577
- const requestQueue = new RequestQueue_1.RequestQueue(5);
1578
- // We need to set up this promise map to map snapIds to their respective startPromises,
1579
- // because otherwise we would lose context on the correct startPromise.
1580
- const startPromises = new Map();
1581
- const rpcHandler = async ({ origin, handler: handlerType, request, timeout, }) => {
1582
- if (this.state.snaps[snapId].enabled === false) {
1583
- throw new Error(`Snap "${snapId}" is disabled.`);
1584
- }
1585
- if (this.state.snaps[snapId].status === snaps_utils_1.SnapStatus.Installing) {
1586
- throw new Error(`Snap "${snapId}" is currently being installed. Please try again later.`);
1587
- }
1588
- if (!this.isRunning(snapId)) {
1589
- let localStartPromise = startPromises.get(snapId);
1590
- if (!localStartPromise) {
1591
- localStartPromise = this.startSnap(snapId);
1592
- startPromises.set(snapId, localStartPromise);
1593
- }
1594
- else if (requestQueue.get(origin) >= requestQueue.maxQueueSize) {
1595
- throw new Error('Exceeds maximum number of requests waiting to be resolved, please try again.');
1762
+ /**
1763
+ * Assert that the returned result of a Snap RPC call is the expected shape.
1764
+ *
1765
+ * @param snapId - The snap ID.
1766
+ * @param handlerType - The handler type of the RPC Request.
1767
+ * @param result - The result of the RPC request.
1768
+ */
1769
+ async #assertSnapRpcRequestResult(snapId, handlerType, result) {
1770
+ switch (handlerType) {
1771
+ case snaps_utils_1.HandlerType.OnTransaction: {
1772
+ (0, utils_1.assertStruct)(result, snaps_utils_1.OnTransactionResponseStruct);
1773
+ if (result && (0, utils_1.hasProperty)(result, 'id')) {
1774
+ this.#assertInterfaceExists(snapId, result.id);
1775
+ }
1776
+ break;
1596
1777
  }
1597
- requestQueue.increment(origin);
1598
- try {
1599
- await localStartPromise;
1778
+ case snaps_utils_1.HandlerType.OnSignature: {
1779
+ (0, utils_1.assertStruct)(result, snaps_utils_1.OnSignatureResponseStruct);
1780
+ if (result && (0, utils_1.hasProperty)(result, 'id')) {
1781
+ this.#assertInterfaceExists(snapId, result.id);
1782
+ }
1783
+ break;
1600
1784
  }
1601
- finally {
1602
- requestQueue.decrement(origin);
1603
- // Only delete startPromise for a snap if its value hasn't changed
1604
- if (startPromises.get(snapId) === localStartPromise) {
1605
- startPromises.delete(snapId);
1785
+ case snaps_utils_1.HandlerType.OnHomePage: {
1786
+ (0, utils_1.assertStruct)(result, snaps_utils_1.OnHomePageResponseStruct);
1787
+ if (result && (0, utils_1.hasProperty)(result, 'id')) {
1788
+ this.#assertInterfaceExists(snapId, result.id);
1606
1789
  }
1790
+ break;
1607
1791
  }
1792
+ case snaps_utils_1.HandlerType.OnNameLookup:
1793
+ (0, utils_1.assertStruct)(result, snaps_utils_1.OnNameLookupResponseStruct);
1794
+ break;
1795
+ default:
1796
+ break;
1608
1797
  }
1609
- const timer = new Timer_1.Timer(timeout);
1610
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_recordSnapRpcRequestStart).call(this, snapId, request.id, timer);
1611
- const handleRpcRequestPromise = this.messagingSystem.call('ExecutionService:handleRpcRequest', snapId, { origin, handler: handlerType, request });
1612
- // This will either get the result or reject due to the timeout.
1613
- try {
1614
- const result = await (0, utils_2.withTimeout)(handleRpcRequestPromise, timer);
1615
- if (result === utils_2.hasTimedOut) {
1616
- throw new Error(`${snapId} failed to respond to the request in time.`);
1617
- }
1618
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertSnapRpcRequestResult).call(this, snapId, handlerType, result);
1619
- const transformedResult = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_transformSnapRpcRequestResult).call(this, snapId, handlerType, result);
1620
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_recordSnapRpcRequestFinish).call(this, snapId, request.id);
1621
- return transformedResult;
1798
+ }
1799
+ #recordSnapRpcRequestStart(snapId, requestId, timer) {
1800
+ const runtime = this.#getRuntimeExpect(snapId);
1801
+ runtime.pendingInboundRequests.push({ requestId, timer });
1802
+ runtime.lastRequest = null;
1803
+ }
1804
+ #recordSnapRpcRequestFinish(snapId, requestId) {
1805
+ const runtime = this.#getRuntimeExpect(snapId);
1806
+ runtime.pendingInboundRequests = runtime.pendingInboundRequests.filter((request) => request.requestId !== requestId);
1807
+ if (runtime.pendingInboundRequests.length === 0) {
1808
+ runtime.lastRequest = Date.now();
1622
1809
  }
1623
- catch (error) {
1624
- // We flag the RPC request as finished early since termination may affect pending requests
1625
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_recordSnapRpcRequestFinish).call(this, snapId, request.id);
1626
- const [jsonRpcError, handled] = (0, snaps_utils_1.unwrapError)(error);
1627
- if (!handled) {
1628
- await this.stopSnap(snapId, snaps_utils_1.SnapStatusEvents.Crash);
1629
- }
1630
- throw jsonRpcError;
1810
+ }
1811
+ /**
1812
+ * Retrieves the rollback snapshot of a snap.
1813
+ *
1814
+ * @param snapId - The snap id.
1815
+ * @returns A `RollbackSnapshot` or `undefined` if one doesn't exist.
1816
+ */
1817
+ #getRollbackSnapshot(snapId) {
1818
+ return this.#rollbackSnapshots.get(snapId);
1819
+ }
1820
+ /**
1821
+ * Creates a `RollbackSnapshot` that is used to help ensure
1822
+ * atomicity in multiple snap updates.
1823
+ *
1824
+ * @param snapId - The snap id.
1825
+ * @throws {@link Error}. If the snap exists before creation or if creation fails.
1826
+ * @returns A `RollbackSnapshot`.
1827
+ */
1828
+ #createRollbackSnapshot(snapId) {
1829
+ (0, utils_1.assert)(this.#rollbackSnapshots.get(snapId) === undefined, new Error(`Snap "${snapId}" rollback snapshot already exists.`));
1830
+ this.#rollbackSnapshots.set(snapId, {
1831
+ statePatches: [],
1832
+ permissions: {},
1833
+ newVersion: '',
1834
+ });
1835
+ const newRollbackSnapshot = this.#rollbackSnapshots.get(snapId);
1836
+ (0, utils_1.assert)(newRollbackSnapshot !== undefined, new Error(`Snapshot creation failed for ${snapId}.`));
1837
+ return newRollbackSnapshot;
1838
+ }
1839
+ /**
1840
+ * Rolls back a snap to its previous state, permissions
1841
+ * and source code based on the `RollbackSnapshot` that
1842
+ * is captured during the update process. After rolling back,
1843
+ * the function also emits an event indicating that the
1844
+ * snap has been rolled back and it clears the snapshot
1845
+ * for that snap.
1846
+ *
1847
+ * @param snapId - The snap id.
1848
+ * @throws {@link Error}. If a snapshot does not exist.
1849
+ */
1850
+ async #rollbackSnap(snapId) {
1851
+ const rollbackSnapshot = this.#getRollbackSnapshot(snapId);
1852
+ if (!rollbackSnapshot) {
1853
+ throw new Error('A snapshot does not exist for this snap.');
1631
1854
  }
1632
- };
1633
- runtime.rpcHandler = rpcHandler;
1634
- return rpcHandler;
1635
- }, _SnapController_createInterface =
1636
- /**
1637
- * Create a dynamic interface in the SnapInterfaceController.
1638
- *
1639
- * @param snapId - The snap ID.
1640
- * @param content - The initial interface content.
1641
- * @returns An identifier that can be used to identify the interface.
1642
- */
1643
- async function _SnapController_createInterface(snapId, content) {
1644
- return this.messagingSystem.call('SnapInterfaceController:createInterface', snapId, content);
1645
- }, _SnapController_assertInterfaceExists = function _SnapController_assertInterfaceExists(snapId, id) {
1646
- // This will throw if the interface isn't accessible, but we assert nevertheless.
1647
- (0, utils_1.assert)(this.messagingSystem.call('SnapInterfaceController:getInterface', snapId, id));
1648
- }, _SnapController_transformSnapRpcRequestResult =
1649
- /**
1650
- * Transform a RPC request result if necessary.
1651
- *
1652
- * @param snapId - The snap ID of the snap that produced the result.
1653
- * @param handlerType - The handler type that produced the result.
1654
- * @param result - The result.
1655
- * @returns The transformed result if applicable, otherwise the original result.
1656
- */
1657
- async function _SnapController_transformSnapRpcRequestResult(snapId, handlerType, result) {
1658
- switch (handlerType) {
1659
- case snaps_utils_1.HandlerType.OnTransaction:
1660
- case snaps_utils_1.HandlerType.OnSignature:
1661
- case snaps_utils_1.HandlerType.OnHomePage: {
1662
- // Since this type has been asserted earlier we can cast
1663
- const castResult = result;
1664
- // If a handler returns static content, we turn it into a dynamic UI
1665
- if (castResult && (0, utils_1.hasProperty)(castResult, 'content')) {
1666
- const { content, ...rest } = castResult;
1667
- const id = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_createInterface).call(this, snapId, content);
1668
- return { ...rest, id };
1669
- }
1670
- return result;
1855
+ await this.stopSnap(snapId, snaps_utils_1.SnapStatusEvents.Stop);
1856
+ // Always set to stopped even if it wasn't running initially
1857
+ if (this.get(snapId)?.status !== snaps_utils_1.SnapStatus.Stopped) {
1858
+ this.#transition(snapId, snaps_utils_1.SnapStatusEvents.Stop);
1671
1859
  }
1672
- default:
1673
- return result;
1674
- }
1675
- }, _SnapController_assertSnapRpcRequestResult =
1676
- /**
1677
- * Assert that the returned result of a Snap RPC call is the expected shape.
1678
- *
1679
- * @param snapId - The snap ID.
1680
- * @param handlerType - The handler type of the RPC Request.
1681
- * @param result - The result of the RPC request.
1682
- */
1683
- async function _SnapController_assertSnapRpcRequestResult(snapId, handlerType, result) {
1684
- switch (handlerType) {
1685
- case snaps_utils_1.HandlerType.OnTransaction: {
1686
- (0, utils_1.assertStruct)(result, snaps_utils_1.OnTransactionResponseStruct);
1687
- if (result && (0, utils_1.hasProperty)(result, 'id')) {
1688
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertInterfaceExists).call(this, snapId, result.id);
1689
- }
1690
- break;
1860
+ const { statePatches, permissions } = rollbackSnapshot;
1861
+ if (statePatches?.length) {
1862
+ this.applyPatches(statePatches);
1691
1863
  }
1692
- case snaps_utils_1.HandlerType.OnSignature: {
1693
- (0, utils_1.assertStruct)(result, snaps_utils_1.OnSignatureResponseStruct);
1694
- if (result && (0, utils_1.hasProperty)(result, 'id')) {
1695
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertInterfaceExists).call(this, snapId, result.id);
1696
- }
1697
- break;
1864
+ // Reset snap status, as we may have been in another state when we stored state patches
1865
+ // But now we are 100% in a stopped state
1866
+ if (this.get(snapId)?.status !== snaps_utils_1.SnapStatus.Stopped) {
1867
+ this.update((state) => {
1868
+ state.snaps[snapId].status = snaps_utils_1.SnapStatus.Stopped;
1869
+ });
1698
1870
  }
1699
- case snaps_utils_1.HandlerType.OnHomePage: {
1700
- (0, utils_1.assertStruct)(result, snaps_utils_1.OnHomePageResponseStruct);
1701
- if (result && (0, utils_1.hasProperty)(result, 'id')) {
1702
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertInterfaceExists).call(this, snapId, result.id);
1703
- }
1704
- break;
1705
- }
1706
- case snaps_utils_1.HandlerType.OnNameLookup:
1707
- (0, utils_1.assertStruct)(result, snaps_utils_1.OnNameLookupResponseStruct);
1708
- break;
1709
- default:
1710
- break;
1711
- }
1712
- }, _SnapController_recordSnapRpcRequestStart = function _SnapController_recordSnapRpcRequestStart(snapId, requestId, timer) {
1713
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1714
- runtime.pendingInboundRequests.push({ requestId, timer });
1715
- runtime.lastRequest = null;
1716
- }, _SnapController_recordSnapRpcRequestFinish = function _SnapController_recordSnapRpcRequestFinish(snapId, requestId) {
1717
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1718
- runtime.pendingInboundRequests = runtime.pendingInboundRequests.filter((request) => request.requestId !== requestId);
1719
- if (runtime.pendingInboundRequests.length === 0) {
1720
- runtime.lastRequest = Date.now();
1721
- }
1722
- }, _SnapController_getRollbackSnapshot = function _SnapController_getRollbackSnapshot(snapId) {
1723
- return __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").get(snapId);
1724
- }, _SnapController_createRollbackSnapshot = function _SnapController_createRollbackSnapshot(snapId) {
1725
- (0, utils_1.assert)(__classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").get(snapId) === undefined, new Error(`Snap "${snapId}" rollback snapshot already exists.`));
1726
- __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").set(snapId, {
1727
- statePatches: [],
1728
- permissions: {},
1729
- newVersion: '',
1730
- });
1731
- const newRollbackSnapshot = __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").get(snapId);
1732
- (0, utils_1.assert)(newRollbackSnapshot !== undefined, new Error(`Snapshot creation failed for ${snapId}.`));
1733
- return newRollbackSnapshot;
1734
- }, _SnapController_rollbackSnap =
1735
- /**
1736
- * Rolls back a snap to its previous state, permissions
1737
- * and source code based on the `RollbackSnapshot` that
1738
- * is captured during the update process. After rolling back,
1739
- * the function also emits an event indicating that the
1740
- * snap has been rolled back and it clears the snapshot
1741
- * for that snap.
1742
- *
1743
- * @param snapId - The snap id.
1744
- * @throws {@link Error}. If a snapshot does not exist.
1745
- */
1746
- async function _SnapController_rollbackSnap(snapId) {
1747
- const rollbackSnapshot = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRollbackSnapshot).call(this, snapId);
1748
- if (!rollbackSnapshot) {
1749
- throw new Error('A snapshot does not exist for this snap.');
1750
- }
1751
- await this.stopSnap(snapId, snaps_utils_1.SnapStatusEvents.Stop);
1752
- // Always set to stopped even if it wasn't running initially
1753
- if (this.get(snapId)?.status !== snaps_utils_1.SnapStatus.Stopped) {
1754
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_transition).call(this, snapId, snaps_utils_1.SnapStatusEvents.Stop);
1755
- }
1756
- const { statePatches, permissions } = rollbackSnapshot;
1757
- if (statePatches?.length) {
1758
- this.applyPatches(statePatches);
1759
- }
1760
- // Reset snap status, as we may have been in another state when we stored state patches
1761
- // But now we are 100% in a stopped state
1762
- if (this.get(snapId)?.status !== snaps_utils_1.SnapStatus.Stopped) {
1763
- this.update((state) => {
1764
- state.snaps[snapId].status = snaps_utils_1.SnapStatus.Stopped;
1871
+ this.#updatePermissions({
1872
+ snapId,
1873
+ unusedPermissions: permissions.granted,
1874
+ newPermissions: permissions.revoked,
1875
+ requestData: permissions.requestData,
1765
1876
  });
1877
+ const truncatedSnap = this.getTruncatedExpect(snapId);
1878
+ this.messagingSystem.publish('SnapController:snapRolledback', truncatedSnap, rollbackSnapshot.newVersion);
1879
+ this.#rollbackSnapshots.delete(snapId);
1766
1880
  }
1767
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updatePermissions).call(this, {
1768
- snapId,
1769
- unusedPermissions: permissions.granted,
1770
- newPermissions: permissions.revoked,
1771
- requestData: permissions.requestData,
1772
- });
1773
- const truncatedSnap = this.getTruncatedExpect(snapId);
1774
- this.messagingSystem.publish('SnapController:snapRolledback', truncatedSnap, rollbackSnapshot.newVersion);
1775
- __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").delete(snapId);
1776
- }, _SnapController_rollbackSnaps =
1777
- /**
1778
- * Iterates through an array of snap ids
1779
- * and calls `rollbackSnap` on them.
1780
- *
1781
- * @param snapIds - An array of snap ids.
1782
- */
1783
- async function _SnapController_rollbackSnaps(snapIds) {
1784
- for (const snapId of snapIds) {
1785
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_rollbackSnap).call(this, snapId);
1786
- }
1787
- }, _SnapController_getRuntime = function _SnapController_getRuntime(snapId) {
1788
- return __classPrivateFieldGet(this, _SnapController_snapsRuntimeData, "f").get(snapId);
1789
- }, _SnapController_getRuntimeExpect = function _SnapController_getRuntimeExpect(snapId) {
1790
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntime).call(this, snapId);
1791
- (0, utils_1.assert)(runtime !== undefined, new Error(`Snap "${snapId}" runtime data not found`));
1792
- return runtime;
1793
- }, _SnapController_setupRuntime = function _SnapController_setupRuntime(snapId) {
1794
- if (__classPrivateFieldGet(this, _SnapController_snapsRuntimeData, "f").has(snapId)) {
1795
- return;
1796
- }
1797
- const snap = this.get(snapId);
1798
- const interpreter = (0, fsm_1.interpret)(__classPrivateFieldGet(this, _SnapController_statusMachine, "f"));
1799
- interpreter.start({
1800
- context: { snapId },
1801
- value: snap?.status ??
1802
- __classPrivateFieldGet(this, _SnapController_statusMachine, "f").config.initial,
1803
- });
1804
- (0, fsm_2.forceStrict)(interpreter);
1805
- __classPrivateFieldGet(this, _SnapController_snapsRuntimeData, "f").set(snapId, {
1806
- lastRequest: null,
1807
- rpcHandler: null,
1808
- installPromise: null,
1809
- encryptionKey: null,
1810
- encryptionSalt: null,
1811
- activeReferences: 0,
1812
- pendingInboundRequests: [],
1813
- pendingOutboundRequests: 0,
1814
- interpreter,
1815
- stopping: false,
1816
- });
1817
- }, _SnapController_calculatePermissionsChange = function _SnapController_calculatePermissionsChange(snapId, desiredPermissionsSet) {
1818
- const oldPermissions = this.messagingSystem.call('PermissionController:getPermissions', snapId) ?? {};
1819
- const newPermissions = (0, utils_2.permissionsDiff)(desiredPermissionsSet, oldPermissions);
1820
- // TODO(ritave): The assumption that these are unused only holds so long as we do not
1821
- // permit dynamic permission requests.
1822
- const unusedPermissions = (0, utils_2.permissionsDiff)(oldPermissions, desiredPermissionsSet);
1823
- // It's a Set Intersection of oldPermissions and desiredPermissionsSet
1824
- // oldPermissions ∖ (oldPermissions ∖ desiredPermissionsSet) ⟺ oldPermissions ∩ desiredPermissionsSet
1825
- const approvedPermissions = (0, utils_2.permissionsDiff)(oldPermissions, unusedPermissions);
1826
- return { newPermissions, unusedPermissions, approvedPermissions };
1827
- }, _SnapController_isSubjectConnectedToSnap = function _SnapController_isSubjectConnectedToSnap(snapId, origin) {
1828
- const subjectPermissions = this.messagingSystem.call('PermissionController:getPermissions', origin);
1829
- const existingCaveat = subjectPermissions?.[snaps_rpc_methods_1.WALLET_SNAP_PERMISSION_KEY]?.caveats?.find((caveat) => caveat.type === snaps_utils_1.SnapCaveatType.SnapIds);
1830
- return Boolean(existingCaveat?.value?.[snapId]);
1831
- }, _SnapController_calculateConnectionsChange = function _SnapController_calculateConnectionsChange(snapId, oldConnectionsSet, desiredConnectionsSet) {
1832
- // Filter out any origins that have been revoked since last install/update.
1833
- // That way they will be represented as new.
1834
- const filteredOldConnections = Object.keys(oldConnectionsSet)
1835
- .filter((origin) => __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_isSubjectConnectedToSnap).call(this, snapId, origin))
1836
- .reduce((accumulator, origin) => {
1837
- accumulator[origin] = oldConnectionsSet[origin];
1838
- return accumulator;
1839
- }, {});
1840
- const newConnections = (0, utils_2.setDiff)(desiredConnectionsSet, filteredOldConnections);
1841
- const unusedConnections = (0, utils_2.setDiff)(filteredOldConnections, desiredConnectionsSet);
1842
- // It's a Set Intersection of oldConnections and desiredConnectionsSet
1843
- // oldConnections ∖ (oldConnections ∖ desiredConnectionsSet) ⟺ oldConnections ∩ desiredConnectionsSet
1844
- const approvedConnections = (0, utils_2.setDiff)(filteredOldConnections, unusedConnections);
1845
- return { newConnections, unusedConnections, approvedConnections };
1846
- }, _SnapController_updatePermissions = function _SnapController_updatePermissions({ snapId, unusedPermissions = {}, newPermissions = {}, requestData, }) {
1847
- const unusedPermissionsKeys = Object.keys(unusedPermissions);
1848
- if ((0, utils_1.isNonEmptyArray)(unusedPermissionsKeys)) {
1849
- this.messagingSystem.call('PermissionController:revokePermissions', {
1850
- [snapId]: unusedPermissionsKeys,
1851
- });
1881
+ /**
1882
+ * Iterates through an array of snap ids
1883
+ * and calls `rollbackSnap` on them.
1884
+ *
1885
+ * @param snapIds - An array of snap ids.
1886
+ */
1887
+ async #rollbackSnaps(snapIds) {
1888
+ for (const snapId of snapIds) {
1889
+ await this.#rollbackSnap(snapId);
1890
+ }
1852
1891
  }
1853
- if ((0, utils_1.isNonEmptyArray)(Object.keys(newPermissions))) {
1854
- this.messagingSystem.call('PermissionController:grantPermissions', {
1855
- approvedPermissions: newPermissions,
1856
- subject: { origin: snapId },
1857
- requestData,
1892
+ #getRuntime(snapId) {
1893
+ return this.#snapsRuntimeData.get(snapId);
1894
+ }
1895
+ #getRuntimeExpect(snapId) {
1896
+ const runtime = this.#getRuntime(snapId);
1897
+ (0, utils_1.assert)(runtime !== undefined, new Error(`Snap "${snapId}" runtime data not found`));
1898
+ return runtime;
1899
+ }
1900
+ #setupRuntime(snapId) {
1901
+ if (this.#snapsRuntimeData.has(snapId)) {
1902
+ return;
1903
+ }
1904
+ const snap = this.get(snapId);
1905
+ const interpreter = (0, fsm_1.interpret)(this.#statusMachine);
1906
+ interpreter.start({
1907
+ context: { snapId },
1908
+ value: snap?.status ??
1909
+ this.#statusMachine.config.initial,
1910
+ });
1911
+ (0, fsm_2.forceStrict)(interpreter);
1912
+ this.#snapsRuntimeData.set(snapId, {
1913
+ lastRequest: null,
1914
+ rpcHandler: null,
1915
+ installPromise: null,
1916
+ encryptionKey: null,
1917
+ encryptionSalt: null,
1918
+ activeReferences: 0,
1919
+ pendingInboundRequests: [],
1920
+ pendingOutboundRequests: 0,
1921
+ interpreter,
1922
+ stopping: false,
1858
1923
  });
1859
1924
  }
1860
- }, _SnapController_isValidUpdate = function _SnapController_isValidUpdate(snapId, newVersionRange) {
1861
- const existingSnap = this.getExpect(snapId);
1862
- if ((0, utils_1.satisfiesVersionRange)(existingSnap.version, newVersionRange)) {
1863
- return false;
1925
+ #calculatePermissionsChange(snapId, desiredPermissionsSet) {
1926
+ const oldPermissions = this.messagingSystem.call('PermissionController:getPermissions', snapId) ?? {};
1927
+ const newPermissions = (0, utils_2.permissionsDiff)(desiredPermissionsSet, oldPermissions);
1928
+ // TODO(ritave): The assumption that these are unused only holds so long as we do not
1929
+ // permit dynamic permission requests.
1930
+ const unusedPermissions = (0, utils_2.permissionsDiff)(oldPermissions, desiredPermissionsSet);
1931
+ // It's a Set Intersection of oldPermissions and desiredPermissionsSet
1932
+ // oldPermissions ∖ (oldPermissions ∖ desiredPermissionsSet) ⟺ oldPermissions ∩ desiredPermissionsSet
1933
+ const approvedPermissions = (0, utils_2.permissionsDiff)(oldPermissions, unusedPermissions);
1934
+ return { newPermissions, unusedPermissions, approvedPermissions };
1935
+ }
1936
+ #isSubjectConnectedToSnap(snapId, origin) {
1937
+ const subjectPermissions = this.messagingSystem.call('PermissionController:getPermissions', origin);
1938
+ const existingCaveat = subjectPermissions?.[snaps_rpc_methods_1.WALLET_SNAP_PERMISSION_KEY]?.caveats?.find((caveat) => caveat.type === snaps_utils_1.SnapCaveatType.SnapIds);
1939
+ return Boolean(existingCaveat?.value?.[snapId]);
1940
+ }
1941
+ #calculateConnectionsChange(snapId, oldConnectionsSet, desiredConnectionsSet) {
1942
+ // Filter out any origins that have been revoked since last install/update.
1943
+ // That way they will be represented as new.
1944
+ const filteredOldConnections = Object.keys(oldConnectionsSet)
1945
+ .filter((origin) => this.#isSubjectConnectedToSnap(snapId, origin))
1946
+ .reduce((accumulator, origin) => {
1947
+ accumulator[origin] = oldConnectionsSet[origin];
1948
+ return accumulator;
1949
+ }, {});
1950
+ const newConnections = (0, utils_2.setDiff)(desiredConnectionsSet, filteredOldConnections);
1951
+ const unusedConnections = (0, utils_2.setDiff)(filteredOldConnections, desiredConnectionsSet);
1952
+ // It's a Set Intersection of oldConnections and desiredConnectionsSet
1953
+ // oldConnections ∖ (oldConnections ∖ desiredConnectionsSet) ⟺ oldConnections ∩ desiredConnectionsSet
1954
+ const approvedConnections = (0, utils_2.setDiff)(filteredOldConnections, unusedConnections);
1955
+ return { newConnections, unusedConnections, approvedConnections };
1864
1956
  }
1865
- if ((0, utils_1.gtRange)(existingSnap.version, newVersionRange)) {
1866
- return false;
1957
+ /**
1958
+ * Updates the permissions for a snap following an install, update or rollback.
1959
+ *
1960
+ * Grants newly requested permissions and revokes unused/revoked permissions.
1961
+ *
1962
+ * @param args - An options bag.
1963
+ * @param args.snapId - The snap ID.
1964
+ * @param args.newPermissions - New permissions to be granted.
1965
+ * @param args.unusedPermissions - Unused permissions to be revoked.
1966
+ * @param args.requestData - Optional request data from an approval.
1967
+ */
1968
+ #updatePermissions({ snapId, unusedPermissions = {}, newPermissions = {}, requestData, }) {
1969
+ const unusedPermissionsKeys = Object.keys(unusedPermissions);
1970
+ if ((0, utils_1.isNonEmptyArray)(unusedPermissionsKeys)) {
1971
+ this.messagingSystem.call('PermissionController:revokePermissions', {
1972
+ [snapId]: unusedPermissionsKeys,
1973
+ });
1974
+ }
1975
+ if ((0, utils_1.isNonEmptyArray)(Object.keys(newPermissions))) {
1976
+ this.messagingSystem.call('PermissionController:grantPermissions', {
1977
+ approvedPermissions: newPermissions,
1978
+ subject: { origin: snapId },
1979
+ requestData,
1980
+ });
1981
+ }
1867
1982
  }
1868
- return true;
1869
- }, _SnapController_callLifecycleHook =
1870
- /**
1871
- * Call a lifecycle hook on a snap, if the snap has the
1872
- * `endowment:lifecycle-hooks` permission. If the snap does not have the
1873
- * permission, nothing happens.
1874
- *
1875
- * @param origin - The origin.
1876
- * @param snapId - The snap ID.
1877
- * @param handler - The lifecycle hook to call. This should be one of the
1878
- * supported lifecycle hooks.
1879
- * @private
1880
- */
1881
- async function _SnapController_callLifecycleHook(origin, snapId, handler) {
1882
- const permissionName = snaps_rpc_methods_1.handlerEndowments[handler];
1883
- (0, utils_1.assert)(permissionName, 'Lifecycle hook must have an endowment.');
1884
- const hasPermission = this.messagingSystem.call('PermissionController:hasPermission', snapId, permissionName);
1885
- if (!hasPermission) {
1886
- return;
1887
- }
1888
- await this.handleRequest({
1889
- snapId,
1890
- handler,
1891
- origin,
1892
- request: {
1893
- jsonrpc: '2.0',
1894
- method: handler,
1895
- },
1896
- });
1897
- };
1983
+ /**
1984
+ * Checks if a snap will pass version validation checks
1985
+ * with the new version range that is requested. The first
1986
+ * check that is done is to check if the existing snap version
1987
+ * falls inside the requested range. If it does, we want to return
1988
+ * false because we do not care to create a rollback snapshot in
1989
+ * that scenario. The second check is to ensure that the current
1990
+ * snap version is not greater than all possible versions in
1991
+ * the requested version range. If it is, then we also want
1992
+ * to return false in that scenario.
1993
+ *
1994
+ * @param snapId - The snap id.
1995
+ * @param newVersionRange - The new version range being requested.
1996
+ * @returns `true` if validation checks pass and `false` if they do not.
1997
+ */
1998
+ #isValidUpdate(snapId, newVersionRange) {
1999
+ const existingSnap = this.getExpect(snapId);
2000
+ if ((0, utils_1.satisfiesVersionRange)(existingSnap.version, newVersionRange)) {
2001
+ return false;
2002
+ }
2003
+ if ((0, utils_1.gtRange)(existingSnap.version, newVersionRange)) {
2004
+ return false;
2005
+ }
2006
+ return true;
2007
+ }
2008
+ /**
2009
+ * Call a lifecycle hook on a snap, if the snap has the
2010
+ * `endowment:lifecycle-hooks` permission. If the snap does not have the
2011
+ * permission, nothing happens.
2012
+ *
2013
+ * @param origin - The origin.
2014
+ * @param snapId - The snap ID.
2015
+ * @param handler - The lifecycle hook to call. This should be one of the
2016
+ * supported lifecycle hooks.
2017
+ * @private
2018
+ */
2019
+ async #callLifecycleHook(origin, snapId, handler) {
2020
+ const permissionName = snaps_rpc_methods_1.handlerEndowments[handler];
2021
+ (0, utils_1.assert)(permissionName, 'Lifecycle hook must have an endowment.');
2022
+ const hasPermission = this.messagingSystem.call('PermissionController:hasPermission', snapId, permissionName);
2023
+ if (!hasPermission) {
2024
+ return;
2025
+ }
2026
+ await this.handleRequest({
2027
+ snapId,
2028
+ handler,
2029
+ origin,
2030
+ request: {
2031
+ jsonrpc: '2.0',
2032
+ method: handler,
2033
+ },
2034
+ });
2035
+ }
2036
+ }
2037
+ exports.SnapController = SnapController;
1898
2038
  //# sourceMappingURL=SnapController.cjs.map