@metamask/snaps-controllers 12.3.1 → 13.0.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 (88) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/dist/cronjob/CronjobController.cjs +250 -276
  3. package/dist/cronjob/CronjobController.cjs.map +1 -1
  4. package/dist/cronjob/CronjobController.d.cts +61 -78
  5. package/dist/cronjob/CronjobController.d.cts.map +1 -1
  6. package/dist/cronjob/CronjobController.d.mts +61 -78
  7. package/dist/cronjob/CronjobController.d.mts.map +1 -1
  8. package/dist/cronjob/CronjobController.mjs +251 -277
  9. package/dist/cronjob/CronjobController.mjs.map +1 -1
  10. package/dist/cronjob/utils.cjs +79 -0
  11. package/dist/cronjob/utils.cjs.map +1 -0
  12. package/dist/cronjob/utils.d.cts +25 -0
  13. package/dist/cronjob/utils.d.cts.map +1 -0
  14. package/dist/cronjob/utils.d.mts +25 -0
  15. package/dist/cronjob/utils.d.mts.map +1 -0
  16. package/dist/cronjob/utils.mjs +75 -0
  17. package/dist/cronjob/utils.mjs.map +1 -0
  18. package/dist/insights/SnapInsightsController.cjs +199 -149
  19. package/dist/insights/SnapInsightsController.cjs.map +1 -1
  20. package/dist/insights/SnapInsightsController.mjs +198 -148
  21. package/dist/insights/SnapInsightsController.mjs.map +1 -1
  22. package/dist/interface/SnapInterfaceController.cjs +160 -101
  23. package/dist/interface/SnapInterfaceController.cjs.map +1 -1
  24. package/dist/interface/SnapInterfaceController.mjs +160 -101
  25. package/dist/interface/SnapInterfaceController.mjs.map +1 -1
  26. package/dist/multichain/MultichainRouter.cjs +117 -114
  27. package/dist/multichain/MultichainRouter.cjs.map +1 -1
  28. package/dist/multichain/MultichainRouter.mjs +117 -114
  29. package/dist/multichain/MultichainRouter.mjs.map +1 -1
  30. package/dist/services/AbstractExecutionService.cjs +131 -139
  31. package/dist/services/AbstractExecutionService.cjs.map +1 -1
  32. package/dist/services/AbstractExecutionService.mjs +131 -139
  33. package/dist/services/AbstractExecutionService.mjs.map +1 -1
  34. package/dist/services/ProxyPostMessageStream.cjs +19 -26
  35. package/dist/services/ProxyPostMessageStream.cjs.map +1 -1
  36. package/dist/services/ProxyPostMessageStream.mjs +19 -26
  37. package/dist/services/ProxyPostMessageStream.mjs.map +1 -1
  38. package/dist/services/iframe/IframeExecutionService.cjs +1 -0
  39. package/dist/services/iframe/IframeExecutionService.cjs.map +1 -1
  40. package/dist/services/iframe/IframeExecutionService.mjs +1 -0
  41. package/dist/services/iframe/IframeExecutionService.mjs.map +1 -1
  42. package/dist/services/offscreen/OffscreenExecutionService.cjs +3 -16
  43. package/dist/services/offscreen/OffscreenExecutionService.cjs.map +1 -1
  44. package/dist/services/offscreen/OffscreenExecutionService.mjs +3 -16
  45. package/dist/services/offscreen/OffscreenExecutionService.mjs.map +1 -1
  46. package/dist/services/proxy/ProxyExecutionService.cjs +4 -17
  47. package/dist/services/proxy/ProxyExecutionService.cjs.map +1 -1
  48. package/dist/services/proxy/ProxyExecutionService.mjs +4 -17
  49. package/dist/services/proxy/ProxyExecutionService.mjs.map +1 -1
  50. package/dist/services/webview/WebViewExecutionService.cjs +6 -19
  51. package/dist/services/webview/WebViewExecutionService.cjs.map +1 -1
  52. package/dist/services/webview/WebViewExecutionService.mjs +6 -19
  53. package/dist/services/webview/WebViewExecutionService.mjs.map +1 -1
  54. package/dist/services/webview/WebViewMessageStream.cjs +13 -26
  55. package/dist/services/webview/WebViewMessageStream.cjs.map +1 -1
  56. package/dist/services/webview/WebViewMessageStream.mjs +13 -26
  57. package/dist/services/webview/WebViewMessageStream.mjs.map +1 -1
  58. package/dist/snaps/SnapController.cjs +1370 -1161
  59. package/dist/snaps/SnapController.cjs.map +1 -1
  60. package/dist/snaps/SnapController.d.cts +4 -4
  61. package/dist/snaps/SnapController.d.cts.map +1 -1
  62. package/dist/snaps/SnapController.d.mts +4 -4
  63. package/dist/snaps/SnapController.d.mts.map +1 -1
  64. package/dist/snaps/SnapController.mjs +1370 -1161
  65. package/dist/snaps/SnapController.mjs.map +1 -1
  66. package/dist/snaps/Timer.cjs +4 -0
  67. package/dist/snaps/Timer.cjs.map +1 -1
  68. package/dist/snaps/Timer.mjs +4 -0
  69. package/dist/snaps/Timer.mjs.map +1 -1
  70. package/dist/snaps/location/http.cjs +20 -4
  71. package/dist/snaps/location/http.cjs.map +1 -1
  72. package/dist/snaps/location/http.mjs +20 -4
  73. package/dist/snaps/location/http.mjs.map +1 -1
  74. package/dist/snaps/location/local.cjs +4 -17
  75. package/dist/snaps/location/local.cjs.map +1 -1
  76. package/dist/snaps/location/local.mjs +4 -17
  77. package/dist/snaps/location/local.mjs.map +1 -1
  78. package/dist/snaps/location/npm.cjs +28 -48
  79. package/dist/snaps/location/npm.cjs.map +1 -1
  80. package/dist/snaps/location/npm.d.cts.map +1 -1
  81. package/dist/snaps/location/npm.d.mts.map +1 -1
  82. package/dist/snaps/location/npm.mjs +28 -48
  83. package/dist/snaps/location/npm.mjs.map +1 -1
  84. package/dist/snaps/registry/json.cjs +173 -166
  85. package/dist/snaps/registry/json.cjs.map +1 -1
  86. package/dist/snaps/registry/json.mjs +172 -165
  87. package/dist/snaps/registry/json.mjs.map +1 -1
  88. package/package.json +9 -9
@@ -1,22 +1,10 @@
1
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
- if (kind === "m") throw new TypeError("Private method is not writable");
3
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
- 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");
5
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
- };
7
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
- 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");
10
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
- };
12
- var _SnapController_instances, _SnapController_closeAllConnections, _SnapController_dynamicPermissions, _SnapController_environmentEndowmentPermissions, _SnapController_excludedPermissions, _SnapController_featureFlags, _SnapController_fetchFunction, _SnapController_idleTimeCheckInterval, _SnapController_maxIdleTime, _SnapController_encryptor, _SnapController_getMnemonicSeed, _SnapController_getFeatureFlags, _SnapController_clientCryptography, _SnapController_detectSnapLocation, _SnapController_snapsRuntimeData, _SnapController_rollbackSnapshots, _SnapController_timeoutForLastRequestStatus, _SnapController_statusMachine, _SnapController_preinstalledSnaps, _SnapController_trackEvent, _SnapController_trackSnapExport, _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_hasCachedEncryptionKey, _SnapController_getSnapEncryptionKey, _SnapController_decryptSnapState, _SnapController_encryptSnapState, _SnapController_getStateToPersist, _SnapController_persistSnapState, _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_validatePlatformVersion, _SnapController_getExecutionTimeout, _SnapController_createInterface, _SnapController_assertInterfaceExists, _SnapController_transformSnapRpcResponse, _SnapController_transformOnAssetsLookupResult, _SnapController_transformOnAssetsConversionResult, _SnapController_transformSnapRpcRequest, _SnapController_assertSnapRpcResponse, _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_getPermissionsToGrant, _SnapController_updatePermissions, _SnapController_isValidUpdate, _SnapController_callLifecycleHook, _SnapController_handleLock;
13
1
  import { BaseController } from "@metamask/base-controller";
14
2
  import { SubjectType } from "@metamask/permission-controller";
15
3
  import { rpcErrors } from "@metamask/rpc-errors";
16
4
  import { WALLET_SNAP_PERMISSION_KEY, getMaxRequestTimeCaveat, handlerEndowments, SnapEndowments, getKeyringCaveatOrigins, getRpcCaveatOrigins, processSnapPermissions, getEncryptionEntropy, getChainIdsCaveat } from "@metamask/snaps-rpc-methods";
17
5
  import { AuxiliaryFileEncoding, getErrorMessage, OnAssetsLookupResponseStruct } from "@metamask/snaps-sdk";
18
6
  import { logWarning, getPlatformVersion, assertIsSnapManifest, assertIsValidSnapId, DEFAULT_ENDOWMENTS, DEFAULT_REQUESTED_SNAP_VERSION, encodeAuxiliaryFile, HandlerType, isOriginAllowed, logError, normalizeRelative, OnTransactionResponseStruct, OnSignatureResponseStruct, resolveVersionRange, SnapCaveatType, SnapStatus, SnapStatusEvents, unwrapError, OnHomePageResponseStruct, getValidatedLocalizationFiles, VirtualFile, NpmSnapFileNames, OnNameLookupResponseStruct, getLocalizedSnapManifest, MAX_FILE_SIZE, OnSettingsPageResponseStruct, isValidUrl, OnAssetHistoricalPriceResponseStruct, OnAssetsConversionResponseStruct } from "@metamask/snaps-utils";
19
- import { hexToNumber, assert, assertIsJsonRpcRequest, assertStruct, Duration, gtRange, gtVersion, hasProperty, inMilliseconds, isNonEmptyArray, isValidSemVerRange, satisfiesVersionRange, timeSince } from "@metamask/utils";
7
+ import { hexToNumber, assert, assertIsJsonRpcRequest, assertStruct, Duration, gtRange, gtVersion, hasProperty, inMilliseconds, isNonEmptyArray, isValidSemVerRange, satisfiesVersionRange, timeSince, createDeferredPromise } from "@metamask/utils";
20
8
  import { createMachine, interpret } from "@xstate/fsm";
21
9
  import { Mutex } from "async-mutex";
22
10
  import { nanoid } from "nanoid";
@@ -68,6 +56,29 @@ function truncateSnap(snap) {
68
56
  * - Start: Initializes the snap in its SES realm with the authorized permissions.
69
57
  */
70
58
  export class SnapController extends BaseController {
59
+ #closeAllConnections;
60
+ #dynamicPermissions;
61
+ #environmentEndowmentPermissions;
62
+ #excludedPermissions;
63
+ #featureFlags;
64
+ #fetchFunction;
65
+ #idleTimeCheckInterval;
66
+ #maxIdleTime;
67
+ // This property cannot be hash private yet because of tests.
68
+ // eslint-disable-next-line no-restricted-syntax
69
+ maxRequestTime;
70
+ #encryptor;
71
+ #getMnemonicSeed;
72
+ #getFeatureFlags;
73
+ #clientCryptography;
74
+ #detectSnapLocation;
75
+ #snapsRuntimeData;
76
+ #rollbackSnapshots;
77
+ #timeoutForLastRequestStatus;
78
+ #statusMachine;
79
+ #preinstalledSnaps;
80
+ #trackEvent;
81
+ #trackSnapExport;
71
82
  constructor({ closeAllConnections, messenger, state, dynamicPermissions = ['eth_accounts'], environmentEndowmentPermissions = [], excludedPermissions = {}, idleTimeCheckInterval = inMilliseconds(5, Duration.Second), maxIdleTime = inMilliseconds(30, Duration.Second), maxRequestTime = inMilliseconds(60, Duration.Second), fetchFunction = globalThis.fetch.bind(undefined), featureFlags = {}, detectSnapLocation: detectSnapLocationFunction = detectSnapLocation, preinstalledSnaps = null, encryptor, getMnemonicSeed, getFeatureFlags = () => ({}), clientCryptography, trackEvent, }) {
72
83
  super({
73
84
  messenger,
@@ -107,103 +118,53 @@ export class SnapController extends BaseController {
107
118
  ...state,
108
119
  },
109
120
  });
110
- _SnapController_instances.add(this);
111
- _SnapController_closeAllConnections.set(this, void 0);
112
- _SnapController_dynamicPermissions.set(this, void 0);
113
- _SnapController_environmentEndowmentPermissions.set(this, void 0);
114
- _SnapController_excludedPermissions.set(this, void 0);
115
- _SnapController_featureFlags.set(this, void 0);
116
- _SnapController_fetchFunction.set(this, void 0);
117
- _SnapController_idleTimeCheckInterval.set(this, void 0);
118
- _SnapController_maxIdleTime.set(this, void 0);
119
- _SnapController_encryptor.set(this, void 0);
120
- _SnapController_getMnemonicSeed.set(this, void 0);
121
- _SnapController_getFeatureFlags.set(this, void 0);
122
- _SnapController_clientCryptography.set(this, void 0);
123
- _SnapController_detectSnapLocation.set(this, void 0);
124
- _SnapController_snapsRuntimeData.set(this, void 0);
125
- _SnapController_rollbackSnapshots.set(this, void 0);
126
- _SnapController_timeoutForLastRequestStatus.set(this, void 0);
127
- _SnapController_statusMachine.set(this, void 0);
128
- _SnapController_preinstalledSnaps.set(this, void 0);
129
- _SnapController_trackEvent.set(this, void 0);
130
- _SnapController_trackSnapExport.set(this, void 0);
131
- /**
132
- * Persist the state of a Snap.
133
- *
134
- * This function is debounced per Snap, meaning that multiple calls to this
135
- * function for the same Snap will only result in one state update. It also
136
- * uses a mutex to ensure that only one state update per Snap is processed at
137
- * a time, avoiding possible race conditions.
138
- *
139
- * @param snapId - The Snap ID.
140
- * @param newSnapState - The new state of the Snap.
141
- * @param encrypted - A flag to indicate whether to use encrypted storage or
142
- * not.
143
- */
144
- _SnapController_persistSnapState.set(this, debouncePersistState((snapId, newSnapState, encrypted) => {
145
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
146
- runtime.stateMutex
147
- .runExclusive(async () => {
148
- const newState = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getStateToPersist).call(this, snapId, newSnapState, encrypted);
149
- if (encrypted) {
150
- return this.update((state) => {
151
- state.snapStates[snapId] = newState;
152
- });
153
- }
154
- return this.update((state) => {
155
- state.unencryptedSnapStates[snapId] = newState;
156
- });
157
- })
158
- .catch(logError);
159
- }, STATE_DEBOUNCE_TIMEOUT));
160
- __classPrivateFieldSet(this, _SnapController_closeAllConnections, closeAllConnections, "f");
161
- __classPrivateFieldSet(this, _SnapController_dynamicPermissions, dynamicPermissions, "f");
162
- __classPrivateFieldSet(this, _SnapController_environmentEndowmentPermissions, environmentEndowmentPermissions, "f");
163
- __classPrivateFieldSet(this, _SnapController_excludedPermissions, excludedPermissions, "f");
164
- __classPrivateFieldSet(this, _SnapController_featureFlags, featureFlags, "f");
165
- __classPrivateFieldSet(this, _SnapController_fetchFunction, fetchFunction, "f");
166
- __classPrivateFieldSet(this, _SnapController_idleTimeCheckInterval, idleTimeCheckInterval, "f");
167
- __classPrivateFieldSet(this, _SnapController_maxIdleTime, maxIdleTime, "f");
121
+ this.#closeAllConnections = closeAllConnections;
122
+ this.#dynamicPermissions = dynamicPermissions;
123
+ this.#environmentEndowmentPermissions = environmentEndowmentPermissions;
124
+ this.#excludedPermissions = excludedPermissions;
125
+ this.#featureFlags = featureFlags;
126
+ this.#fetchFunction = fetchFunction;
127
+ this.#idleTimeCheckInterval = idleTimeCheckInterval;
128
+ this.#maxIdleTime = maxIdleTime;
168
129
  this.maxRequestTime = maxRequestTime;
169
- __classPrivateFieldSet(this, _SnapController_detectSnapLocation, detectSnapLocationFunction, "f");
170
- __classPrivateFieldSet(this, _SnapController_encryptor, encryptor, "f");
171
- __classPrivateFieldSet(this, _SnapController_getMnemonicSeed, getMnemonicSeed, "f");
172
- __classPrivateFieldSet(this, _SnapController_getFeatureFlags, getFeatureFlags, "f");
173
- __classPrivateFieldSet(this, _SnapController_clientCryptography, clientCryptography, "f");
174
- __classPrivateFieldSet(this, _SnapController_preinstalledSnaps, preinstalledSnaps, "f");
130
+ this.#detectSnapLocation = detectSnapLocationFunction;
131
+ this.#encryptor = encryptor;
132
+ this.#getMnemonicSeed = getMnemonicSeed;
133
+ this.#getFeatureFlags = getFeatureFlags;
134
+ this.#clientCryptography = clientCryptography;
135
+ this.#preinstalledSnaps = preinstalledSnaps;
175
136
  this._onUnhandledSnapError = this._onUnhandledSnapError.bind(this);
176
137
  this._onOutboundRequest = this._onOutboundRequest.bind(this);
177
138
  this._onOutboundResponse = this._onOutboundResponse.bind(this);
178
- __classPrivateFieldSet(this, _SnapController_rollbackSnapshots, new Map(), "f");
179
- __classPrivateFieldSet(this, _SnapController_snapsRuntimeData, new Map(), "f");
180
- __classPrivateFieldSet(this, _SnapController_trackEvent, trackEvent, "f");
181
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_pollForLastRequestStatus).call(this);
139
+ this.#rollbackSnapshots = new Map();
140
+ this.#snapsRuntimeData = new Map();
141
+ this.#trackEvent = trackEvent;
142
+ this.#pollForLastRequestStatus();
182
143
  /* eslint-disable @typescript-eslint/unbound-method */
183
144
  this.messagingSystem.subscribe('ExecutionService:unhandledError', this._onUnhandledSnapError);
184
145
  this.messagingSystem.subscribe('ExecutionService:outboundRequest', this._onOutboundRequest);
185
146
  this.messagingSystem.subscribe('ExecutionService:outboundResponse', this._onOutboundResponse);
186
147
  /* eslint-enable @typescript-eslint/unbound-method */
187
148
  this.messagingSystem.subscribe('SnapController:snapInstalled', ({ id }, origin) => {
188
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_callLifecycleHook).call(this, origin, id, HandlerType.OnInstall).catch((error) => {
149
+ this.#callLifecycleHook(origin, id, HandlerType.OnInstall).catch((error) => {
189
150
  logError(`Error when calling \`onInstall\` lifecycle hook for snap "${id}": ${getErrorMessage(error)}`);
190
151
  });
191
152
  });
192
153
  this.messagingSystem.subscribe('SnapController:snapUpdated', ({ id }, _oldVersion, origin) => {
193
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_callLifecycleHook).call(this, origin, id, HandlerType.OnUpdate).catch((error) => {
154
+ this.#callLifecycleHook(origin, id, HandlerType.OnUpdate).catch((error) => {
194
155
  logError(`Error when calling \`onUpdate\` lifecycle hook for snap "${id}": ${getErrorMessage(error)}`);
195
156
  });
196
157
  });
197
- this.messagingSystem.subscribe('KeyringController:lock', __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_handleLock).bind(this));
198
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_initializeStateMachine).call(this);
199
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_registerMessageHandlers).call(this);
200
- Object.values(this.state?.snaps ?? {}).forEach((snap) => __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_setupRuntime).call(this, snap.id));
201
- if (__classPrivateFieldGet(this, _SnapController_preinstalledSnaps, "f")) {
202
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_handlePreinstalledSnaps).call(this, __classPrivateFieldGet(this, _SnapController_preinstalledSnaps, "f"));
203
- }
204
- __classPrivateFieldSet(this, _SnapController_trackSnapExport, throttleTracking((snapId, handler, success, origin) => {
158
+ this.messagingSystem.subscribe('KeyringController:lock', this.#handleLock.bind(this));
159
+ this.#initializeStateMachine();
160
+ this.#registerMessageHandlers();
161
+ Object.values(this.state?.snaps ?? {}).forEach((snap) => this.#setupRuntime(snap.id));
162
+ if (this.#preinstalledSnaps) {
163
+ this.#handlePreinstalledSnaps(this.#preinstalledSnaps);
164
+ }
165
+ this.#trackSnapExport = throttleTracking((snapId, handler, success, origin) => {
205
166
  const snapMetadata = this.messagingSystem.call('SnapsRegistry:getMetadata', snapId);
206
- __classPrivateFieldGet(this, _SnapController_trackEvent, "f").call(this, {
167
+ this.#trackEvent({
207
168
  event: 'Snap Export Used',
208
169
  category: 'Snaps',
209
170
  properties: {
@@ -216,7 +177,171 @@ export class SnapController extends BaseController {
216
177
  origin,
217
178
  },
218
179
  });
219
- }), "f");
180
+ });
181
+ }
182
+ /**
183
+ * We track status of a Snap using a finite-state-machine.
184
+ * It keeps track of whether the snap is started / stopped / etc.
185
+ *
186
+ * @see {@link SnapController.transition} for interacting with the machine.
187
+ */
188
+ // We initialize the machine in the instance because the status is currently tightly coupled
189
+ // with the SnapController - the guard checks for enabled status inside the SnapController state.
190
+ // In the future, side-effects could be added to the machine during transitions.
191
+ #initializeStateMachine() {
192
+ const disableGuard = ({ snapId }) => {
193
+ return this.getExpect(snapId).enabled;
194
+ };
195
+ const statusConfig = {
196
+ initial: SnapStatus.Installing,
197
+ states: {
198
+ [SnapStatus.Installing]: {
199
+ on: {
200
+ [SnapStatusEvents.Start]: {
201
+ target: SnapStatus.Running,
202
+ cond: disableGuard,
203
+ },
204
+ },
205
+ },
206
+ [SnapStatus.Updating]: {
207
+ on: {
208
+ [SnapStatusEvents.Start]: {
209
+ target: SnapStatus.Running,
210
+ cond: disableGuard,
211
+ },
212
+ [SnapStatusEvents.Stop]: SnapStatus.Stopped,
213
+ },
214
+ },
215
+ [SnapStatus.Running]: {
216
+ on: {
217
+ [SnapStatusEvents.Stop]: SnapStatus.Stopped,
218
+ [SnapStatusEvents.Crash]: SnapStatus.Crashed,
219
+ },
220
+ },
221
+ [SnapStatus.Stopped]: {
222
+ on: {
223
+ [SnapStatusEvents.Start]: {
224
+ target: SnapStatus.Running,
225
+ cond: disableGuard,
226
+ },
227
+ [SnapStatusEvents.Update]: SnapStatus.Updating,
228
+ },
229
+ },
230
+ [SnapStatus.Crashed]: {
231
+ on: {
232
+ [SnapStatusEvents.Start]: {
233
+ target: SnapStatus.Running,
234
+ cond: disableGuard,
235
+ },
236
+ [SnapStatusEvents.Update]: SnapStatus.Updating,
237
+ },
238
+ },
239
+ },
240
+ };
241
+ this.#statusMachine = createMachine(statusConfig);
242
+ validateMachine(this.#statusMachine);
243
+ }
244
+ /**
245
+ * Constructor helper for registering the controller's messaging system
246
+ * actions.
247
+ */
248
+ #registerMessageHandlers() {
249
+ this.messagingSystem.registerActionHandler(`${controllerName}:clearSnapState`, (...args) => this.clearSnapState(...args));
250
+ this.messagingSystem.registerActionHandler(`${controllerName}:get`, (...args) => this.get(...args));
251
+ this.messagingSystem.registerActionHandler(`${controllerName}:getSnapState`, async (...args) => this.getSnapState(...args));
252
+ this.messagingSystem.registerActionHandler(`${controllerName}:handleRequest`, async (...args) => this.handleRequest(...args));
253
+ this.messagingSystem.registerActionHandler(`${controllerName}:has`, (...args) => this.has(...args));
254
+ this.messagingSystem.registerActionHandler(`${controllerName}:updateBlockedSnaps`, async () => this.updateBlockedSnaps());
255
+ this.messagingSystem.registerActionHandler(`${controllerName}:updateSnapState`, async (...args) => this.updateSnapState(...args));
256
+ this.messagingSystem.registerActionHandler(`${controllerName}:enable`, (...args) => this.enableSnap(...args));
257
+ this.messagingSystem.registerActionHandler(`${controllerName}:disable`, async (...args) => this.disableSnap(...args));
258
+ this.messagingSystem.registerActionHandler(`${controllerName}:remove`, async (...args) => this.removeSnap(...args));
259
+ this.messagingSystem.registerActionHandler(`${controllerName}:getPermitted`, (...args) => this.getPermittedSnaps(...args));
260
+ this.messagingSystem.registerActionHandler(`${controllerName}:install`, async (...args) => this.installSnaps(...args));
261
+ this.messagingSystem.registerActionHandler(`${controllerName}:getAll`, (...args) => this.getAllSnaps(...args));
262
+ this.messagingSystem.registerActionHandler(`${controllerName}:getRunnableSnaps`, (...args) => this.getRunnableSnaps(...args));
263
+ this.messagingSystem.registerActionHandler(`${controllerName}:incrementActiveReferences`, (...args) => this.incrementActiveReferences(...args));
264
+ this.messagingSystem.registerActionHandler(`${controllerName}:decrementActiveReferences`, (...args) => this.decrementActiveReferences(...args));
265
+ this.messagingSystem.registerActionHandler(`${controllerName}:disconnectOrigin`, (...args) => this.removeSnapFromSubject(...args));
266
+ this.messagingSystem.registerActionHandler(`${controllerName}:revokeDynamicPermissions`, (...args) => this.revokeDynamicSnapPermissions(...args));
267
+ this.messagingSystem.registerActionHandler(`${controllerName}:getFile`, async (...args) => this.getSnapFile(...args));
268
+ this.messagingSystem.registerActionHandler(`${controllerName}:stopAllSnaps`, async (...args) => this.stopAllSnaps(...args));
269
+ this.messagingSystem.registerActionHandler(`${controllerName}:isMinimumPlatformVersion`, (...args) => this.isMinimumPlatformVersion(...args));
270
+ }
271
+ #handlePreinstalledSnaps(preinstalledSnaps) {
272
+ for (const { snapId, manifest, files, removable, hidden, hideSnapBranding, } of preinstalledSnaps) {
273
+ const existingSnap = this.get(snapId);
274
+ const isAlreadyInstalled = existingSnap !== undefined;
275
+ const isUpdate = isAlreadyInstalled && gtVersion(manifest.version, existingSnap.version);
276
+ // Disallow downgrades and overwriting non preinstalled snaps
277
+ if (isAlreadyInstalled &&
278
+ (!isUpdate || existingSnap.preinstalled !== true)) {
279
+ continue;
280
+ }
281
+ const manifestFile = new VirtualFile({
282
+ path: NpmSnapFileNames.Manifest,
283
+ value: JSON.stringify(manifest),
284
+ result: manifest,
285
+ });
286
+ const virtualFiles = files.map(({ path, value }) => new VirtualFile({ value, path }));
287
+ const { filePath, iconPath } = manifest.source.location.npm;
288
+ const sourceCode = virtualFiles.find((file) => file.path === filePath);
289
+ const svgIcon = iconPath
290
+ ? virtualFiles.find((file) => file.path === iconPath)
291
+ : undefined;
292
+ assert(sourceCode, 'Source code not provided for preinstalled snap.');
293
+ assert(!iconPath || (iconPath && svgIcon), 'Icon not provided for preinstalled snap.');
294
+ assert(manifest.source.files === undefined, 'Auxiliary files are not currently supported for preinstalled snaps.');
295
+ const localizationFiles = manifest.source.locales?.map((path) => virtualFiles.find((file) => file.path === path)) ?? [];
296
+ const validatedLocalizationFiles = getValidatedLocalizationFiles(localizationFiles.filter(Boolean));
297
+ assert(localizationFiles.length === validatedLocalizationFiles.length, 'Missing localization files for preinstalled snap.');
298
+ const filesObject = {
299
+ manifest: manifestFile,
300
+ sourceCode,
301
+ svgIcon,
302
+ auxiliaryFiles: [],
303
+ localizationFiles: validatedLocalizationFiles,
304
+ };
305
+ // Add snap to the SnapController state
306
+ this.#set({
307
+ id: snapId,
308
+ origin: METAMASK_ORIGIN,
309
+ files: filesObject,
310
+ removable,
311
+ hidden,
312
+ hideSnapBranding,
313
+ preinstalled: true,
314
+ });
315
+ // Setup permissions
316
+ const processedPermissions = processSnapPermissions(manifest.initialPermissions);
317
+ this.#validateSnapPermissions(processedPermissions);
318
+ const { newPermissions, unusedPermissions } = this.#calculatePermissionsChange(snapId, processedPermissions);
319
+ this.#updatePermissions({ snapId, newPermissions, unusedPermissions });
320
+ if (manifest.initialConnections) {
321
+ this.#handleInitialConnections(snapId, existingSnap?.initialConnections ?? null, manifest.initialConnections);
322
+ }
323
+ // Set status
324
+ this.update((state) => {
325
+ state.snaps[snapId].status = SnapStatus.Stopped;
326
+ });
327
+ this.#setupRuntime(snapId);
328
+ // Emit events
329
+ if (isUpdate) {
330
+ this.messagingSystem.publish('SnapController:snapUpdated', this.getTruncatedExpect(snapId), existingSnap.version, METAMASK_ORIGIN, true);
331
+ }
332
+ else {
333
+ this.messagingSystem.publish('SnapController:snapInstalled', this.getTruncatedExpect(snapId), METAMASK_ORIGIN, true);
334
+ }
335
+ }
336
+ }
337
+ #pollForLastRequestStatus() {
338
+ this.#timeoutForLastRequestStatus = setTimeout(() => {
339
+ this.#stopSnapsLastRequestPastMax().catch((error) => {
340
+ // TODO: Decide how to handle errors.
341
+ logError(error);
342
+ });
343
+ this.#pollForLastRequestStatus();
344
+ }, this.#idleTimeCheckInterval);
220
345
  }
221
346
  /**
222
347
  * Checks all installed snaps against the block list and
@@ -224,7 +349,7 @@ export class SnapController extends BaseController {
224
349
  * for more information.
225
350
  */
226
351
  async updateBlockedSnaps() {
227
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertCanUsePlatform).call(this);
352
+ this.#assertCanUsePlatform();
228
353
  await this.messagingSystem.call('SnapsRegistry:update');
229
354
  const blockedSnaps = await this.messagingSystem.call('SnapsRegistry:get', Object.values(this.state.snaps).reduce((blockListArg, snap) => {
230
355
  blockListArg[snap.id] = {
@@ -235,11 +360,92 @@ export class SnapController extends BaseController {
235
360
  }, {}));
236
361
  await Promise.all(Object.entries(blockedSnaps).map(async ([snapId, { status, reason }]) => {
237
362
  if (status === SnapsRegistryStatus.Blocked) {
238
- return __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_blockSnap).call(this, snapId, reason);
363
+ return this.#blockSnap(snapId, reason);
239
364
  }
240
- return __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_unblockSnap).call(this, snapId);
365
+ return this.#unblockSnap(snapId);
241
366
  }));
242
367
  }
368
+ /**
369
+ * Blocks an installed snap and prevents it from being started again. Emits
370
+ * {@link SnapBlocked}. Does nothing if the snap is not installed.
371
+ *
372
+ * @param snapId - The snap to block.
373
+ * @param blockedSnapInfo - Information detailing why the snap is blocked.
374
+ */
375
+ async #blockSnap(snapId, blockedSnapInfo) {
376
+ if (!this.has(snapId)) {
377
+ return;
378
+ }
379
+ try {
380
+ this.update((state) => {
381
+ state.snaps[snapId].blocked = true;
382
+ state.snaps[snapId].blockInformation = blockedSnapInfo;
383
+ });
384
+ await this.disableSnap(snapId);
385
+ }
386
+ catch (error) {
387
+ logError(`Encountered error when stopping blocked snap "${snapId}".`, error);
388
+ }
389
+ this.messagingSystem.publish(`${controllerName}:snapBlocked`, snapId, blockedSnapInfo);
390
+ }
391
+ /**
392
+ * Unblocks a snap so that it can be enabled and started again. Emits
393
+ * {@link SnapUnblocked}. Does nothing if the snap is not installed or already
394
+ * unblocked.
395
+ *
396
+ * @param snapId - The id of the snap to unblock.
397
+ */
398
+ #unblockSnap(snapId) {
399
+ if (!this.has(snapId) || !this.state.snaps[snapId].blocked) {
400
+ return;
401
+ }
402
+ this.update((state) => {
403
+ state.snaps[snapId].blocked = false;
404
+ delete state.snaps[snapId].blockInformation;
405
+ });
406
+ this.messagingSystem.publish(`${controllerName}:snapUnblocked`, snapId);
407
+ }
408
+ async #assertIsInstallAllowed(snapId, { platformVersion, ...snapInfo }) {
409
+ const results = await this.messagingSystem.call('SnapsRegistry:get', {
410
+ [snapId]: snapInfo,
411
+ });
412
+ const result = results[snapId];
413
+ if (result.status === SnapsRegistryStatus.Blocked) {
414
+ throw new Error(`Cannot install version "${snapInfo.version}" of snap "${snapId}": The version is blocked. ${result.reason?.explanation ?? ''}`);
415
+ }
416
+ const isAllowlistingRequired = Object.keys(snapInfo.permissions).some((permission) => !ALLOWED_PERMISSIONS.includes(permission));
417
+ if (this.#featureFlags.requireAllowlist &&
418
+ isAllowlistingRequired &&
419
+ result.status !== SnapsRegistryStatus.Verified) {
420
+ throw new Error(`Cannot install version "${snapInfo.version}" of snap "${snapId}": ${result.status === SnapsRegistryStatus.Unavailable
421
+ ? 'The registry is temporarily unavailable.'
422
+ : 'The snap is not on the allowlist.'}`);
423
+ }
424
+ this.#validatePlatformVersion(snapId, platformVersion);
425
+ }
426
+ /**
427
+ * Asserts whether new Snaps are allowed to be installed.
428
+ */
429
+ #assertCanInstallSnaps() {
430
+ assert(this.#featureFlags.disableSnapInstallation !== true, 'Installing Snaps is currently disabled in this version of MetaMask.');
431
+ }
432
+ /**
433
+ * Asserts whether the Snaps platform is allowed to run.
434
+ */
435
+ #assertCanUsePlatform() {
436
+ const flags = this.#getFeatureFlags();
437
+ assert(flags.disableSnaps !== true, 'The Snaps platform requires basic functionality to be used. Enable basic functionality in the settings to use the Snaps platform.');
438
+ }
439
+ async #stopSnapsLastRequestPastMax() {
440
+ const entries = [...this.#snapsRuntimeData.entries()];
441
+ return Promise.all(entries
442
+ .filter(([_snapId, runtime]) => runtime.activeReferences === 0 &&
443
+ runtime.pendingInboundRequests.length === 0 &&
444
+ runtime.lastRequest &&
445
+ this.#maxIdleTime &&
446
+ timeSince(runtime.lastRequest) > this.#maxIdleTime)
447
+ .map(async ([snapId]) => this.stopSnap(snapId, SnapStatusEvents.Stop)));
448
+ }
243
449
  _onUnhandledSnapError(snapId, error) {
244
450
  // Log the error that caused the crash
245
451
  // so it gets raised to the developer for debugging purposes.
@@ -250,7 +456,7 @@ export class SnapController extends BaseController {
250
456
  });
251
457
  }
252
458
  _onOutboundRequest(snapId) {
253
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
459
+ const runtime = this.#getRuntimeExpect(snapId);
254
460
  // Ideally we would only pause the pending request that is making the outbound request
255
461
  // but right now we don't have a way to know which request initiated the outbound request
256
462
  runtime.pendingInboundRequests
@@ -259,7 +465,7 @@ export class SnapController extends BaseController {
259
465
  runtime.pendingOutboundRequests += 1;
260
466
  }
261
467
  _onOutboundResponse(snapId) {
262
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
468
+ const runtime = this.#getRuntimeExpect(snapId);
263
469
  runtime.pendingOutboundRequests -= 1;
264
470
  if (runtime.pendingOutboundRequests === 0) {
265
471
  runtime.pendingInboundRequests
@@ -267,6 +473,25 @@ export class SnapController extends BaseController {
267
473
  .forEach((pendingRequest) => pendingRequest.timer.resume());
268
474
  }
269
475
  }
476
+ /**
477
+ * Transitions between states using `snapStatusStateMachineConfig` as the template to figure out
478
+ * the next state. This transition function uses a very minimal subset of XState conventions:
479
+ * - supports initial state
480
+ * - .on supports raw event target string
481
+ * - .on supports {target, cond} object
482
+ * - the arguments for `cond` is the `SerializedSnap` instead of Xstate convention of `(event,
483
+ * context) => boolean`
484
+ *
485
+ * @param snapId - The id of the snap to transition.
486
+ * @param event - The event enum to use to transition.
487
+ */
488
+ #transition(snapId, event) {
489
+ const { interpreter } = this.#getRuntimeExpect(snapId);
490
+ interpreter.send(event);
491
+ this.update((state) => {
492
+ state.snaps[snapId].status = interpreter.state.value;
493
+ });
494
+ }
270
495
  /**
271
496
  * Starts the given snap. Throws an error if no such snap exists
272
497
  * or if it is already running.
@@ -274,12 +499,12 @@ export class SnapController extends BaseController {
274
499
  * @param snapId - The id of the Snap to start.
275
500
  */
276
501
  async startSnap(snapId) {
277
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertCanUsePlatform).call(this);
502
+ this.#assertCanUsePlatform();
278
503
  const snap = this.state.snaps[snapId];
279
504
  if (!snap.enabled) {
280
505
  throw new Error(`Snap "${snapId}" is disabled.`);
281
506
  }
282
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_startSnap).call(this, {
507
+ await this.#startSnap({
283
508
  snapId,
284
509
  sourceCode: snap.sourceCode,
285
510
  });
@@ -327,21 +552,23 @@ export class SnapController extends BaseController {
327
552
  * stopped.
328
553
  */
329
554
  async stopSnap(snapId, statusEvent = SnapStatusEvents.Stop) {
330
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntime).call(this, snapId);
555
+ const runtime = this.#getRuntime(snapId);
331
556
  if (!runtime) {
332
557
  throw new Error(`The snap "${snapId}" is not running.`);
333
558
  }
334
- // No-op if the Snap is already stopping.
335
- if (runtime.stopping) {
559
+ // If we are already stopping, wait for that to finish.
560
+ if (runtime.stopPromise) {
561
+ await runtime.stopPromise;
336
562
  return;
337
563
  }
338
564
  // Flag that the Snap is actively stopping, this prevents other calls to stopSnap
339
565
  // while we are handling termination of the Snap
340
- runtime.stopping = true;
566
+ const { promise, resolve } = createDeferredPromise();
567
+ runtime.stopPromise = promise;
341
568
  try {
342
569
  if (this.isRunning(snapId)) {
343
- __classPrivateFieldGet(this, _SnapController_closeAllConnections, "f")?.call(this, snapId);
344
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_terminateSnap).call(this, snapId);
570
+ this.#closeAllConnections?.(snapId);
571
+ await this.#terminateSnap(snapId);
345
572
  }
346
573
  }
347
574
  finally {
@@ -349,10 +576,11 @@ export class SnapController extends BaseController {
349
576
  runtime.lastRequest = null;
350
577
  runtime.pendingInboundRequests = [];
351
578
  runtime.pendingOutboundRequests = 0;
352
- runtime.stopping = false;
579
+ runtime.stopPromise = null;
353
580
  if (this.isRunning(snapId)) {
354
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_transition).call(this, snapId, statusEvent);
581
+ this.#transition(snapId, statusEvent);
355
582
  }
583
+ resolve();
356
584
  }
357
585
  }
358
586
  /**
@@ -367,6 +595,24 @@ export class SnapController extends BaseController {
367
595
  const promises = snaps.map(async (snap) => this.stopSnap(snap.id, statusEvent));
368
596
  await Promise.allSettled(promises);
369
597
  }
598
+ /**
599
+ * Terminates the specified snap and emits the `snapTerminated` event.
600
+ *
601
+ * @param snapId - The snap to terminate.
602
+ */
603
+ async #terminateSnap(snapId) {
604
+ await this.messagingSystem.call('ExecutionService:terminateSnap', snapId);
605
+ // Hack to give up execution for a bit to let gracefully terminating Snaps return.
606
+ await new Promise((resolve) => setTimeout(resolve, 1));
607
+ const runtime = this.#getRuntimeExpect(snapId);
608
+ // Unresponsive requests may still be timed, time them out.
609
+ runtime.pendingInboundRequests
610
+ .filter((pendingRequest) => pendingRequest.timer.status !== 'finished')
611
+ .forEach((pendingRequest) => pendingRequest.timer.finish());
612
+ // Hack to give up execution for a bit to let timed out requests return.
613
+ await new Promise((resolve) => setTimeout(resolve, 1));
614
+ this.messagingSystem.publish('SnapController:snapTerminated', this.getTruncatedExpect(snapId));
615
+ }
370
616
  /**
371
617
  * Returns whether the given snap is running.
372
618
  * Throws an error if the snap doesn't exist.
@@ -434,6 +680,157 @@ export class SnapController extends BaseController {
434
680
  getTruncatedExpect(snapId) {
435
681
  return truncateSnap(this.getExpect(snapId));
436
682
  }
683
+ /**
684
+ * Check if a given Snap has a cached encryption key stored in the runtime.
685
+ *
686
+ * @param snapId - The Snap ID.
687
+ * @param runtime - The Snap runtime data.
688
+ * @returns True if the Snap has a cached encryption key, otherwise false.
689
+ */
690
+ #hasCachedEncryptionKey(snapId, runtime = this.#getRuntimeExpect(snapId)) {
691
+ return runtime.encryptionKey !== null && runtime.encryptionSalt !== null;
692
+ }
693
+ /**
694
+ * Generate an encryption key to be used for state encryption for a given Snap.
695
+ *
696
+ * @param options - An options bag.
697
+ * @param options.snapId - The Snap ID.
698
+ * @param options.salt - A salt to be used for the encryption key.
699
+ * @param options.useCache - Whether to use caching or not.
700
+ * @param options.keyMetadata - Optional metadata about how to derive the encryption key.
701
+ * @returns An encryption key.
702
+ */
703
+ async #getSnapEncryptionKey({ snapId, salt: passedSalt, useCache, keyMetadata, }) {
704
+ const runtime = this.#getRuntimeExpect(snapId);
705
+ if (this.#hasCachedEncryptionKey(snapId, runtime) && useCache) {
706
+ return {
707
+ key: await this.#encryptor.importKey(runtime.encryptionKey),
708
+ salt: runtime.encryptionSalt,
709
+ };
710
+ }
711
+ const salt = passedSalt ?? this.#encryptor.generateSalt();
712
+ const seed = await this.#getMnemonicSeed();
713
+ const entropy = await getEncryptionEntropy({
714
+ snapId,
715
+ seed,
716
+ cryptographicFunctions: this.#clientCryptography,
717
+ });
718
+ const encryptionKey = await this.#encryptor.keyFromPassword(entropy, salt, true, keyMetadata);
719
+ const exportedKey = await this.#encryptor.exportKey(encryptionKey);
720
+ // Cache exported encryption key in runtime
721
+ if (useCache) {
722
+ runtime.encryptionKey = exportedKey;
723
+ runtime.encryptionSalt = salt;
724
+ }
725
+ return { key: encryptionKey, salt };
726
+ }
727
+ /**
728
+ * Decrypt the encrypted state for a given Snap.
729
+ *
730
+ * @param snapId - The Snap ID.
731
+ * @param state - The encrypted state as a string.
732
+ * @returns A valid JSON object derived from the encrypted state.
733
+ * @throws If the decryption fails or the decrypted state is not valid JSON.
734
+ */
735
+ async #decryptSnapState(snapId, state) {
736
+ try {
737
+ // We assume that the state string here is valid JSON since we control serialization.
738
+ // This lets us skip JSON validation.
739
+ const parsed = JSON.parse(state);
740
+ const { salt, keyMetadata } = parsed;
741
+ // We only cache encryption keys if they are already cached or if the encryption key is using the latest key derivation params.
742
+ const useCache = this.#hasCachedEncryptionKey(snapId) ||
743
+ this.#encryptor.isVaultUpdated(state);
744
+ const { key } = await this.#getSnapEncryptionKey({
745
+ snapId,
746
+ salt,
747
+ useCache,
748
+ // When decrypting state we expect key metadata to be present.
749
+ // If it isn't present, we assume that the Snap state we are decrypting is old enough to use the legacy encryption params.
750
+ keyMetadata: keyMetadata ?? LEGACY_ENCRYPTION_KEY_DERIVATION_OPTIONS,
751
+ });
752
+ const decryptedState = await this.#encryptor.decryptWithKey(key, parsed);
753
+ // We assume this to be valid JSON, since all RPC requests from a Snap are validated and sanitized.
754
+ return decryptedState;
755
+ }
756
+ catch {
757
+ throw rpcErrors.internal({
758
+ message: 'Failed to decrypt snap state, the state must be corrupted.',
759
+ });
760
+ }
761
+ }
762
+ /**
763
+ * Encrypt a JSON state object for a given Snap.
764
+ *
765
+ * Note: This function does not assert the validity of the object,
766
+ * please ensure only valid JSON is passed to it.
767
+ *
768
+ * @param snapId - The Snap ID.
769
+ * @param state - The state object.
770
+ * @returns A string containing the encrypted JSON object.
771
+ */
772
+ async #encryptSnapState(snapId, state) {
773
+ const { key, salt } = await this.#getSnapEncryptionKey({
774
+ snapId,
775
+ useCache: true,
776
+ });
777
+ const encryptedState = await this.#encryptor.encryptWithKey(key, state);
778
+ encryptedState.salt = salt;
779
+ return JSON.stringify(encryptedState);
780
+ }
781
+ /**
782
+ * Get the new Snap state to persist based on the given state and encryption
783
+ * flag.
784
+ *
785
+ * - If the state is null, return null.
786
+ * - If the state should be encrypted, return the encrypted state.
787
+ * - Otherwise, if the state should not be encrypted, return the JSON-
788
+ * stringified state.
789
+ *
790
+ * @param snapId - The Snap ID.
791
+ * @param state - The state to persist.
792
+ * @param encrypted - A flag to indicate whether to use encrypted storage or
793
+ * not.
794
+ * @returns The state to persist.
795
+ */
796
+ async #getStateToPersist(snapId, state, encrypted) {
797
+ if (state === null) {
798
+ return null;
799
+ }
800
+ if (encrypted) {
801
+ return await this.#encryptSnapState(snapId, state);
802
+ }
803
+ return JSON.stringify(state);
804
+ }
805
+ /**
806
+ * Persist the state of a Snap.
807
+ *
808
+ * This function is debounced per Snap, meaning that multiple calls to this
809
+ * function for the same Snap will only result in one state update. It also
810
+ * uses a mutex to ensure that only one state update per Snap is processed at
811
+ * a time, avoiding possible race conditions.
812
+ *
813
+ * @param snapId - The Snap ID.
814
+ * @param newSnapState - The new state of the Snap.
815
+ * @param encrypted - A flag to indicate whether to use encrypted storage or
816
+ * not.
817
+ */
818
+ #persistSnapState = debouncePersistState((snapId, newSnapState, encrypted) => {
819
+ const runtime = this.#getRuntimeExpect(snapId);
820
+ runtime.stateMutex
821
+ .runExclusive(async () => {
822
+ const newState = await this.#getStateToPersist(snapId, newSnapState, encrypted);
823
+ if (encrypted) {
824
+ return this.update((state) => {
825
+ state.snapStates[snapId] = newState;
826
+ });
827
+ }
828
+ return this.update((state) => {
829
+ state.unencryptedSnapStates[snapId] = newState;
830
+ });
831
+ })
832
+ .catch(logError);
833
+ }, STATE_DEBOUNCE_TIMEOUT);
437
834
  /**
438
835
  * Updates the own state of the snap with the given id.
439
836
  * This is distinct from the state MetaMask uses to manage snaps.
@@ -443,14 +840,14 @@ export class SnapController extends BaseController {
443
840
  * @param encrypted - A flag to indicate whether to use encrypted storage or not.
444
841
  */
445
842
  async updateSnapState(snapId, newSnapState, encrypted) {
446
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
843
+ const runtime = this.#getRuntimeExpect(snapId);
447
844
  if (encrypted) {
448
845
  runtime.state = newSnapState;
449
846
  }
450
847
  else {
451
848
  runtime.unencryptedState = newSnapState;
452
849
  }
453
- __classPrivateFieldGet(this, _SnapController_persistSnapState, "f").call(this, snapId, newSnapState, encrypted);
850
+ this.#persistSnapState(snapId, newSnapState, encrypted);
454
851
  }
455
852
  /**
456
853
  * Clears the state of the snap with the given id.
@@ -460,14 +857,14 @@ export class SnapController extends BaseController {
460
857
  * @param encrypted - A flag to indicate whether to use encrypted storage or not.
461
858
  */
462
859
  clearSnapState(snapId, encrypted) {
463
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
860
+ const runtime = this.#getRuntimeExpect(snapId);
464
861
  if (encrypted) {
465
862
  runtime.state = null;
466
863
  }
467
864
  else {
468
865
  runtime.unencryptedState = null;
469
866
  }
470
- __classPrivateFieldGet(this, _SnapController_persistSnapState, "f").call(this, snapId, null, encrypted);
867
+ this.#persistSnapState(snapId, null, encrypted);
471
868
  }
472
869
  /**
473
870
  * Gets the own state of the snap with the given id.
@@ -478,7 +875,7 @@ export class SnapController extends BaseController {
478
875
  * @returns The requested snap state or null if no state exists.
479
876
  */
480
877
  async getSnapState(snapId, encrypted) {
481
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
878
+ const runtime = this.#getRuntimeExpect(snapId);
482
879
  return await runtime.getStateMutex.runExclusive(async () => {
483
880
  const cachedState = encrypted ? runtime.state : runtime.unencryptedState;
484
881
  if (cachedState !== undefined) {
@@ -497,7 +894,7 @@ export class SnapController extends BaseController {
497
894
  runtime.unencryptedState = json;
498
895
  return json;
499
896
  }
500
- const decrypted = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_decryptSnapState).call(this, snapId, state);
897
+ const decrypted = await this.#decryptSnapState(snapId, state);
501
898
  // eslint-disable-next-line require-atomic-updates
502
899
  runtime.state = decrypted;
503
900
  return decrypted;
@@ -544,23 +941,23 @@ export class SnapController extends BaseController {
544
941
  */
545
942
  async clearState() {
546
943
  const snapIds = Object.keys(this.state.snaps);
547
- if (__classPrivateFieldGet(this, _SnapController_closeAllConnections, "f")) {
944
+ if (this.#closeAllConnections) {
548
945
  snapIds.forEach((snapId) => {
549
- __classPrivateFieldGet(this, _SnapController_closeAllConnections, "f")?.call(this, snapId);
946
+ this.#closeAllConnections?.(snapId);
550
947
  });
551
948
  }
552
949
  await this.messagingSystem.call('ExecutionService:terminateAllSnaps');
553
- snapIds.forEach((snapId) => __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_revokeAllSnapPermissions).call(this, snapId));
950
+ snapIds.forEach((snapId) => this.#revokeAllSnapPermissions(snapId));
554
951
  this.update((state) => {
555
952
  state.snaps = {};
556
953
  state.snapStates = {};
557
954
  state.unencryptedSnapStates = {};
558
955
  });
559
- __classPrivateFieldGet(this, _SnapController_snapsRuntimeData, "f").clear();
560
- __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").clear();
956
+ this.#snapsRuntimeData.clear();
957
+ this.#rollbackSnapshots.clear();
561
958
  // We want to remove all snaps & permissions, except for preinstalled snaps
562
- if (__classPrivateFieldGet(this, _SnapController_preinstalledSnaps, "f")) {
563
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_handlePreinstalledSnaps).call(this, __classPrivateFieldGet(this, _SnapController_preinstalledSnaps, "f"));
959
+ if (this.#preinstalledSnaps) {
960
+ this.#handlePreinstalledSnaps(this.#preinstalledSnaps);
564
961
  }
565
962
  }
566
963
  /**
@@ -594,9 +991,9 @@ export class SnapController extends BaseController {
594
991
  // it. This ensures that the snap will not be restarted or otherwise
595
992
  // affect the host environment while we are deleting it.
596
993
  await this.disableSnap(snapId);
597
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_revokeAllSnapPermissions).call(this, snapId);
598
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_removeSnapFromSubjects).call(this, snapId);
599
- __classPrivateFieldGet(this, _SnapController_snapsRuntimeData, "f").delete(snapId);
994
+ this.#revokeAllSnapPermissions(snapId);
995
+ this.#removeSnapFromSubjects(snapId);
996
+ this.#snapsRuntimeData.delete(snapId);
600
997
  this.update((state) => {
601
998
  delete state.snaps[snapId];
602
999
  delete state.snapStates[snapId];
@@ -608,6 +1005,47 @@ export class SnapController extends BaseController {
608
1005
  }
609
1006
  }));
610
1007
  }
1008
+ #handleInitialConnections(snapId, previousInitialConnections, initialConnections) {
1009
+ if (previousInitialConnections) {
1010
+ const revokedInitialConnections = setDiff(previousInitialConnections, initialConnections);
1011
+ for (const origin of Object.keys(revokedInitialConnections)) {
1012
+ this.removeSnapFromSubject(origin, snapId);
1013
+ }
1014
+ }
1015
+ for (const origin of Object.keys(initialConnections)) {
1016
+ this.#addSnapToSubject(origin, snapId);
1017
+ }
1018
+ }
1019
+ #addSnapToSubject(origin, snapId) {
1020
+ const subjectPermissions = this.messagingSystem.call('PermissionController:getPermissions', origin);
1021
+ const existingCaveat = subjectPermissions?.[WALLET_SNAP_PERMISSION_KEY]?.caveats?.find((caveat) => caveat.type === SnapCaveatType.SnapIds);
1022
+ const subjectHasSnap = Boolean(existingCaveat?.value?.[snapId]);
1023
+ // If the subject is already connected to the snap, this is a no-op.
1024
+ if (subjectHasSnap) {
1025
+ return;
1026
+ }
1027
+ // If an existing caveat exists, we add the snap to that.
1028
+ if (existingCaveat) {
1029
+ this.messagingSystem.call('PermissionController:updateCaveat', origin, WALLET_SNAP_PERMISSION_KEY, SnapCaveatType.SnapIds, { ...existingCaveat.value, [snapId]: {} });
1030
+ return;
1031
+ }
1032
+ const approvedPermissions = {
1033
+ [WALLET_SNAP_PERMISSION_KEY]: {
1034
+ caveats: [
1035
+ {
1036
+ type: SnapCaveatType.SnapIds,
1037
+ value: {
1038
+ [snapId]: {},
1039
+ },
1040
+ },
1041
+ ],
1042
+ },
1043
+ };
1044
+ this.messagingSystem.call('PermissionController:grantPermissions', {
1045
+ approvedPermissions,
1046
+ subject: { origin },
1047
+ });
1048
+ }
611
1049
  /**
612
1050
  * Removes a snap's permission (caveat) from the specified subject.
613
1051
  *
@@ -644,18 +1082,39 @@ export class SnapController extends BaseController {
644
1082
  * @throws If non-dynamic permissions are passed.
645
1083
  */
646
1084
  revokeDynamicSnapPermissions(snapId, permissionNames) {
647
- assert(permissionNames.every((permissionName) => __classPrivateFieldGet(this, _SnapController_dynamicPermissions, "f").includes(permissionName)), 'Non-dynamic permissions cannot be revoked');
1085
+ assert(permissionNames.every((permissionName) => this.#dynamicPermissions.includes(permissionName)), 'Non-dynamic permissions cannot be revoked');
648
1086
  this.messagingSystem.call('PermissionController:revokePermissions', {
649
1087
  [snapId]: permissionNames,
650
1088
  });
651
1089
  }
1090
+ /**
1091
+ * Removes a snap's permission (caveat) from all subjects.
1092
+ *
1093
+ * @param snapId - The id of the Snap.
1094
+ */
1095
+ #removeSnapFromSubjects(snapId) {
1096
+ const subjects = this.messagingSystem.call('PermissionController:getSubjectNames');
1097
+ for (const subject of subjects) {
1098
+ this.removeSnapFromSubject(subject, snapId);
1099
+ }
1100
+ }
1101
+ /**
1102
+ * Safely revokes all permissions granted to a Snap.
1103
+ *
1104
+ * @param snapId - The snap ID.
1105
+ */
1106
+ #revokeAllSnapPermissions(snapId) {
1107
+ if (this.messagingSystem.call('PermissionController:hasPermissions', snapId)) {
1108
+ this.messagingSystem.call('PermissionController:revokeAllPermissions', snapId);
1109
+ }
1110
+ }
652
1111
  /**
653
1112
  * Handles incrementing the activeReferences counter.
654
1113
  *
655
1114
  * @param snapId - The snap id of the snap that was referenced.
656
1115
  */
657
1116
  incrementActiveReferences(snapId) {
658
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1117
+ const runtime = this.#getRuntimeExpect(snapId);
659
1118
  runtime.activeReferences += 1;
660
1119
  }
661
1120
  /**
@@ -664,7 +1123,7 @@ export class SnapController extends BaseController {
664
1123
  * @param snapId - The snap id of the snap that was referenced..
665
1124
  */
666
1125
  decrementActiveReferences(snapId) {
667
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1126
+ const runtime = this.#getRuntimeExpect(snapId);
668
1127
  assert(runtime.activeReferences > 0, 'SnapController reference management is in an invalid state.');
669
1128
  runtime.activeReferences -= 1;
670
1129
  }
@@ -713,7 +1172,7 @@ export class SnapController extends BaseController {
713
1172
  * snap couldn't be installed.
714
1173
  */
715
1174
  async installSnaps(origin, requestedSnaps) {
716
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertCanUsePlatform).call(this);
1175
+ this.#assertCanUsePlatform();
717
1176
  const result = {};
718
1177
  const snapIds = Object.keys(requestedSnaps);
719
1178
  const pendingUpdates = [];
@@ -725,23 +1184,23 @@ export class SnapController extends BaseController {
725
1184
  if (error) {
726
1185
  throw rpcErrors.invalidParams(`The "version" field must be a valid SemVer version range if specified. Received: "${rawVersion}".`);
727
1186
  }
728
- const location = __classPrivateFieldGet(this, _SnapController_detectSnapLocation, "f").call(this, snapId, {
1187
+ const location = this.#detectSnapLocation(snapId, {
729
1188
  versionRange: version,
730
- fetch: __classPrivateFieldGet(this, _SnapController_fetchFunction, "f"),
731
- allowLocal: __classPrivateFieldGet(this, _SnapController_featureFlags, "f").allowLocalSnaps,
732
- resolveVersion: async (range) => __classPrivateFieldGet(this, _SnapController_featureFlags, "f").requireAllowlist
733
- ? await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_resolveAllowlistVersion).call(this, snapId, range)
1189
+ fetch: this.#fetchFunction,
1190
+ allowLocal: this.#featureFlags.allowLocalSnaps,
1191
+ resolveVersion: async (range) => this.#featureFlags.requireAllowlist
1192
+ ? await this.#resolveAllowlistVersion(snapId, range)
734
1193
  : range,
735
1194
  });
736
1195
  // Existing snaps may need to be updated, unless they should be re-installed (e.g. local snaps)
737
1196
  // Everything else is treated as an install
738
1197
  const isUpdate = this.has(snapId) && !location.shouldAlwaysReload;
739
- if (isUpdate && __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_isValidUpdate).call(this, snapId, version)) {
1198
+ if (isUpdate && this.#isValidUpdate(snapId, version)) {
740
1199
  const existingSnap = this.getExpect(snapId);
741
1200
  pendingUpdates.push({ snapId, oldVersion: existingSnap.version });
742
- let rollbackSnapshot = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRollbackSnapshot).call(this, snapId);
1201
+ let rollbackSnapshot = this.#getRollbackSnapshot(snapId);
743
1202
  if (rollbackSnapshot === undefined) {
744
- rollbackSnapshot = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_createRollbackSnapshot).call(this, snapId);
1203
+ rollbackSnapshot = this.#createRollbackSnapshot(snapId);
745
1204
  rollbackSnapshot.newVersion = version;
746
1205
  }
747
1206
  else {
@@ -756,16 +1215,16 @@ export class SnapController extends BaseController {
756
1215
  // Once we finish all installs / updates, emit events.
757
1216
  pendingInstalls.forEach((snapId) => this.messagingSystem.publish(`SnapController:snapInstalled`, this.getTruncatedExpect(snapId), origin, false));
758
1217
  pendingUpdates.forEach(({ snapId, oldVersion }) => this.messagingSystem.publish(`SnapController:snapUpdated`, this.getTruncatedExpect(snapId), oldVersion, origin, false));
759
- snapIds.forEach((snapId) => __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").delete(snapId));
1218
+ snapIds.forEach((snapId) => this.#rollbackSnapshots.delete(snapId));
760
1219
  }
761
1220
  catch (error) {
762
1221
  const installed = pendingInstalls.filter((snapId) => this.has(snapId));
763
1222
  await this.removeSnaps(installed);
764
- const snapshottedSnaps = [...__classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").keys()];
1223
+ const snapshottedSnaps = [...this.#rollbackSnapshots.keys()];
765
1224
  const snapsToRollback = pendingUpdates
766
1225
  .map(({ snapId }) => snapId)
767
1226
  .filter((snapId) => snapshottedSnaps.includes(snapId));
768
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_rollbackSnaps).call(this, snapsToRollback);
1227
+ await this.#rollbackSnaps(snapsToRollback);
769
1228
  throw error;
770
1229
  }
771
1230
  return result;
@@ -798,8 +1257,8 @@ export class SnapController extends BaseController {
798
1257
  // and we don't want to emit events prematurely.
799
1258
  false);
800
1259
  }
801
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertCanInstallSnaps).call(this);
802
- let pendingApproval = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_createApproval).call(this, {
1260
+ this.#assertCanInstallSnaps();
1261
+ let pendingApproval = this.#createApproval({
803
1262
  origin,
804
1263
  snapId,
805
1264
  type: SNAP_APPROVAL_INSTALL,
@@ -811,27 +1270,27 @@ export class SnapController extends BaseController {
811
1270
  }
812
1271
  // Existing snaps that should be re-installed should not maintain their existing permissions
813
1272
  if (existingSnap && location.shouldAlwaysReload) {
814
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_revokeAllSnapPermissions).call(this, snapId);
1273
+ this.#revokeAllSnapPermissions(snapId);
815
1274
  }
816
1275
  try {
817
- const { sourceCode } = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_add).call(this, {
1276
+ const { sourceCode } = await this.#add({
818
1277
  origin,
819
1278
  id: snapId,
820
1279
  location,
821
1280
  versionRange,
822
1281
  });
823
1282
  await this.authorize(snapId, pendingApproval);
824
- pendingApproval = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_createApproval).call(this, {
1283
+ pendingApproval = this.#createApproval({
825
1284
  origin,
826
1285
  snapId,
827
1286
  type: SNAP_APPROVAL_RESULT,
828
1287
  });
829
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_startSnap).call(this, {
1288
+ await this.#startSnap({
830
1289
  snapId,
831
1290
  sourceCode,
832
1291
  });
833
1292
  const truncated = this.getTruncatedExpect(snapId);
834
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updateApproval).call(this, pendingApproval.id, {
1293
+ this.#updateApproval(pendingApproval.id, {
835
1294
  loading: false,
836
1295
  type: SNAP_APPROVAL_INSTALL,
837
1296
  });
@@ -840,7 +1299,7 @@ export class SnapController extends BaseController {
840
1299
  catch (error) {
841
1300
  logError(`Error when adding ${snapId}.`, error);
842
1301
  const errorString = error instanceof Error ? error.message : error.toString();
843
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updateApproval).call(this, pendingApproval.id, {
1302
+ this.#updateApproval(pendingApproval.id, {
844
1303
  loading: false,
845
1304
  type: SNAP_APPROVAL_INSTALL,
846
1305
  error: errorString,
@@ -849,6 +1308,34 @@ export class SnapController extends BaseController {
849
1308
  throw error;
850
1309
  }
851
1310
  }
1311
+ #createApproval({ origin, snapId, type, }) {
1312
+ const id = nanoid();
1313
+ const promise = this.messagingSystem.call('ApprovalController:addRequest', {
1314
+ origin,
1315
+ id,
1316
+ type,
1317
+ requestData: {
1318
+ // Mirror previous installation metadata
1319
+ metadata: { id, origin: snapId, dappOrigin: origin },
1320
+ snapId,
1321
+ },
1322
+ requestState: {
1323
+ loading: true,
1324
+ },
1325
+ }, true);
1326
+ return { id, promise };
1327
+ }
1328
+ #updateApproval(id, requestState) {
1329
+ try {
1330
+ this.messagingSystem.call('ApprovalController:updateRequestState', {
1331
+ id,
1332
+ requestState,
1333
+ });
1334
+ }
1335
+ catch {
1336
+ // Do nothing
1337
+ }
1338
+ }
852
1339
  /**
853
1340
  * Updates an installed snap. The flow is similar to
854
1341
  * {@link SnapController.installSnaps}. The user will be asked if they want
@@ -869,8 +1356,8 @@ export class SnapController extends BaseController {
869
1356
  * @returns The snap metadata if updated, `null` otherwise.
870
1357
  */
871
1358
  async updateSnap(origin, snapId, location, newVersionRange = DEFAULT_REQUESTED_SNAP_VERSION, emitEvent = true) {
872
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertCanInstallSnaps).call(this);
873
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertCanUsePlatform).call(this);
1359
+ this.#assertCanInstallSnaps();
1360
+ this.#assertCanUsePlatform();
874
1361
  const snap = this.getExpect(snapId);
875
1362
  if (snap.preinstalled) {
876
1363
  throw new Error('Preinstalled Snaps cannot be manually updated.');
@@ -878,7 +1365,7 @@ export class SnapController extends BaseController {
878
1365
  if (!isValidSemVerRange(newVersionRange)) {
879
1366
  throw new Error(`Received invalid snap version range: "${newVersionRange}".`);
880
1367
  }
881
- let pendingApproval = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_createApproval).call(this, {
1368
+ let pendingApproval = this.#createApproval({
882
1369
  origin,
883
1370
  snapId,
884
1371
  type: SNAP_APPROVAL_UPDATE,
@@ -896,17 +1383,17 @@ export class SnapController extends BaseController {
896
1383
  if (!satisfiesVersionRange(newVersion, newVersionRange)) {
897
1384
  throw new Error(`Version mismatch. Manifest for "${snapId}" specifies version "${newVersion}" which doesn't satisfy requested version range "${newVersionRange}".`);
898
1385
  }
899
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertIsInstallAllowed).call(this, snapId, {
1386
+ await this.#assertIsInstallAllowed(snapId, {
900
1387
  version: newVersion,
901
1388
  checksum: manifest.source.shasum,
902
1389
  permissions: manifest.initialPermissions,
903
1390
  platformVersion: manifest.platformVersion,
904
1391
  });
905
1392
  const processedPermissions = processSnapPermissions(manifest.initialPermissions);
906
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_validateSnapPermissions).call(this, processedPermissions);
907
- const { newPermissions, unusedPermissions, approvedPermissions } = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_calculatePermissionsChange).call(this, snapId, processedPermissions);
908
- const { newConnections, unusedConnections, approvedConnections } = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_calculateConnectionsChange).call(this, snapId, oldManifest.initialConnections ?? {}, manifest.initialConnections ?? {});
909
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updateApproval).call(this, pendingApproval.id, {
1393
+ this.#validateSnapPermissions(processedPermissions);
1394
+ const { newPermissions, unusedPermissions, approvedPermissions } = this.#calculatePermissionsChange(snapId, processedPermissions);
1395
+ const { newConnections, unusedConnections, approvedConnections } = this.#calculateConnectionsChange(snapId, oldManifest.initialConnections ?? {}, manifest.initialConnections ?? {});
1396
+ this.#updateApproval(pendingApproval.id, {
910
1397
  permissions: newPermissions,
911
1398
  newVersion: manifest.version,
912
1399
  newPermissions,
@@ -918,7 +1405,7 @@ export class SnapController extends BaseController {
918
1405
  loading: false,
919
1406
  });
920
1407
  const { permissions: approvedNewPermissions, ...requestData } = (await pendingApproval.promise);
921
- pendingApproval = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_createApproval).call(this, {
1408
+ pendingApproval = this.#createApproval({
922
1409
  origin,
923
1410
  snapId,
924
1411
  type: SNAP_APPROVAL_RESULT,
@@ -926,23 +1413,23 @@ export class SnapController extends BaseController {
926
1413
  if (this.isRunning(snapId)) {
927
1414
  await this.stopSnap(snapId, SnapStatusEvents.Stop);
928
1415
  }
929
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_transition).call(this, snapId, SnapStatusEvents.Update);
930
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_set).call(this, {
1416
+ this.#transition(snapId, SnapStatusEvents.Update);
1417
+ this.#set({
931
1418
  origin,
932
1419
  id: snapId,
933
1420
  files: newSnap,
934
1421
  isUpdate: true,
935
1422
  });
936
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updatePermissions).call(this, {
1423
+ this.#updatePermissions({
937
1424
  snapId,
938
1425
  unusedPermissions,
939
1426
  newPermissions: approvedNewPermissions,
940
1427
  requestData,
941
1428
  });
942
1429
  if (manifest.initialConnections) {
943
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_handleInitialConnections).call(this, snapId, oldManifest.initialConnections ?? null, manifest.initialConnections);
1430
+ this.#handleInitialConnections(snapId, oldManifest.initialConnections ?? null, manifest.initialConnections);
944
1431
  }
945
- const rollbackSnapshot = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRollbackSnapshot).call(this, snapId);
1432
+ const rollbackSnapshot = this.#getRollbackSnapshot(snapId);
946
1433
  if (rollbackSnapshot !== undefined) {
947
1434
  rollbackSnapshot.permissions.revoked = unusedPermissions;
948
1435
  rollbackSnapshot.permissions.granted = approvedNewPermissions;
@@ -951,7 +1438,7 @@ export class SnapController extends BaseController {
951
1438
  const sourceCode = sourceCodeFile.toString();
952
1439
  assert(typeof sourceCode === 'string' && sourceCode.length > 0, `Invalid source code for snap "${snapId}".`);
953
1440
  try {
954
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_startSnap).call(this, { snapId, sourceCode });
1441
+ await this.#startSnap({ snapId, sourceCode });
955
1442
  }
956
1443
  catch {
957
1444
  throw new Error(`Snap ${snapId} crashed with updated source code.`);
@@ -960,7 +1447,7 @@ export class SnapController extends BaseController {
960
1447
  if (emitEvent) {
961
1448
  this.messagingSystem.publish('SnapController:snapUpdated', truncatedSnap, snap.version, origin, false);
962
1449
  }
963
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updateApproval).call(this, pendingApproval.id, {
1450
+ this.#updateApproval(pendingApproval.id, {
964
1451
  loading: false,
965
1452
  type: SNAP_APPROVAL_UPDATE,
966
1453
  });
@@ -969,7 +1456,7 @@ export class SnapController extends BaseController {
969
1456
  catch (error) {
970
1457
  logError(`Error when updating ${snapId},`, error);
971
1458
  const errorString = error instanceof Error ? error.message : error.toString();
972
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updateApproval).call(this, pendingApproval.id, {
1459
+ this.#updateApproval(pendingApproval.id, {
973
1460
  loading: false,
974
1461
  error: errorString,
975
1462
  type: SNAP_APPROVAL_UPDATE,
@@ -978,6 +1465,235 @@ export class SnapController extends BaseController {
978
1465
  throw error;
979
1466
  }
980
1467
  }
1468
+ async #resolveAllowlistVersion(snapId, versionRange) {
1469
+ return await this.messagingSystem.call('SnapsRegistry:resolveVersion', snapId, versionRange);
1470
+ }
1471
+ /**
1472
+ * Returns a promise representing the complete installation of the requested snap.
1473
+ * If the snap is already being installed, the previously pending promise will be returned.
1474
+ *
1475
+ * @param args - Object containing the snap id and either the URL of the snap's manifest,
1476
+ * or the snap's manifest and source code. The object may also optionally contain a target
1477
+ * version.
1478
+ * @returns The resulting snap object.
1479
+ */
1480
+ async #add(args) {
1481
+ const { id: snapId, location, versionRange } = args;
1482
+ this.#setupRuntime(snapId);
1483
+ const runtime = this.#getRuntimeExpect(snapId);
1484
+ if (!runtime.installPromise) {
1485
+ log(`Adding snap: ${snapId}`);
1486
+ // If fetching and setting the snap succeeds, this property will be set
1487
+ // to null in the authorize() method.
1488
+ runtime.installPromise = (async () => {
1489
+ const fetchedSnap = await fetchSnap(snapId, location);
1490
+ const manifest = fetchedSnap.manifest.result;
1491
+ if (!satisfiesVersionRange(manifest.version, versionRange)) {
1492
+ throw new Error(`Version mismatch. Manifest for "${snapId}" specifies version "${manifest.version}" which doesn't satisfy requested version range "${versionRange}".`);
1493
+ }
1494
+ await this.#assertIsInstallAllowed(snapId, {
1495
+ version: manifest.version,
1496
+ checksum: manifest.source.shasum,
1497
+ permissions: manifest.initialPermissions,
1498
+ platformVersion: manifest.platformVersion,
1499
+ });
1500
+ return this.#set({
1501
+ ...args,
1502
+ files: fetchedSnap,
1503
+ id: snapId,
1504
+ });
1505
+ })();
1506
+ }
1507
+ try {
1508
+ return await runtime.installPromise;
1509
+ }
1510
+ catch (error) {
1511
+ // Reset promise so users can retry installation in case the problem is
1512
+ // temporary.
1513
+ runtime.installPromise = null;
1514
+ throw error;
1515
+ }
1516
+ }
1517
+ async #startSnap(snapData) {
1518
+ const { snapId } = snapData;
1519
+ if (this.isRunning(snapId)) {
1520
+ throw new Error(`Snap "${snapId}" is already started.`);
1521
+ }
1522
+ try {
1523
+ const runtime = this.#getRuntimeExpect(snapId);
1524
+ const result = await this.messagingSystem.call('ExecutionService:executeSnap', {
1525
+ ...snapData,
1526
+ endowments: await this.#getEndowments(snapId),
1527
+ });
1528
+ this.#transition(snapId, SnapStatusEvents.Start);
1529
+ // We treat the initialization of the snap as the first request, for idle timing purposes.
1530
+ runtime.lastRequest = Date.now();
1531
+ return result;
1532
+ }
1533
+ catch (error) {
1534
+ await this.#terminateSnap(snapId);
1535
+ throw error;
1536
+ }
1537
+ }
1538
+ /**
1539
+ * Gets the names of all endowments that will be added to the Snap's
1540
+ * Compartment when it executes. These should be the names of global
1541
+ * JavaScript APIs accessible in the root realm of the execution environment.
1542
+ *
1543
+ * Throws an error if the endowment getter for a permission returns a truthy
1544
+ * value that is not an array of strings.
1545
+ *
1546
+ * @param snapId - The id of the snap whose SES endowments to get.
1547
+ * @returns An array of the names of the endowments.
1548
+ */
1549
+ async #getEndowments(snapId) {
1550
+ let allEndowments = [];
1551
+ for (const permissionName of this.#environmentEndowmentPermissions) {
1552
+ if (this.messagingSystem.call('PermissionController:hasPermission', snapId, permissionName)) {
1553
+ const endowments = await this.messagingSystem.call('PermissionController:getEndowments', snapId, permissionName);
1554
+ if (endowments) {
1555
+ // We don't have any guarantees about the type of the endowments
1556
+ // value, so we have to guard at runtime.
1557
+ if (!Array.isArray(endowments) ||
1558
+ endowments.some((value) => typeof value !== 'string')) {
1559
+ throw new Error('Expected an array of string endowment names.');
1560
+ }
1561
+ allEndowments = allEndowments.concat(endowments);
1562
+ }
1563
+ }
1564
+ }
1565
+ const dedupedEndowments = [
1566
+ ...new Set([...DEFAULT_ENDOWMENTS, ...allEndowments]),
1567
+ ];
1568
+ if (dedupedEndowments.length <
1569
+ DEFAULT_ENDOWMENTS.length + allEndowments.length) {
1570
+ logError(`Duplicate endowments found for ${snapId}. Default endowments should not be requested.`, allEndowments);
1571
+ }
1572
+ return dedupedEndowments;
1573
+ }
1574
+ /**
1575
+ * Sets a snap in state. Called when a snap is installed or updated. Performs
1576
+ * various validation checks on the received arguments, and will throw if
1577
+ * validation fails.
1578
+ *
1579
+ * The snap will be enabled and unblocked by the time this method returns,
1580
+ * regardless of its previous state.
1581
+ *
1582
+ * See {@link SnapController.add} and {@link SnapController.updateSnap} for
1583
+ * usage.
1584
+ *
1585
+ * @param args - The add snap args.
1586
+ * @returns The resulting snap object.
1587
+ */
1588
+ #set(args) {
1589
+ const { id: snapId, origin, files, isUpdate = false, removable, preinstalled, hidden, hideSnapBranding, } = args;
1590
+ const { manifest, sourceCode: sourceCodeFile, svgIcon, auxiliaryFiles: rawAuxiliaryFiles, localizationFiles, } = files;
1591
+ assertIsSnapManifest(manifest.result);
1592
+ const { version } = manifest.result;
1593
+ const sourceCode = sourceCodeFile.toString();
1594
+ assert(typeof sourceCode === 'string' && sourceCode.length > 0, `Invalid source code for snap "${snapId}".`);
1595
+ const auxiliaryFiles = rawAuxiliaryFiles.map((file) => {
1596
+ assert(typeof file.data.base64 === 'string');
1597
+ return {
1598
+ path: file.path,
1599
+ value: file.data.base64,
1600
+ };
1601
+ });
1602
+ const snapsState = this.state.snaps;
1603
+ const existingSnap = snapsState[snapId];
1604
+ const previousVersionHistory = existingSnap?.versionHistory ?? [];
1605
+ const versionHistory = [
1606
+ ...previousVersionHistory,
1607
+ {
1608
+ version,
1609
+ date: Date.now(),
1610
+ origin,
1611
+ },
1612
+ ];
1613
+ const localizedFiles = localizationFiles.map((file) => file.result);
1614
+ const snap = {
1615
+ // Restore relevant snap state if it exists
1616
+ ...existingSnap,
1617
+ // Note that the snap will be unblocked and enabled, regardless of its
1618
+ // previous state.
1619
+ blocked: false,
1620
+ enabled: true,
1621
+ removable,
1622
+ preinstalled,
1623
+ hidden,
1624
+ hideSnapBranding,
1625
+ id: snapId,
1626
+ initialConnections: manifest.result.initialConnections,
1627
+ initialPermissions: manifest.result.initialPermissions,
1628
+ manifest: manifest.result,
1629
+ status: this.#statusMachine.config.initial,
1630
+ sourceCode,
1631
+ version,
1632
+ versionHistory,
1633
+ auxiliaryFiles,
1634
+ localizationFiles: localizedFiles,
1635
+ };
1636
+ // If the snap was blocked, it isn't any longer
1637
+ delete snap.blockInformation;
1638
+ // store the snap back in state
1639
+ const { inversePatches } = this.update((state) => {
1640
+ state.snaps[snapId] = snap;
1641
+ });
1642
+ // checking for isUpdate here as this function is also used in
1643
+ // the install flow, we do not care to create snapshots for installs
1644
+ if (isUpdate) {
1645
+ const rollbackSnapshot = this.#getRollbackSnapshot(snapId);
1646
+ if (rollbackSnapshot !== undefined) {
1647
+ rollbackSnapshot.statePatches = inversePatches;
1648
+ }
1649
+ }
1650
+ // In case the Snap uses a localized manifest, we need to get the
1651
+ // proposed name from the localized manifest.
1652
+ const { proposedName } = getLocalizedSnapManifest(manifest.result, 'en', localizedFiles);
1653
+ this.messagingSystem.call('SubjectMetadataController:addSubjectMetadata', {
1654
+ subjectType: SubjectType.Snap,
1655
+ name: proposedName,
1656
+ origin: snap.id,
1657
+ version,
1658
+ svgIcon: svgIcon?.toString() ?? null,
1659
+ });
1660
+ return { ...snap, sourceCode };
1661
+ }
1662
+ #validateSnapPermissions(processedPermissions) {
1663
+ const permissionKeys = Object.keys(processedPermissions);
1664
+ const handlerPermissions = Array.from(new Set(Object.values(handlerEndowments)));
1665
+ assert(permissionKeys.some((key) => handlerPermissions.includes(key)), `A snap must request at least one of the following permissions: ${handlerPermissions
1666
+ .filter((handler) => handler !== null)
1667
+ .join(', ')}.`);
1668
+ const excludedPermissionErrors = permissionKeys.reduce((errors, permission) => {
1669
+ if (hasProperty(this.#excludedPermissions, permission)) {
1670
+ errors.push(this.#excludedPermissions[permission]);
1671
+ }
1672
+ return errors;
1673
+ }, []);
1674
+ assert(excludedPermissionErrors.length === 0, `One or more permissions are not allowed:\n${excludedPermissionErrors.join('\n')}`);
1675
+ }
1676
+ /**
1677
+ * Validate that the platform version specified in the manifest (if any) is
1678
+ * compatible with the current platform version.
1679
+ *
1680
+ * @param snapId - The ID of the Snap.
1681
+ * @param platformVersion - The platform version to validate against.
1682
+ * @throws If the platform version is greater than the current platform
1683
+ * version.
1684
+ */
1685
+ #validatePlatformVersion(snapId, platformVersion) {
1686
+ if (platformVersion === undefined) {
1687
+ return;
1688
+ }
1689
+ if (gt(platformVersion, getPlatformVersion())) {
1690
+ const message = `The Snap "${snapId}" requires platform version "${platformVersion}" which is greater than the current platform version "${getPlatformVersion()}".`;
1691
+ if (this.#featureFlags.rejectInvalidPlatformVersion) {
1692
+ throw new Error(message);
1693
+ }
1694
+ logWarning(message);
1695
+ }
1696
+ }
981
1697
  /**
982
1698
  * Initiates a request for the given snap's initial permissions.
983
1699
  * Must be called in order. See processRequestedSnap.
@@ -996,31 +1712,31 @@ export class SnapController extends BaseController {
996
1712
  const { initialPermissions, initialConnections } = snap;
997
1713
  try {
998
1714
  const processedPermissions = processSnapPermissions(initialPermissions);
999
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_validateSnapPermissions).call(this, processedPermissions);
1000
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updateApproval).call(this, pendingApproval.id, {
1715
+ this.#validateSnapPermissions(processedPermissions);
1716
+ this.#updateApproval(pendingApproval.id, {
1001
1717
  loading: false,
1002
1718
  connections: initialConnections ?? {},
1003
1719
  permissions: processedPermissions,
1004
1720
  });
1005
1721
  const { permissions: approvedPermissions, ...requestData } = (await pendingApproval.promise);
1006
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updatePermissions).call(this, {
1722
+ this.#updatePermissions({
1007
1723
  snapId,
1008
1724
  newPermissions: approvedPermissions,
1009
1725
  requestData,
1010
1726
  });
1011
1727
  if (snap.manifest.initialConnections) {
1012
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_handleInitialConnections).call(this, snapId, null, snap.manifest.initialConnections);
1728
+ this.#handleInitialConnections(snapId, null, snap.manifest.initialConnections);
1013
1729
  }
1014
1730
  }
1015
1731
  finally {
1016
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1732
+ const runtime = this.#getRuntimeExpect(snapId);
1017
1733
  runtime.installPromise = null;
1018
1734
  }
1019
1735
  }
1020
1736
  destroy() {
1021
1737
  super.destroy();
1022
- if (__classPrivateFieldGet(this, _SnapController_timeoutForLastRequestStatus, "f")) {
1023
- clearTimeout(__classPrivateFieldGet(this, _SnapController_timeoutForLastRequestStatus, "f"));
1738
+ if (this.#timeoutForLastRequestStatus) {
1739
+ clearTimeout(this.#timeoutForLastRequestStatus);
1024
1740
  }
1025
1741
  /* eslint-disable @typescript-eslint/unbound-method */
1026
1742
  this.messagingSystem.unsubscribe('ExecutionService:unhandledError', this._onUnhandledSnapError);
@@ -1041,7 +1757,7 @@ export class SnapController extends BaseController {
1041
1757
  * @returns The result of the JSON-RPC request.
1042
1758
  */
1043
1759
  async handleRequest({ snapId, origin, handler: handlerType, request: rawRequest, }) {
1044
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertCanUsePlatform).call(this);
1760
+ this.#assertCanUsePlatform();
1045
1761
  assert(origin === METAMASK_ORIGIN || isValidUrl(origin), "'origin' must be a valid URL or 'metamask'.");
1046
1762
  const request = {
1047
1763
  jsonrpc: '2.0',
@@ -1082,9 +1798,12 @@ export class SnapController extends BaseController {
1082
1798
  if (this.state.snaps[snapId].status === SnapStatus.Installing) {
1083
1799
  throw new Error(`Snap "${snapId}" is currently being installed. Please try again later.`);
1084
1800
  }
1085
- const timeout = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getExecutionTimeout).call(this, handlerPermissions);
1801
+ const timeout = this.#getExecutionTimeout(handlerPermissions);
1802
+ const runtime = this.#getRuntimeExpect(snapId);
1803
+ if (runtime.stopPromise) {
1804
+ await runtime.stopPromise;
1805
+ }
1086
1806
  if (!this.isRunning(snapId)) {
1087
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1088
1807
  if (!runtime.startPromise) {
1089
1808
  runtime.startPromise = this.startSnap(snapId);
1090
1809
  }
@@ -1095,9 +1814,9 @@ export class SnapController extends BaseController {
1095
1814
  runtime.startPromise = null;
1096
1815
  }
1097
1816
  }
1098
- const transformedRequest = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_transformSnapRpcRequest).call(this, snapId, handlerType, request);
1817
+ const transformedRequest = this.#transformSnapRpcRequest(snapId, handlerType, request);
1099
1818
  const timer = new Timer(timeout);
1100
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_recordSnapRpcRequestStart).call(this, snapId, transformedRequest.id, timer);
1819
+ this.#recordSnapRpcRequestStart(snapId, transformedRequest.id, timer);
1101
1820
  const handleRpcRequestPromise = this.messagingSystem.call('ExecutionService:handleRpcRequest', snapId, { origin, handler: handlerType, request: transformedRequest });
1102
1821
  // This will either get the result or reject due to the timeout.
1103
1822
  try {
@@ -1105,1030 +1824,520 @@ export class SnapController extends BaseController {
1105
1824
  if (result === hasTimedOut) {
1106
1825
  throw new Error(`${snapId} failed to respond to the request in time.`);
1107
1826
  }
1108
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertSnapRpcResponse).call(this, snapId, handlerType, result);
1109
- const transformedResult = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_transformSnapRpcResponse).call(this, snapId, handlerType, transformedRequest, result);
1110
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_recordSnapRpcRequestFinish).call(this, snapId, transformedRequest.id, handlerType, origin, true);
1827
+ await this.#assertSnapRpcResponse(snapId, handlerType, result);
1828
+ const transformedResult = await this.#transformSnapRpcResponse(snapId, handlerType, transformedRequest, result);
1829
+ this.#recordSnapRpcRequestFinish(snapId, transformedRequest.id, handlerType, origin, true);
1111
1830
  return transformedResult;
1112
1831
  }
1113
1832
  catch (error) {
1114
1833
  // We flag the RPC request as finished early since termination may affect pending requests
1115
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_recordSnapRpcRequestFinish).call(this, snapId, transformedRequest.id, handlerType, origin, false);
1834
+ this.#recordSnapRpcRequestFinish(snapId, transformedRequest.id, handlerType, origin, false);
1116
1835
  const [jsonRpcError, handled] = unwrapError(error);
1117
1836
  if (!handled) {
1837
+ logError(`"${snapId}" crashed due to an unhandled error:`, jsonRpcError);
1118
1838
  await this.stopSnap(snapId, SnapStatusEvents.Crash);
1119
1839
  }
1120
1840
  throw jsonRpcError;
1121
1841
  }
1122
1842
  }
1123
- }
1124
- _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_getMnemonicSeed = new WeakMap(), _SnapController_getFeatureFlags = new WeakMap(), _SnapController_clientCryptography = 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_trackEvent = new WeakMap(), _SnapController_trackSnapExport = new WeakMap(), _SnapController_persistSnapState = new WeakMap(), _SnapController_instances = new WeakSet(), _SnapController_initializeStateMachine = function _SnapController_initializeStateMachine() {
1125
- const disableGuard = ({ snapId }) => {
1126
- return this.getExpect(snapId).enabled;
1127
- };
1128
- const statusConfig = {
1129
- initial: SnapStatus.Installing,
1130
- states: {
1131
- [SnapStatus.Installing]: {
1132
- on: {
1133
- [SnapStatusEvents.Start]: {
1134
- target: SnapStatus.Running,
1135
- cond: disableGuard,
1136
- },
1137
- },
1138
- },
1139
- [SnapStatus.Updating]: {
1140
- on: {
1141
- [SnapStatusEvents.Start]: {
1142
- target: SnapStatus.Running,
1143
- cond: disableGuard,
1144
- },
1145
- [SnapStatusEvents.Stop]: SnapStatus.Stopped,
1146
- },
1147
- },
1148
- [SnapStatus.Running]: {
1149
- on: {
1150
- [SnapStatusEvents.Stop]: SnapStatus.Stopped,
1151
- [SnapStatusEvents.Crash]: SnapStatus.Crashed,
1152
- },
1153
- },
1154
- [SnapStatus.Stopped]: {
1155
- on: {
1156
- [SnapStatusEvents.Start]: {
1157
- target: SnapStatus.Running,
1158
- cond: disableGuard,
1159
- },
1160
- [SnapStatusEvents.Update]: SnapStatus.Updating,
1161
- },
1162
- },
1163
- [SnapStatus.Crashed]: {
1164
- on: {
1165
- [SnapStatusEvents.Start]: {
1166
- target: SnapStatus.Running,
1167
- cond: disableGuard,
1168
- },
1169
- [SnapStatusEvents.Update]: SnapStatus.Updating,
1170
- },
1171
- },
1172
- },
1173
- };
1174
- __classPrivateFieldSet(this, _SnapController_statusMachine, createMachine(statusConfig), "f");
1175
- validateMachine(__classPrivateFieldGet(this, _SnapController_statusMachine, "f"));
1176
- }, _SnapController_registerMessageHandlers = function _SnapController_registerMessageHandlers() {
1177
- this.messagingSystem.registerActionHandler(`${controllerName}:clearSnapState`, (...args) => this.clearSnapState(...args));
1178
- this.messagingSystem.registerActionHandler(`${controllerName}:get`, (...args) => this.get(...args));
1179
- this.messagingSystem.registerActionHandler(`${controllerName}:getSnapState`, async (...args) => this.getSnapState(...args));
1180
- this.messagingSystem.registerActionHandler(`${controllerName}:handleRequest`, async (...args) => this.handleRequest(...args));
1181
- this.messagingSystem.registerActionHandler(`${controllerName}:has`, (...args) => this.has(...args));
1182
- this.messagingSystem.registerActionHandler(`${controllerName}:updateBlockedSnaps`, async () => this.updateBlockedSnaps());
1183
- this.messagingSystem.registerActionHandler(`${controllerName}:updateSnapState`, async (...args) => this.updateSnapState(...args));
1184
- this.messagingSystem.registerActionHandler(`${controllerName}:enable`, (...args) => this.enableSnap(...args));
1185
- this.messagingSystem.registerActionHandler(`${controllerName}:disable`, async (...args) => this.disableSnap(...args));
1186
- this.messagingSystem.registerActionHandler(`${controllerName}:remove`, async (...args) => this.removeSnap(...args));
1187
- this.messagingSystem.registerActionHandler(`${controllerName}:getPermitted`, (...args) => this.getPermittedSnaps(...args));
1188
- this.messagingSystem.registerActionHandler(`${controllerName}:install`, async (...args) => this.installSnaps(...args));
1189
- this.messagingSystem.registerActionHandler(`${controllerName}:getAll`, (...args) => this.getAllSnaps(...args));
1190
- this.messagingSystem.registerActionHandler(`${controllerName}:getRunnableSnaps`, (...args) => this.getRunnableSnaps(...args));
1191
- this.messagingSystem.registerActionHandler(`${controllerName}:incrementActiveReferences`, (...args) => this.incrementActiveReferences(...args));
1192
- this.messagingSystem.registerActionHandler(`${controllerName}:decrementActiveReferences`, (...args) => this.decrementActiveReferences(...args));
1193
- this.messagingSystem.registerActionHandler(`${controllerName}:disconnectOrigin`, (...args) => this.removeSnapFromSubject(...args));
1194
- this.messagingSystem.registerActionHandler(`${controllerName}:revokeDynamicPermissions`, (...args) => this.revokeDynamicSnapPermissions(...args));
1195
- this.messagingSystem.registerActionHandler(`${controllerName}:getFile`, async (...args) => this.getSnapFile(...args));
1196
- this.messagingSystem.registerActionHandler(`${controllerName}:stopAllSnaps`, async (...args) => this.stopAllSnaps(...args));
1197
- this.messagingSystem.registerActionHandler(`${controllerName}:isMinimumPlatformVersion`, (...args) => this.isMinimumPlatformVersion(...args));
1198
- }, _SnapController_handlePreinstalledSnaps = function _SnapController_handlePreinstalledSnaps(preinstalledSnaps) {
1199
- for (const { snapId, manifest, files, removable, hidden, hideSnapBranding, } of preinstalledSnaps) {
1200
- const existingSnap = this.get(snapId);
1201
- const isAlreadyInstalled = existingSnap !== undefined;
1202
- const isUpdate = isAlreadyInstalled && gtVersion(manifest.version, existingSnap.version);
1203
- // Disallow downgrades and overwriting non preinstalled snaps
1204
- if (isAlreadyInstalled &&
1205
- (!isUpdate || existingSnap.preinstalled !== true)) {
1206
- continue;
1207
- }
1208
- const manifestFile = new VirtualFile({
1209
- path: NpmSnapFileNames.Manifest,
1210
- value: JSON.stringify(manifest),
1211
- result: manifest,
1212
- });
1213
- const virtualFiles = files.map(({ path, value }) => new VirtualFile({ value, path }));
1214
- const { filePath, iconPath } = manifest.source.location.npm;
1215
- const sourceCode = virtualFiles.find((file) => file.path === filePath);
1216
- const svgIcon = iconPath
1217
- ? virtualFiles.find((file) => file.path === iconPath)
1218
- : undefined;
1219
- assert(sourceCode, 'Source code not provided for preinstalled snap.');
1220
- assert(!iconPath || (iconPath && svgIcon), 'Icon not provided for preinstalled snap.');
1221
- assert(manifest.source.files === undefined, 'Auxiliary files are not currently supported for preinstalled snaps.');
1222
- const localizationFiles = manifest.source.locales?.map((path) => virtualFiles.find((file) => file.path === path)) ?? [];
1223
- const validatedLocalizationFiles = getValidatedLocalizationFiles(localizationFiles.filter(Boolean));
1224
- assert(localizationFiles.length === validatedLocalizationFiles.length, 'Missing localization files for preinstalled snap.');
1225
- const filesObject = {
1226
- manifest: manifestFile,
1227
- sourceCode,
1228
- svgIcon,
1229
- auxiliaryFiles: [],
1230
- localizationFiles: validatedLocalizationFiles,
1231
- };
1232
- // Add snap to the SnapController state
1233
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_set).call(this, {
1234
- id: snapId,
1235
- origin: METAMASK_ORIGIN,
1236
- files: filesObject,
1237
- removable,
1238
- hidden,
1239
- hideSnapBranding,
1240
- preinstalled: true,
1241
- });
1242
- // Setup permissions
1243
- const processedPermissions = processSnapPermissions(manifest.initialPermissions);
1244
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_validateSnapPermissions).call(this, processedPermissions);
1245
- const { newPermissions, unusedPermissions } = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_calculatePermissionsChange).call(this, snapId, processedPermissions);
1246
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updatePermissions).call(this, { snapId, newPermissions, unusedPermissions });
1247
- if (manifest.initialConnections) {
1248
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_handleInitialConnections).call(this, snapId, existingSnap?.initialConnections ?? null, manifest.initialConnections);
1249
- }
1250
- // Set status
1251
- this.update((state) => {
1252
- state.snaps[snapId].status = SnapStatus.Stopped;
1253
- });
1254
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_setupRuntime).call(this, snapId);
1255
- // Emit events
1256
- if (isUpdate) {
1257
- this.messagingSystem.publish('SnapController:snapUpdated', this.getTruncatedExpect(snapId), existingSnap.version, METAMASK_ORIGIN, true);
1258
- }
1259
- else {
1260
- this.messagingSystem.publish('SnapController:snapInstalled', this.getTruncatedExpect(snapId), METAMASK_ORIGIN, true);
1261
- }
1262
- }
1263
- }, _SnapController_pollForLastRequestStatus = function _SnapController_pollForLastRequestStatus() {
1264
- __classPrivateFieldSet(this, _SnapController_timeoutForLastRequestStatus, setTimeout(() => {
1265
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_stopSnapsLastRequestPastMax).call(this).catch((error) => {
1266
- // TODO: Decide how to handle errors.
1267
- logError(error);
1268
- });
1269
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_pollForLastRequestStatus).call(this);
1270
- }, __classPrivateFieldGet(this, _SnapController_idleTimeCheckInterval, "f")), "f");
1271
- }, _SnapController_blockSnap =
1272
- /**
1273
- * Blocks an installed snap and prevents it from being started again. Emits
1274
- * {@link SnapBlocked}. Does nothing if the snap is not installed.
1275
- *
1276
- * @param snapId - The snap to block.
1277
- * @param blockedSnapInfo - Information detailing why the snap is blocked.
1278
- */
1279
- async function _SnapController_blockSnap(snapId, blockedSnapInfo) {
1280
- if (!this.has(snapId)) {
1281
- return;
1282
- }
1283
- try {
1284
- this.update((state) => {
1285
- state.snaps[snapId].blocked = true;
1286
- state.snaps[snapId].blockInformation = blockedSnapInfo;
1287
- });
1288
- await this.disableSnap(snapId);
1289
- }
1290
- catch (error) {
1291
- logError(`Encountered error when stopping blocked snap "${snapId}".`, error);
1292
- }
1293
- this.messagingSystem.publish(`${controllerName}:snapBlocked`, snapId, blockedSnapInfo);
1294
- }, _SnapController_unblockSnap = function _SnapController_unblockSnap(snapId) {
1295
- if (!this.has(snapId) || !this.state.snaps[snapId].blocked) {
1296
- return;
1297
- }
1298
- this.update((state) => {
1299
- state.snaps[snapId].blocked = false;
1300
- delete state.snaps[snapId].blockInformation;
1301
- });
1302
- this.messagingSystem.publish(`${controllerName}:snapUnblocked`, snapId);
1303
- }, _SnapController_assertIsInstallAllowed = async function _SnapController_assertIsInstallAllowed(snapId, { platformVersion, ...snapInfo }) {
1304
- const results = await this.messagingSystem.call('SnapsRegistry:get', {
1305
- [snapId]: snapInfo,
1306
- });
1307
- const result = results[snapId];
1308
- if (result.status === SnapsRegistryStatus.Blocked) {
1309
- throw new Error(`Cannot install version "${snapInfo.version}" of snap "${snapId}": The version is blocked. ${result.reason?.explanation ?? ''}`);
1310
- }
1311
- const isAllowlistingRequired = Object.keys(snapInfo.permissions).some((permission) => !ALLOWED_PERMISSIONS.includes(permission));
1312
- if (__classPrivateFieldGet(this, _SnapController_featureFlags, "f").requireAllowlist &&
1313
- isAllowlistingRequired &&
1314
- result.status !== SnapsRegistryStatus.Verified) {
1315
- throw new Error(`Cannot install version "${snapInfo.version}" of snap "${snapId}": ${result.status === SnapsRegistryStatus.Unavailable
1316
- ? 'The registry is temporarily unavailable.'
1317
- : 'The snap is not on the allowlist.'}`);
1318
- }
1319
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_validatePlatformVersion).call(this, snapId, platformVersion);
1320
- }, _SnapController_assertCanInstallSnaps = function _SnapController_assertCanInstallSnaps() {
1321
- assert(__classPrivateFieldGet(this, _SnapController_featureFlags, "f").disableSnapInstallation !== true, 'Installing Snaps is currently disabled in this version of MetaMask.');
1322
- }, _SnapController_assertCanUsePlatform = function _SnapController_assertCanUsePlatform() {
1323
- const flags = __classPrivateFieldGet(this, _SnapController_getFeatureFlags, "f").call(this);
1324
- assert(flags.disableSnaps !== true, 'The Snaps platform requires basic functionality to be used. Enable basic functionality in the settings to use the Snaps platform.');
1325
- }, _SnapController_stopSnapsLastRequestPastMax = async function _SnapController_stopSnapsLastRequestPastMax() {
1326
- const entries = [...__classPrivateFieldGet(this, _SnapController_snapsRuntimeData, "f").entries()];
1327
- return Promise.all(entries
1328
- .filter(([_snapId, runtime]) => runtime.activeReferences === 0 &&
1329
- runtime.pendingInboundRequests.length === 0 &&
1330
- runtime.lastRequest &&
1331
- __classPrivateFieldGet(this, _SnapController_maxIdleTime, "f") &&
1332
- timeSince(runtime.lastRequest) > __classPrivateFieldGet(this, _SnapController_maxIdleTime, "f"))
1333
- .map(async ([snapId]) => this.stopSnap(snapId, SnapStatusEvents.Stop)));
1334
- }, _SnapController_transition = function _SnapController_transition(snapId, event) {
1335
- const { interpreter } = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1336
- interpreter.send(event);
1337
- this.update((state) => {
1338
- state.snaps[snapId].status = interpreter.state.value;
1339
- });
1340
- }, _SnapController_terminateSnap =
1341
- /**
1342
- * Terminates the specified snap and emits the `snapTerminated` event.
1343
- *
1344
- * @param snapId - The snap to terminate.
1345
- */
1346
- async function _SnapController_terminateSnap(snapId) {
1347
- await this.messagingSystem.call('ExecutionService:terminateSnap', snapId);
1348
- // Hack to give up execution for a bit to let gracefully terminating Snaps return.
1349
- await new Promise((resolve) => setTimeout(resolve, 1));
1350
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1351
- // Unresponsive requests may still be timed, time them out.
1352
- runtime.pendingInboundRequests
1353
- .filter((pendingRequest) => pendingRequest.timer.status !== 'finished')
1354
- .forEach((pendingRequest) => pendingRequest.timer.finish());
1355
- // Hack to give up execution for a bit to let timed out requests return.
1356
- await new Promise((resolve) => setTimeout(resolve, 1));
1357
- this.messagingSystem.publish('SnapController:snapTerminated', this.getTruncatedExpect(snapId));
1358
- }, _SnapController_hasCachedEncryptionKey = function _SnapController_hasCachedEncryptionKey(snapId, runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId)) {
1359
- return runtime.encryptionKey !== null && runtime.encryptionSalt !== null;
1360
- }, _SnapController_getSnapEncryptionKey =
1361
- /**
1362
- * Generate an encryption key to be used for state encryption for a given Snap.
1363
- *
1364
- * @param options - An options bag.
1365
- * @param options.snapId - The Snap ID.
1366
- * @param options.salt - A salt to be used for the encryption key.
1367
- * @param options.useCache - Whether to use caching or not.
1368
- * @param options.keyMetadata - Optional metadata about how to derive the encryption key.
1369
- * @returns An encryption key.
1370
- */
1371
- async function _SnapController_getSnapEncryptionKey({ snapId, salt: passedSalt, useCache, keyMetadata, }) {
1372
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1373
- if (__classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_hasCachedEncryptionKey).call(this, snapId, runtime) && useCache) {
1374
- return {
1375
- key: await __classPrivateFieldGet(this, _SnapController_encryptor, "f").importKey(runtime.encryptionKey),
1376
- salt: runtime.encryptionSalt,
1377
- };
1843
+ /**
1844
+ * Determine the execution timeout for a given handler permission.
1845
+ *
1846
+ * If no permission is specified or the permission itself has no execution timeout defined
1847
+ * the constructor argument `maxRequestTime` will be used.
1848
+ *
1849
+ * @param permission - An optional permission constraint for the handler being called.
1850
+ * @returns The execution timeout for the given handler.
1851
+ */
1852
+ #getExecutionTimeout(permission) {
1853
+ return getMaxRequestTimeCaveat(permission) ?? this.maxRequestTime;
1378
1854
  }
1379
- const salt = passedSalt ?? __classPrivateFieldGet(this, _SnapController_encryptor, "f").generateSalt();
1380
- const seed = await __classPrivateFieldGet(this, _SnapController_getMnemonicSeed, "f").call(this);
1381
- const entropy = await getEncryptionEntropy({
1382
- snapId,
1383
- seed,
1384
- cryptographicFunctions: __classPrivateFieldGet(this, _SnapController_clientCryptography, "f"),
1385
- });
1386
- const encryptionKey = await __classPrivateFieldGet(this, _SnapController_encryptor, "f").keyFromPassword(entropy, salt, true, keyMetadata);
1387
- const exportedKey = await __classPrivateFieldGet(this, _SnapController_encryptor, "f").exportKey(encryptionKey);
1388
- // Cache exported encryption key in runtime
1389
- if (useCache) {
1390
- runtime.encryptionKey = exportedKey;
1391
- runtime.encryptionSalt = salt;
1392
- }
1393
- return { key: encryptionKey, salt };
1394
- }, _SnapController_decryptSnapState =
1395
- /**
1396
- * Decrypt the encrypted state for a given Snap.
1397
- *
1398
- * @param snapId - The Snap ID.
1399
- * @param state - The encrypted state as a string.
1400
- * @returns A valid JSON object derived from the encrypted state.
1401
- * @throws If the decryption fails or the decrypted state is not valid JSON.
1402
- */
1403
- async function _SnapController_decryptSnapState(snapId, state) {
1404
- try {
1405
- // We assume that the state string here is valid JSON since we control serialization.
1406
- // This lets us skip JSON validation.
1407
- const parsed = JSON.parse(state);
1408
- const { salt, keyMetadata } = parsed;
1409
- // We only cache encryption keys if they are already cached or if the encryption key is using the latest key derivation params.
1410
- const useCache = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_hasCachedEncryptionKey).call(this, snapId) ||
1411
- __classPrivateFieldGet(this, _SnapController_encryptor, "f").isVaultUpdated(state);
1412
- const { key } = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getSnapEncryptionKey).call(this, {
1413
- snapId,
1414
- salt,
1415
- useCache,
1416
- // When decrypting state we expect key metadata to be present.
1417
- // If it isn't present, we assume that the Snap state we are decrypting is old enough to use the legacy encryption params.
1418
- keyMetadata: keyMetadata ?? LEGACY_ENCRYPTION_KEY_DERIVATION_OPTIONS,
1419
- });
1420
- const decryptedState = await __classPrivateFieldGet(this, _SnapController_encryptor, "f").decryptWithKey(key, parsed);
1421
- // We assume this to be valid JSON, since all RPC requests from a Snap are validated and sanitized.
1422
- return decryptedState;
1855
+ /**
1856
+ * Create a dynamic interface in the SnapInterfaceController.
1857
+ *
1858
+ * @param snapId - The snap ID.
1859
+ * @param content - The initial interface content.
1860
+ * @param contentType - The type of content.
1861
+ * @returns An identifier that can be used to identify the interface.
1862
+ */
1863
+ async #createInterface(snapId, content, contentType) {
1864
+ return this.messagingSystem.call('SnapInterfaceController:createInterface', snapId, content, undefined, contentType);
1423
1865
  }
1424
- catch {
1425
- throw rpcErrors.internal({
1426
- message: 'Failed to decrypt snap state, the state must be corrupted.',
1427
- });
1866
+ #assertInterfaceExists(snapId, id) {
1867
+ // This will throw if the interface isn't accessible, but we assert nevertheless.
1868
+ assert(this.messagingSystem.call('SnapInterfaceController:getInterface', snapId, id));
1428
1869
  }
1429
- }, _SnapController_encryptSnapState =
1430
- /**
1431
- * Encrypt a JSON state object for a given Snap.
1432
- *
1433
- * Note: This function does not assert the validity of the object,
1434
- * please ensure only valid JSON is passed to it.
1435
- *
1436
- * @param snapId - The Snap ID.
1437
- * @param state - The state object.
1438
- * @returns A string containing the encrypted JSON object.
1439
- */
1440
- async function _SnapController_encryptSnapState(snapId, state) {
1441
- const { key, salt } = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getSnapEncryptionKey).call(this, {
1442
- snapId,
1443
- useCache: true,
1444
- });
1445
- const encryptedState = await __classPrivateFieldGet(this, _SnapController_encryptor, "f").encryptWithKey(key, state);
1446
- encryptedState.salt = salt;
1447
- return JSON.stringify(encryptedState);
1448
- }, _SnapController_getStateToPersist =
1449
- /**
1450
- * Get the new Snap state to persist based on the given state and encryption
1451
- * flag.
1452
- *
1453
- * - If the state is null, return null.
1454
- * - If the state should be encrypted, return the encrypted state.
1455
- * - Otherwise, if the state should not be encrypted, return the JSON-
1456
- * stringified state.
1457
- *
1458
- * @param snapId - The Snap ID.
1459
- * @param state - The state to persist.
1460
- * @param encrypted - A flag to indicate whether to use encrypted storage or
1461
- * not.
1462
- * @returns The state to persist.
1463
- */
1464
- async function _SnapController_getStateToPersist(snapId, state, encrypted) {
1465
- if (state === null) {
1466
- return null;
1467
- }
1468
- if (encrypted) {
1469
- return await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_encryptSnapState).call(this, snapId, state);
1470
- }
1471
- return JSON.stringify(state);
1472
- }, _SnapController_handleInitialConnections = function _SnapController_handleInitialConnections(snapId, previousInitialConnections, initialConnections) {
1473
- if (previousInitialConnections) {
1474
- const revokedInitialConnections = setDiff(previousInitialConnections, initialConnections);
1475
- for (const origin of Object.keys(revokedInitialConnections)) {
1476
- this.removeSnapFromSubject(origin, snapId);
1477
- }
1478
- }
1479
- for (const origin of Object.keys(initialConnections)) {
1480
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_addSnapToSubject).call(this, origin, snapId);
1481
- }
1482
- }, _SnapController_addSnapToSubject = function _SnapController_addSnapToSubject(origin, snapId) {
1483
- const subjectPermissions = this.messagingSystem.call('PermissionController:getPermissions', origin);
1484
- const existingCaveat = subjectPermissions?.[WALLET_SNAP_PERMISSION_KEY]?.caveats?.find((caveat) => caveat.type === SnapCaveatType.SnapIds);
1485
- const subjectHasSnap = Boolean(existingCaveat?.value?.[snapId]);
1486
- // If the subject is already connected to the snap, this is a no-op.
1487
- if (subjectHasSnap) {
1488
- return;
1489
- }
1490
- // If an existing caveat exists, we add the snap to that.
1491
- if (existingCaveat) {
1492
- this.messagingSystem.call('PermissionController:updateCaveat', origin, WALLET_SNAP_PERMISSION_KEY, SnapCaveatType.SnapIds, { ...existingCaveat.value, [snapId]: {} });
1493
- return;
1494
- }
1495
- const approvedPermissions = {
1496
- [WALLET_SNAP_PERMISSION_KEY]: {
1497
- caveats: [
1498
- {
1499
- type: SnapCaveatType.SnapIds,
1500
- value: {
1501
- [snapId]: {},
1502
- },
1503
- },
1504
- ],
1505
- },
1506
- };
1507
- this.messagingSystem.call('PermissionController:grantPermissions', {
1508
- approvedPermissions,
1509
- subject: { origin },
1510
- });
1511
- }, _SnapController_removeSnapFromSubjects = function _SnapController_removeSnapFromSubjects(snapId) {
1512
- const subjects = this.messagingSystem.call('PermissionController:getSubjectNames');
1513
- for (const subject of subjects) {
1514
- this.removeSnapFromSubject(subject, snapId);
1515
- }
1516
- }, _SnapController_revokeAllSnapPermissions = function _SnapController_revokeAllSnapPermissions(snapId) {
1517
- if (this.messagingSystem.call('PermissionController:hasPermissions', snapId)) {
1518
- this.messagingSystem.call('PermissionController:revokeAllPermissions', snapId);
1519
- }
1520
- }, _SnapController_createApproval = function _SnapController_createApproval({ origin, snapId, type, }) {
1521
- const id = nanoid();
1522
- const promise = this.messagingSystem.call('ApprovalController:addRequest', {
1523
- origin,
1524
- id,
1525
- type,
1526
- requestData: {
1527
- // Mirror previous installation metadata
1528
- metadata: { id, origin: snapId, dappOrigin: origin },
1529
- snapId,
1530
- },
1531
- requestState: {
1532
- loading: true,
1533
- },
1534
- }, true);
1535
- return { id, promise };
1536
- }, _SnapController_updateApproval = function _SnapController_updateApproval(id, requestState) {
1537
- try {
1538
- this.messagingSystem.call('ApprovalController:updateRequestState', {
1539
- id,
1540
- requestState,
1541
- });
1870
+ /**
1871
+ * Transform a RPC response if necessary.
1872
+ *
1873
+ * @param snapId - The snap ID of the snap that produced the result.
1874
+ * @param handlerType - The handler type that produced the result.
1875
+ * @param request - The request that returned the result.
1876
+ * @param result - The response.
1877
+ * @returns The transformed result if applicable, otherwise the original result.
1878
+ */
1879
+ async #transformSnapRpcResponse(snapId, handlerType, request, result) {
1880
+ switch (handlerType) {
1881
+ case HandlerType.OnTransaction:
1882
+ case HandlerType.OnSignature:
1883
+ case HandlerType.OnHomePage:
1884
+ case HandlerType.OnSettingsPage: {
1885
+ // Since this type has been asserted earlier we can cast
1886
+ const castResult = result;
1887
+ // If a handler returns static content, we turn it into a dynamic UI
1888
+ if (castResult && hasProperty(castResult, 'content')) {
1889
+ const { content, ...rest } = castResult;
1890
+ const id = await this.#createInterface(snapId, content);
1891
+ return { ...rest, id };
1892
+ }
1893
+ return result;
1894
+ }
1895
+ case HandlerType.OnAssetsLookup:
1896
+ // We can cast since the request and result have already been validated.
1897
+ return this.#transformOnAssetsLookupResult(snapId, request, result);
1898
+ case HandlerType.OnAssetsConversion:
1899
+ // We can cast since the request and result have already been validated.
1900
+ return this.#transformOnAssetsConversionResult(request, result);
1901
+ default:
1902
+ return result;
1903
+ }
1542
1904
  }
1543
- catch {
1544
- // Do nothing
1905
+ /**
1906
+ * Transform an RPC response coming from the `onAssetsLookup` handler.
1907
+ *
1908
+ * This filters out responses that are out of scope for the Snap based on
1909
+ * its permissions and the incoming request.
1910
+ *
1911
+ * @param snapId - The snap ID of the snap that produced the result.
1912
+ * @param request - The request that returned the result.
1913
+ * @param request.params - The parameters for the request.
1914
+ * @param result - The result.
1915
+ * @param result.assets - The assets returned by the Snap.
1916
+ * @returns The transformed result.
1917
+ */
1918
+ #transformOnAssetsLookupResult(snapId, { params: requestedParams }, { assets }) {
1919
+ const permissions = this.messagingSystem.call('PermissionController:getPermissions', snapId);
1920
+ // We know the permissions are guaranteed to be set here.
1921
+ assert(permissions);
1922
+ const permission = permissions[SnapEndowments.Assets];
1923
+ const scopes = getChainIdsCaveat(permission);
1924
+ assert(scopes);
1925
+ const { assets: requestedAssets } = requestedParams;
1926
+ const filteredAssets = Object.keys(assets).reduce((accumulator, assetType) => {
1927
+ const castAssetType = assetType;
1928
+ const isValid = scopes.some((scope) => castAssetType.startsWith(scope)) &&
1929
+ requestedAssets.includes(castAssetType);
1930
+ // Filter out unrequested assets and assets for scopes the Snap hasn't registered for.
1931
+ if (isValid) {
1932
+ accumulator[castAssetType] = assets[castAssetType];
1933
+ }
1934
+ return accumulator;
1935
+ }, {});
1936
+ return { assets: filteredAssets };
1545
1937
  }
1546
- }, _SnapController_resolveAllowlistVersion = async function _SnapController_resolveAllowlistVersion(snapId, versionRange) {
1547
- return await this.messagingSystem.call('SnapsRegistry:resolveVersion', snapId, versionRange);
1548
- }, _SnapController_add =
1549
- /**
1550
- * Returns a promise representing the complete installation of the requested snap.
1551
- * If the snap is already being installed, the previously pending promise will be returned.
1552
- *
1553
- * @param args - Object containing the snap id and either the URL of the snap's manifest,
1554
- * or the snap's manifest and source code. The object may also optionally contain a target
1555
- * version.
1556
- * @returns The resulting snap object.
1557
- */
1558
- async function _SnapController_add(args) {
1559
- const { id: snapId, location, versionRange } = args;
1560
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_setupRuntime).call(this, snapId);
1561
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1562
- if (!runtime.installPromise) {
1563
- log(`Adding snap: ${snapId}`);
1564
- // If fetching and setting the snap succeeds, this property will be set
1565
- // to null in the authorize() method.
1566
- runtime.installPromise = (async () => {
1567
- const fetchedSnap = await fetchSnap(snapId, location);
1568
- const manifest = fetchedSnap.manifest.result;
1569
- if (!satisfiesVersionRange(manifest.version, versionRange)) {
1570
- throw new Error(`Version mismatch. Manifest for "${snapId}" specifies version "${manifest.version}" which doesn't satisfy requested version range "${versionRange}".`);
1938
+ /**
1939
+ * Transform an RPC response coming from the `onAssetsConversion` handler.
1940
+ *
1941
+ * This filters out responses that are out of scope for the Snap based on
1942
+ * the incoming request.
1943
+ *
1944
+ * @param request - The request that returned the result.
1945
+ * @param request.params - The parameters for the request.
1946
+ * @param result - The result.
1947
+ * @param result.conversionRates - The conversion rates returned by the Snap.
1948
+ * @returns The transformed result.
1949
+ */
1950
+ #transformOnAssetsConversionResult({ params: requestedParams }, { conversionRates }) {
1951
+ const { conversions: requestedConversions } = requestedParams;
1952
+ const filteredConversionRates = requestedConversions.reduce((accumulator, conversion) => {
1953
+ const rate = conversionRates[conversion.from]?.[conversion.to];
1954
+ // Only include rates that were actually requested.
1955
+ if (rate) {
1956
+ accumulator[conversion.from] ??= {};
1957
+ accumulator[conversion.from][conversion.to] = rate;
1571
1958
  }
1572
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertIsInstallAllowed).call(this, snapId, {
1573
- version: manifest.version,
1574
- checksum: manifest.source.shasum,
1575
- permissions: manifest.initialPermissions,
1576
- platformVersion: manifest.platformVersion,
1577
- });
1578
- return __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_set).call(this, {
1579
- ...args,
1580
- files: fetchedSnap,
1581
- id: snapId,
1582
- });
1583
- })();
1584
- }
1585
- try {
1586
- return await runtime.installPromise;
1587
- }
1588
- catch (error) {
1589
- // Reset promise so users can retry installation in case the problem is
1590
- // temporary.
1591
- runtime.installPromise = null;
1592
- throw error;
1593
- }
1594
- }, _SnapController_startSnap = async function _SnapController_startSnap(snapData) {
1595
- const { snapId } = snapData;
1596
- if (this.isRunning(snapId)) {
1597
- throw new Error(`Snap "${snapId}" is already started.`);
1598
- }
1599
- try {
1600
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1601
- const result = await this.messagingSystem.call('ExecutionService:executeSnap', {
1602
- ...snapData,
1603
- endowments: await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getEndowments).call(this, snapId),
1604
- });
1605
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_transition).call(this, snapId, SnapStatusEvents.Start);
1606
- // We treat the initialization of the snap as the first request, for idle timing purposes.
1607
- runtime.lastRequest = Date.now();
1608
- return result;
1959
+ return accumulator;
1960
+ }, {});
1961
+ return { conversionRates: filteredConversionRates };
1609
1962
  }
1610
- catch (error) {
1611
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_terminateSnap).call(this, snapId);
1612
- throw error;
1963
+ /**
1964
+ * Transforms a JSON-RPC request before sending it to the Snap, if required for a given handler.
1965
+ *
1966
+ * @param snapId - The Snap ID.
1967
+ * @param handlerType - The handler being called.
1968
+ * @param request - The JSON-RPC request.
1969
+ * @returns The potentially transformed JSON-RPC request.
1970
+ */
1971
+ #transformSnapRpcRequest(snapId, handlerType, request) {
1972
+ switch (handlerType) {
1973
+ // For onUserInput we inject context, so the client doesn't have to worry about keeping it in sync.
1974
+ case HandlerType.OnUserInput: {
1975
+ assert(request.params && hasProperty(request.params, 'id'));
1976
+ const interfaceId = request.params.id;
1977
+ const { context } = this.messagingSystem.call('SnapInterfaceController:getInterface', snapId, interfaceId);
1978
+ return {
1979
+ ...request,
1980
+ params: { ...request.params, context },
1981
+ };
1982
+ }
1983
+ default:
1984
+ return request;
1985
+ }
1613
1986
  }
1614
- }, _SnapController_getEndowments =
1615
- /**
1616
- * Gets the names of all endowments that will be added to the Snap's
1617
- * Compartment when it executes. These should be the names of global
1618
- * JavaScript APIs accessible in the root realm of the execution environment.
1619
- *
1620
- * Throws an error if the endowment getter for a permission returns a truthy
1621
- * value that is not an array of strings.
1622
- *
1623
- * @param snapId - The id of the snap whose SES endowments to get.
1624
- * @returns An array of the names of the endowments.
1625
- */
1626
- async function _SnapController_getEndowments(snapId) {
1627
- let allEndowments = [];
1628
- for (const permissionName of __classPrivateFieldGet(this, _SnapController_environmentEndowmentPermissions, "f")) {
1629
- if (this.messagingSystem.call('PermissionController:hasPermission', snapId, permissionName)) {
1630
- const endowments = await this.messagingSystem.call('PermissionController:getEndowments', snapId, permissionName);
1631
- if (endowments) {
1632
- // We don't have any guarantees about the type of the endowments
1633
- // value, so we have to guard at runtime.
1634
- if (!Array.isArray(endowments) ||
1635
- endowments.some((value) => typeof value !== 'string')) {
1636
- throw new Error('Expected an array of string endowment names.');
1987
+ /**
1988
+ * Assert that the returned result of a Snap RPC call is the expected shape.
1989
+ *
1990
+ * @param snapId - The snap ID.
1991
+ * @param handlerType - The handler type of the RPC Request.
1992
+ * @param result - The result of the RPC request.
1993
+ */
1994
+ async #assertSnapRpcResponse(snapId, handlerType, result) {
1995
+ switch (handlerType) {
1996
+ case HandlerType.OnTransaction: {
1997
+ assertStruct(result, OnTransactionResponseStruct);
1998
+ if (result && hasProperty(result, 'id')) {
1999
+ this.#assertInterfaceExists(snapId, result.id);
2000
+ }
2001
+ break;
2002
+ }
2003
+ case HandlerType.OnSignature: {
2004
+ assertStruct(result, OnSignatureResponseStruct);
2005
+ if (result && hasProperty(result, 'id')) {
2006
+ this.#assertInterfaceExists(snapId, result.id);
1637
2007
  }
1638
- allEndowments = allEndowments.concat(endowments);
2008
+ break;
1639
2009
  }
2010
+ case HandlerType.OnHomePage: {
2011
+ assertStruct(result, OnHomePageResponseStruct);
2012
+ if (result && hasProperty(result, 'id')) {
2013
+ this.#assertInterfaceExists(snapId, result.id);
2014
+ }
2015
+ break;
2016
+ }
2017
+ case HandlerType.OnSettingsPage: {
2018
+ assertStruct(result, OnSettingsPageResponseStruct);
2019
+ if (result && hasProperty(result, 'id')) {
2020
+ this.#assertInterfaceExists(snapId, result.id);
2021
+ }
2022
+ break;
2023
+ }
2024
+ case HandlerType.OnNameLookup:
2025
+ assertStruct(result, OnNameLookupResponseStruct);
2026
+ break;
2027
+ case HandlerType.OnAssetsLookup:
2028
+ assertStruct(result, OnAssetsLookupResponseStruct);
2029
+ break;
2030
+ case HandlerType.OnAssetsConversion:
2031
+ assertStruct(result, OnAssetsConversionResponseStruct);
2032
+ break;
2033
+ case HandlerType.OnAssetHistoricalPrice:
2034
+ assertStruct(result, OnAssetHistoricalPriceResponseStruct);
2035
+ break;
2036
+ default:
2037
+ break;
1640
2038
  }
1641
2039
  }
1642
- const dedupedEndowments = [
1643
- ...new Set([...DEFAULT_ENDOWMENTS, ...allEndowments]),
1644
- ];
1645
- if (dedupedEndowments.length <
1646
- DEFAULT_ENDOWMENTS.length + allEndowments.length) {
1647
- logError(`Duplicate endowments found for ${snapId}. Default endowments should not be requested.`, allEndowments);
1648
- }
1649
- return dedupedEndowments;
1650
- }, _SnapController_set = function _SnapController_set(args) {
1651
- const { id: snapId, origin, files, isUpdate = false, removable, preinstalled, hidden, hideSnapBranding, } = args;
1652
- const { manifest, sourceCode: sourceCodeFile, svgIcon, auxiliaryFiles: rawAuxiliaryFiles, localizationFiles, } = files;
1653
- assertIsSnapManifest(manifest.result);
1654
- const { version } = manifest.result;
1655
- const sourceCode = sourceCodeFile.toString();
1656
- assert(typeof sourceCode === 'string' && sourceCode.length > 0, `Invalid source code for snap "${snapId}".`);
1657
- const auxiliaryFiles = rawAuxiliaryFiles.map((file) => {
1658
- assert(typeof file.data.base64 === 'string');
1659
- return {
1660
- path: file.path,
1661
- value: file.data.base64,
1662
- };
1663
- });
1664
- const snapsState = this.state.snaps;
1665
- const existingSnap = snapsState[snapId];
1666
- const previousVersionHistory = existingSnap?.versionHistory ?? [];
1667
- const versionHistory = [
1668
- ...previousVersionHistory,
1669
- {
1670
- version,
1671
- date: Date.now(),
1672
- origin,
1673
- },
1674
- ];
1675
- const localizedFiles = localizationFiles.map((file) => file.result);
1676
- const snap = {
1677
- // Restore relevant snap state if it exists
1678
- ...existingSnap,
1679
- // Note that the snap will be unblocked and enabled, regardless of its
1680
- // previous state.
1681
- blocked: false,
1682
- enabled: true,
1683
- removable,
1684
- preinstalled,
1685
- hidden,
1686
- hideSnapBranding,
1687
- id: snapId,
1688
- initialConnections: manifest.result.initialConnections,
1689
- initialPermissions: manifest.result.initialPermissions,
1690
- manifest: manifest.result,
1691
- status: __classPrivateFieldGet(this, _SnapController_statusMachine, "f").config.initial,
1692
- sourceCode,
1693
- version,
1694
- versionHistory,
1695
- auxiliaryFiles,
1696
- localizationFiles: localizedFiles,
1697
- };
1698
- // If the snap was blocked, it isn't any longer
1699
- delete snap.blockInformation;
1700
- // store the snap back in state
1701
- const { inversePatches } = this.update((state) => {
1702
- state.snaps[snapId] = snap;
1703
- });
1704
- // checking for isUpdate here as this function is also used in
1705
- // the install flow, we do not care to create snapshots for installs
1706
- if (isUpdate) {
1707
- const rollbackSnapshot = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRollbackSnapshot).call(this, snapId);
1708
- if (rollbackSnapshot !== undefined) {
1709
- rollbackSnapshot.statePatches = inversePatches;
1710
- }
1711
- }
1712
- // In case the Snap uses a localized manifest, we need to get the
1713
- // proposed name from the localized manifest.
1714
- const { proposedName } = getLocalizedSnapManifest(manifest.result, 'en', localizedFiles);
1715
- this.messagingSystem.call('SubjectMetadataController:addSubjectMetadata', {
1716
- subjectType: SubjectType.Snap,
1717
- name: proposedName,
1718
- origin: snap.id,
1719
- version,
1720
- svgIcon: svgIcon?.toString() ?? null,
1721
- });
1722
- return { ...snap, sourceCode };
1723
- }, _SnapController_validateSnapPermissions = function _SnapController_validateSnapPermissions(processedPermissions) {
1724
- const permissionKeys = Object.keys(processedPermissions);
1725
- const handlerPermissions = Array.from(new Set(Object.values(handlerEndowments)));
1726
- assert(permissionKeys.some((key) => handlerPermissions.includes(key)), `A snap must request at least one of the following permissions: ${handlerPermissions
1727
- .filter((handler) => handler !== null)
1728
- .join(', ')}.`);
1729
- const excludedPermissionErrors = permissionKeys.reduce((errors, permission) => {
1730
- if (hasProperty(__classPrivateFieldGet(this, _SnapController_excludedPermissions, "f"), permission)) {
1731
- errors.push(__classPrivateFieldGet(this, _SnapController_excludedPermissions, "f")[permission]);
1732
- }
1733
- return errors;
1734
- }, []);
1735
- assert(excludedPermissionErrors.length === 0, `One or more permissions are not allowed:\n${excludedPermissionErrors.join('\n')}`);
1736
- }, _SnapController_validatePlatformVersion = function _SnapController_validatePlatformVersion(snapId, platformVersion) {
1737
- if (platformVersion === undefined) {
1738
- return;
1739
- }
1740
- if (gt(platformVersion, getPlatformVersion())) {
1741
- const message = `The Snap "${snapId}" requires platform version "${platformVersion}" which is greater than the current platform version "${getPlatformVersion()}".`;
1742
- if (__classPrivateFieldGet(this, _SnapController_featureFlags, "f").rejectInvalidPlatformVersion) {
1743
- throw new Error(message);
1744
- }
1745
- logWarning(message);
1746
- }
1747
- }, _SnapController_getExecutionTimeout = function _SnapController_getExecutionTimeout(permission) {
1748
- return getMaxRequestTimeCaveat(permission) ?? this.maxRequestTime;
1749
- }, _SnapController_createInterface =
1750
- /**
1751
- * Create a dynamic interface in the SnapInterfaceController.
1752
- *
1753
- * @param snapId - The snap ID.
1754
- * @param content - The initial interface content.
1755
- * @param contentType - The type of content.
1756
- * @returns An identifier that can be used to identify the interface.
1757
- */
1758
- async function _SnapController_createInterface(snapId, content, contentType) {
1759
- return this.messagingSystem.call('SnapInterfaceController:createInterface', snapId, content, undefined, contentType);
1760
- }, _SnapController_assertInterfaceExists = function _SnapController_assertInterfaceExists(snapId, id) {
1761
- // This will throw if the interface isn't accessible, but we assert nevertheless.
1762
- assert(this.messagingSystem.call('SnapInterfaceController:getInterface', snapId, id));
1763
- }, _SnapController_transformSnapRpcResponse =
1764
- /**
1765
- * Transform a RPC response if necessary.
1766
- *
1767
- * @param snapId - The snap ID of the snap that produced the result.
1768
- * @param handlerType - The handler type that produced the result.
1769
- * @param request - The request that returned the result.
1770
- * @param result - The response.
1771
- * @returns The transformed result if applicable, otherwise the original result.
1772
- */
1773
- async function _SnapController_transformSnapRpcResponse(snapId, handlerType, request, result) {
1774
- switch (handlerType) {
1775
- case HandlerType.OnTransaction:
1776
- case HandlerType.OnSignature:
1777
- case HandlerType.OnHomePage:
1778
- case HandlerType.OnSettingsPage: {
1779
- // Since this type has been asserted earlier we can cast
1780
- const castResult = result;
1781
- // If a handler returns static content, we turn it into a dynamic UI
1782
- if (castResult && hasProperty(castResult, 'content')) {
1783
- const { content, ...rest } = castResult;
1784
- const id = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_createInterface).call(this, snapId, content);
1785
- return { ...rest, id };
2040
+ #recordSnapRpcRequestStart(snapId, requestId, timer) {
2041
+ const runtime = this.#getRuntimeExpect(snapId);
2042
+ runtime.pendingInboundRequests.push({ requestId, timer });
2043
+ runtime.lastRequest = null;
2044
+ }
2045
+ #recordSnapRpcRequestFinish(snapId, requestId, handlerType, origin, success) {
2046
+ const runtime = this.#getRuntimeExpect(snapId);
2047
+ runtime.pendingInboundRequests = runtime.pendingInboundRequests.filter((request) => request.requestId !== requestId);
2048
+ if (runtime.pendingInboundRequests.length === 0) {
2049
+ runtime.lastRequest = Date.now();
2050
+ }
2051
+ const snap = this.get(snapId);
2052
+ if (isTrackableHandler(handlerType) && !snap?.preinstalled) {
2053
+ try {
2054
+ this.#trackSnapExport(snapId, handlerType, success, origin);
2055
+ }
2056
+ catch (error) {
2057
+ logError(`Error when calling MetaMetrics hook for snap "${snap?.id}": ${getErrorMessage(error)}`);
1786
2058
  }
1787
- return result;
1788
2059
  }
1789
- case HandlerType.OnAssetsLookup:
1790
- // We can cast since the request and result have already been validated.
1791
- return __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_transformOnAssetsLookupResult).call(this, snapId, request, result);
1792
- case HandlerType.OnAssetsConversion:
1793
- // We can cast since the request and result have already been validated.
1794
- return __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_transformOnAssetsConversionResult).call(this, request, result);
1795
- default:
1796
- return result;
1797
2060
  }
1798
- }, _SnapController_transformOnAssetsLookupResult = function _SnapController_transformOnAssetsLookupResult(snapId, { params: requestedParams }, { assets }) {
1799
- const permissions = this.messagingSystem.call('PermissionController:getPermissions', snapId);
1800
- // We know the permissions are guaranteed to be set here.
1801
- assert(permissions);
1802
- const permission = permissions[SnapEndowments.Assets];
1803
- const scopes = getChainIdsCaveat(permission);
1804
- assert(scopes);
1805
- const { assets: requestedAssets } = requestedParams;
1806
- const filteredAssets = Object.keys(assets).reduce((accumulator, assetType) => {
1807
- const castAssetType = assetType;
1808
- const isValid = scopes.some((scope) => castAssetType.startsWith(scope)) &&
1809
- requestedAssets.includes(castAssetType);
1810
- // Filter out unrequested assets and assets for scopes the Snap hasn't registered for.
1811
- if (isValid) {
1812
- accumulator[castAssetType] = assets[castAssetType];
1813
- }
1814
- return accumulator;
1815
- }, {});
1816
- return { assets: filteredAssets };
1817
- }, _SnapController_transformOnAssetsConversionResult = function _SnapController_transformOnAssetsConversionResult({ params: requestedParams }, { conversionRates }) {
1818
- const { conversions: requestedConversions } = requestedParams;
1819
- const filteredConversionRates = requestedConversions.reduce((accumulator, conversion) => {
1820
- var _a;
1821
- const rate = conversionRates[conversion.from]?.[conversion.to];
1822
- // Only include rates that were actually requested.
1823
- if (rate) {
1824
- accumulator[_a = conversion.from] ?? (accumulator[_a] = {});
1825
- accumulator[conversion.from][conversion.to] = rate;
1826
- }
1827
- return accumulator;
1828
- }, {});
1829
- return { conversionRates: filteredConversionRates };
1830
- }, _SnapController_transformSnapRpcRequest = function _SnapController_transformSnapRpcRequest(snapId, handlerType, request) {
1831
- switch (handlerType) {
1832
- // For onUserInput we inject context, so the client doesn't have to worry about keeping it in sync.
1833
- case HandlerType.OnUserInput: {
1834
- assert(request.params && hasProperty(request.params, 'id'));
1835
- const interfaceId = request.params.id;
1836
- const { context } = this.messagingSystem.call('SnapInterfaceController:getInterface', snapId, interfaceId);
1837
- return {
1838
- ...request,
1839
- params: { ...request.params, context },
1840
- };
1841
- }
1842
- default:
1843
- return request;
2061
+ /**
2062
+ * Retrieves the rollback snapshot of a snap.
2063
+ *
2064
+ * @param snapId - The snap id.
2065
+ * @returns A `RollbackSnapshot` or `undefined` if one doesn't exist.
2066
+ */
2067
+ #getRollbackSnapshot(snapId) {
2068
+ return this.#rollbackSnapshots.get(snapId);
1844
2069
  }
1845
- }, _SnapController_assertSnapRpcResponse =
1846
- /**
1847
- * Assert that the returned result of a Snap RPC call is the expected shape.
1848
- *
1849
- * @param snapId - The snap ID.
1850
- * @param handlerType - The handler type of the RPC Request.
1851
- * @param result - The result of the RPC request.
1852
- */
1853
- async function _SnapController_assertSnapRpcResponse(snapId, handlerType, result) {
1854
- switch (handlerType) {
1855
- case HandlerType.OnTransaction: {
1856
- assertStruct(result, OnTransactionResponseStruct);
1857
- if (result && hasProperty(result, 'id')) {
1858
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertInterfaceExists).call(this, snapId, result.id);
1859
- }
1860
- break;
2070
+ /**
2071
+ * Creates a `RollbackSnapshot` that is used to help ensure
2072
+ * atomicity in multiple snap updates.
2073
+ *
2074
+ * @param snapId - The snap id.
2075
+ * @throws {@link Error}. If the snap exists before creation or if creation fails.
2076
+ * @returns A `RollbackSnapshot`.
2077
+ */
2078
+ #createRollbackSnapshot(snapId) {
2079
+ assert(this.#rollbackSnapshots.get(snapId) === undefined, new Error(`Snap "${snapId}" rollback snapshot already exists.`));
2080
+ this.#rollbackSnapshots.set(snapId, {
2081
+ statePatches: [],
2082
+ permissions: {},
2083
+ newVersion: '',
2084
+ });
2085
+ const newRollbackSnapshot = this.#rollbackSnapshots.get(snapId);
2086
+ assert(newRollbackSnapshot !== undefined, new Error(`Snapshot creation failed for ${snapId}.`));
2087
+ return newRollbackSnapshot;
2088
+ }
2089
+ /**
2090
+ * Rolls back a snap to its previous state, permissions
2091
+ * and source code based on the `RollbackSnapshot` that
2092
+ * is captured during the update process. After rolling back,
2093
+ * the function also emits an event indicating that the
2094
+ * snap has been rolled back and it clears the snapshot
2095
+ * for that snap.
2096
+ *
2097
+ * @param snapId - The snap id.
2098
+ * @throws {@link Error}. If a snapshot does not exist.
2099
+ */
2100
+ async #rollbackSnap(snapId) {
2101
+ const rollbackSnapshot = this.#getRollbackSnapshot(snapId);
2102
+ if (!rollbackSnapshot) {
2103
+ throw new Error('A snapshot does not exist for this snap.');
1861
2104
  }
1862
- case HandlerType.OnSignature: {
1863
- assertStruct(result, OnSignatureResponseStruct);
1864
- if (result && hasProperty(result, 'id')) {
1865
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertInterfaceExists).call(this, snapId, result.id);
1866
- }
1867
- break;
2105
+ await this.stopSnap(snapId, SnapStatusEvents.Stop);
2106
+ // Always set to stopped even if it wasn't running initially
2107
+ if (this.get(snapId)?.status !== SnapStatus.Stopped) {
2108
+ this.#transition(snapId, SnapStatusEvents.Stop);
1868
2109
  }
1869
- case HandlerType.OnHomePage: {
1870
- assertStruct(result, OnHomePageResponseStruct);
1871
- if (result && hasProperty(result, 'id')) {
1872
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertInterfaceExists).call(this, snapId, result.id);
1873
- }
1874
- break;
2110
+ const { statePatches, permissions } = rollbackSnapshot;
2111
+ if (statePatches?.length) {
2112
+ this.applyPatches(statePatches);
1875
2113
  }
1876
- case HandlerType.OnSettingsPage: {
1877
- assertStruct(result, OnSettingsPageResponseStruct);
1878
- if (result && hasProperty(result, 'id')) {
1879
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertInterfaceExists).call(this, snapId, result.id);
1880
- }
1881
- break;
1882
- }
1883
- case HandlerType.OnNameLookup:
1884
- assertStruct(result, OnNameLookupResponseStruct);
1885
- break;
1886
- case HandlerType.OnAssetsLookup:
1887
- assertStruct(result, OnAssetsLookupResponseStruct);
1888
- break;
1889
- case HandlerType.OnAssetsConversion:
1890
- assertStruct(result, OnAssetsConversionResponseStruct);
1891
- break;
1892
- case HandlerType.OnAssetHistoricalPrice:
1893
- assertStruct(result, OnAssetHistoricalPriceResponseStruct);
1894
- break;
1895
- default:
1896
- break;
1897
- }
1898
- }, _SnapController_recordSnapRpcRequestStart = function _SnapController_recordSnapRpcRequestStart(snapId, requestId, timer) {
1899
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1900
- runtime.pendingInboundRequests.push({ requestId, timer });
1901
- runtime.lastRequest = null;
1902
- }, _SnapController_recordSnapRpcRequestFinish = function _SnapController_recordSnapRpcRequestFinish(snapId, requestId, handlerType, origin, success) {
1903
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1904
- runtime.pendingInboundRequests = runtime.pendingInboundRequests.filter((request) => request.requestId !== requestId);
1905
- if (runtime.pendingInboundRequests.length === 0) {
1906
- runtime.lastRequest = Date.now();
1907
- }
1908
- const snap = this.get(snapId);
1909
- if (isTrackableHandler(handlerType) && !snap?.preinstalled) {
1910
- try {
1911
- __classPrivateFieldGet(this, _SnapController_trackSnapExport, "f").call(this, snapId, handlerType, success, origin);
2114
+ // Reset snap status, as we may have been in another state when we stored state patches
2115
+ // But now we are 100% in a stopped state
2116
+ if (this.get(snapId)?.status !== SnapStatus.Stopped) {
2117
+ this.update((state) => {
2118
+ state.snaps[snapId].status = SnapStatus.Stopped;
2119
+ });
1912
2120
  }
1913
- catch (error) {
1914
- logError(`Error when calling MetaMetrics hook for snap "${snap?.id}": ${getErrorMessage(error)}`);
1915
- }
1916
- }
1917
- }, _SnapController_getRollbackSnapshot = function _SnapController_getRollbackSnapshot(snapId) {
1918
- return __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").get(snapId);
1919
- }, _SnapController_createRollbackSnapshot = function _SnapController_createRollbackSnapshot(snapId) {
1920
- assert(__classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").get(snapId) === undefined, new Error(`Snap "${snapId}" rollback snapshot already exists.`));
1921
- __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").set(snapId, {
1922
- statePatches: [],
1923
- permissions: {},
1924
- newVersion: '',
1925
- });
1926
- const newRollbackSnapshot = __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").get(snapId);
1927
- assert(newRollbackSnapshot !== undefined, new Error(`Snapshot creation failed for ${snapId}.`));
1928
- return newRollbackSnapshot;
1929
- }, _SnapController_rollbackSnap =
1930
- /**
1931
- * Rolls back a snap to its previous state, permissions
1932
- * and source code based on the `RollbackSnapshot` that
1933
- * is captured during the update process. After rolling back,
1934
- * the function also emits an event indicating that the
1935
- * snap has been rolled back and it clears the snapshot
1936
- * for that snap.
1937
- *
1938
- * @param snapId - The snap id.
1939
- * @throws {@link Error}. If a snapshot does not exist.
1940
- */
1941
- async function _SnapController_rollbackSnap(snapId) {
1942
- const rollbackSnapshot = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRollbackSnapshot).call(this, snapId);
1943
- if (!rollbackSnapshot) {
1944
- throw new Error('A snapshot does not exist for this snap.');
1945
- }
1946
- await this.stopSnap(snapId, SnapStatusEvents.Stop);
1947
- // Always set to stopped even if it wasn't running initially
1948
- if (this.get(snapId)?.status !== SnapStatus.Stopped) {
1949
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_transition).call(this, snapId, SnapStatusEvents.Stop);
1950
- }
1951
- const { statePatches, permissions } = rollbackSnapshot;
1952
- if (statePatches?.length) {
1953
- this.applyPatches(statePatches);
1954
- }
1955
- // Reset snap status, as we may have been in another state when we stored state patches
1956
- // But now we are 100% in a stopped state
1957
- if (this.get(snapId)?.status !== SnapStatus.Stopped) {
1958
- this.update((state) => {
1959
- state.snaps[snapId].status = SnapStatus.Stopped;
2121
+ this.#updatePermissions({
2122
+ snapId,
2123
+ unusedPermissions: permissions.granted,
2124
+ newPermissions: permissions.revoked,
2125
+ requestData: permissions.requestData,
1960
2126
  });
2127
+ const truncatedSnap = this.getTruncatedExpect(snapId);
2128
+ this.messagingSystem.publish('SnapController:snapRolledback', truncatedSnap, rollbackSnapshot.newVersion);
2129
+ this.#rollbackSnapshots.delete(snapId);
1961
2130
  }
1962
- __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_updatePermissions).call(this, {
1963
- snapId,
1964
- unusedPermissions: permissions.granted,
1965
- newPermissions: permissions.revoked,
1966
- requestData: permissions.requestData,
1967
- });
1968
- const truncatedSnap = this.getTruncatedExpect(snapId);
1969
- this.messagingSystem.publish('SnapController:snapRolledback', truncatedSnap, rollbackSnapshot.newVersion);
1970
- __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").delete(snapId);
1971
- }, _SnapController_rollbackSnaps =
1972
- /**
1973
- * Iterates through an array of snap ids
1974
- * and calls `rollbackSnap` on them.
1975
- *
1976
- * @param snapIds - An array of snap ids.
1977
- */
1978
- async function _SnapController_rollbackSnaps(snapIds) {
1979
- for (const snapId of snapIds) {
1980
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_rollbackSnap).call(this, snapId);
1981
- }
1982
- }, _SnapController_getRuntime = function _SnapController_getRuntime(snapId) {
1983
- return __classPrivateFieldGet(this, _SnapController_snapsRuntimeData, "f").get(snapId);
1984
- }, _SnapController_getRuntimeExpect = function _SnapController_getRuntimeExpect(snapId) {
1985
- const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntime).call(this, snapId);
1986
- assert(runtime !== undefined, new Error(`Snap "${snapId}" runtime data not found`));
1987
- return runtime;
1988
- }, _SnapController_setupRuntime = function _SnapController_setupRuntime(snapId) {
1989
- if (__classPrivateFieldGet(this, _SnapController_snapsRuntimeData, "f").has(snapId)) {
1990
- return;
1991
- }
1992
- const snap = this.get(snapId);
1993
- const interpreter = interpret(__classPrivateFieldGet(this, _SnapController_statusMachine, "f"));
1994
- interpreter.start({
1995
- context: { snapId },
1996
- value: snap?.status ??
1997
- __classPrivateFieldGet(this, _SnapController_statusMachine, "f").config.initial,
1998
- });
1999
- forceStrict(interpreter);
2000
- __classPrivateFieldGet(this, _SnapController_snapsRuntimeData, "f").set(snapId, {
2001
- lastRequest: null,
2002
- startPromise: null,
2003
- installPromise: null,
2004
- encryptionKey: null,
2005
- encryptionSalt: null,
2006
- activeReferences: 0,
2007
- pendingInboundRequests: [],
2008
- pendingOutboundRequests: 0,
2009
- interpreter,
2010
- stopping: false,
2011
- stateMutex: new Mutex(),
2012
- getStateMutex: new Mutex(),
2013
- });
2014
- }, _SnapController_calculatePermissionsChange = function _SnapController_calculatePermissionsChange(snapId, desiredPermissionsSet) {
2015
- const oldPermissions = this.messagingSystem.call('PermissionController:getPermissions', snapId) ?? {};
2016
- const newPermissions = permissionsDiff(desiredPermissionsSet, oldPermissions);
2017
- // TODO(ritave): The assumption that these are unused only holds so long as we do not
2018
- // permit dynamic permission requests.
2019
- const unusedPermissions = permissionsDiff(oldPermissions, desiredPermissionsSet);
2020
- // It's a Set Intersection of oldPermissions and desiredPermissionsSet
2021
- // oldPermissions (oldPermissions ∖ desiredPermissionsSet) oldPermissions desiredPermissionsSet
2022
- const approvedPermissions = permissionsDiff(oldPermissions, unusedPermissions);
2023
- return { newPermissions, unusedPermissions, approvedPermissions };
2024
- }, _SnapController_isSubjectConnectedToSnap = function _SnapController_isSubjectConnectedToSnap(snapId, origin) {
2025
- const subjectPermissions = this.messagingSystem.call('PermissionController:getPermissions', origin);
2026
- const existingCaveat = subjectPermissions?.[WALLET_SNAP_PERMISSION_KEY]?.caveats?.find((caveat) => caveat.type === SnapCaveatType.SnapIds);
2027
- return Boolean(existingCaveat?.value?.[snapId]);
2028
- }, _SnapController_calculateConnectionsChange = function _SnapController_calculateConnectionsChange(snapId, oldConnectionsSet, desiredConnectionsSet) {
2029
- // Filter out any origins that have been revoked since last install/update.
2030
- // That way they will be represented as new.
2031
- const filteredOldConnections = Object.keys(oldConnectionsSet)
2032
- .filter((origin) => __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_isSubjectConnectedToSnap).call(this, snapId, origin))
2033
- .reduce((accumulator, origin) => {
2034
- accumulator[origin] = oldConnectionsSet[origin];
2035
- return accumulator;
2036
- }, {});
2037
- const newConnections = setDiff(desiredConnectionsSet, filteredOldConnections);
2038
- const unusedConnections = setDiff(filteredOldConnections, desiredConnectionsSet);
2039
- // It's a Set Intersection of oldConnections and desiredConnectionsSet
2040
- // oldConnections ∖ (oldConnections ∖ desiredConnectionsSet) ⟺ oldConnections ∩ desiredConnectionsSet
2041
- const approvedConnections = setDiff(filteredOldConnections, unusedConnections);
2042
- return { newConnections, unusedConnections, approvedConnections };
2043
- }, _SnapController_getPermissionsToGrant = function _SnapController_getPermissionsToGrant(snapId, newPermissions) {
2044
- if (__classPrivateFieldGet(this, _SnapController_featureFlags, "f").useCaip25Permission &&
2045
- Object.keys(newPermissions).includes(SnapEndowments.EthereumProvider)) {
2046
- // This will return the globally selected network if the Snap doesn't have
2047
- // one set.
2048
- const networkClientId = this.messagingSystem.call('SelectedNetworkController:getNetworkClientIdForDomain', snapId);
2049
- const { configuration } = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
2050
- const chainId = hexToNumber(configuration.chainId);
2051
- // This needs to be assigned to have proper type inference.
2052
- const modifiedPermissions = {
2053
- ...newPermissions,
2054
- 'endowment:caip25': {
2055
- caveats: [
2056
- {
2057
- type: 'authorizedScopes',
2058
- value: {
2059
- requiredScopes: {},
2060
- optionalScopes: {
2061
- [`eip155:${chainId}`]: {
2062
- accounts: [],
2131
+ /**
2132
+ * Iterates through an array of snap ids
2133
+ * and calls `rollbackSnap` on them.
2134
+ *
2135
+ * @param snapIds - An array of snap ids.
2136
+ */
2137
+ async #rollbackSnaps(snapIds) {
2138
+ for (const snapId of snapIds) {
2139
+ await this.#rollbackSnap(snapId);
2140
+ }
2141
+ }
2142
+ #getRuntime(snapId) {
2143
+ return this.#snapsRuntimeData.get(snapId);
2144
+ }
2145
+ #getRuntimeExpect(snapId) {
2146
+ const runtime = this.#getRuntime(snapId);
2147
+ assert(runtime !== undefined, new Error(`Snap "${snapId}" runtime data not found`));
2148
+ return runtime;
2149
+ }
2150
+ #setupRuntime(snapId) {
2151
+ if (this.#snapsRuntimeData.has(snapId)) {
2152
+ return;
2153
+ }
2154
+ const snap = this.get(snapId);
2155
+ const interpreter = interpret(this.#statusMachine);
2156
+ interpreter.start({
2157
+ context: { snapId },
2158
+ value: snap?.status ??
2159
+ this.#statusMachine.config.initial,
2160
+ });
2161
+ forceStrict(interpreter);
2162
+ this.#snapsRuntimeData.set(snapId, {
2163
+ lastRequest: null,
2164
+ startPromise: null,
2165
+ stopPromise: null,
2166
+ installPromise: null,
2167
+ encryptionKey: null,
2168
+ encryptionSalt: null,
2169
+ activeReferences: 0,
2170
+ pendingInboundRequests: [],
2171
+ pendingOutboundRequests: 0,
2172
+ interpreter,
2173
+ stateMutex: new Mutex(),
2174
+ getStateMutex: new Mutex(),
2175
+ });
2176
+ }
2177
+ #calculatePermissionsChange(snapId, desiredPermissionsSet) {
2178
+ const oldPermissions = this.messagingSystem.call('PermissionController:getPermissions', snapId) ?? {};
2179
+ const newPermissions = permissionsDiff(desiredPermissionsSet, oldPermissions);
2180
+ // TODO(ritave): The assumption that these are unused only holds so long as we do not
2181
+ // permit dynamic permission requests.
2182
+ const unusedPermissions = permissionsDiff(oldPermissions, desiredPermissionsSet);
2183
+ // It's a Set Intersection of oldPermissions and desiredPermissionsSet
2184
+ // oldPermissions (oldPermissions ∖ desiredPermissionsSet) oldPermissions ∩ desiredPermissionsSet
2185
+ const approvedPermissions = permissionsDiff(oldPermissions, unusedPermissions);
2186
+ return { newPermissions, unusedPermissions, approvedPermissions };
2187
+ }
2188
+ #isSubjectConnectedToSnap(snapId, origin) {
2189
+ const subjectPermissions = this.messagingSystem.call('PermissionController:getPermissions', origin);
2190
+ const existingCaveat = subjectPermissions?.[WALLET_SNAP_PERMISSION_KEY]?.caveats?.find((caveat) => caveat.type === SnapCaveatType.SnapIds);
2191
+ return Boolean(existingCaveat?.value?.[snapId]);
2192
+ }
2193
+ #calculateConnectionsChange(snapId, oldConnectionsSet, desiredConnectionsSet) {
2194
+ // Filter out any origins that have been revoked since last install/update.
2195
+ // That way they will be represented as new.
2196
+ const filteredOldConnections = Object.keys(oldConnectionsSet)
2197
+ .filter((origin) => this.#isSubjectConnectedToSnap(snapId, origin))
2198
+ .reduce((accumulator, origin) => {
2199
+ accumulator[origin] = oldConnectionsSet[origin];
2200
+ return accumulator;
2201
+ }, {});
2202
+ const newConnections = setDiff(desiredConnectionsSet, filteredOldConnections);
2203
+ const unusedConnections = setDiff(filteredOldConnections, desiredConnectionsSet);
2204
+ // It's a Set Intersection of oldConnections and desiredConnectionsSet
2205
+ // oldConnections ∖ (oldConnections ∖ desiredConnectionsSet) ⟺ oldConnections ∩ desiredConnectionsSet
2206
+ const approvedConnections = setDiff(filteredOldConnections, unusedConnections);
2207
+ return { newConnections, unusedConnections, approvedConnections };
2208
+ }
2209
+ /**
2210
+ * Get the permissions to grant to a Snap following an install, update or
2211
+ * rollback.
2212
+ *
2213
+ * @param snapId - The snap ID.
2214
+ * @param newPermissions - The new permissions to be granted.
2215
+ * @returns The permissions to grant to the Snap.
2216
+ */
2217
+ #getPermissionsToGrant(snapId, newPermissions) {
2218
+ if (this.#featureFlags.useCaip25Permission &&
2219
+ Object.keys(newPermissions).includes(SnapEndowments.EthereumProvider)) {
2220
+ // This will return the globally selected network if the Snap doesn't have
2221
+ // one set.
2222
+ const networkClientId = this.messagingSystem.call('SelectedNetworkController:getNetworkClientIdForDomain', snapId);
2223
+ const { configuration } = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
2224
+ const chainId = hexToNumber(configuration.chainId);
2225
+ // This needs to be assigned to have proper type inference.
2226
+ const modifiedPermissions = {
2227
+ ...newPermissions,
2228
+ 'endowment:caip25': {
2229
+ caveats: [
2230
+ {
2231
+ type: 'authorizedScopes',
2232
+ value: {
2233
+ requiredScopes: {},
2234
+ optionalScopes: {
2235
+ [`eip155:${chainId}`]: {
2236
+ accounts: [],
2237
+ },
2063
2238
  },
2239
+ sessionProperties: {},
2240
+ isMultichainOrigin: false,
2064
2241
  },
2065
- sessionProperties: {},
2066
- isMultichainOrigin: false,
2067
2242
  },
2068
- },
2069
- ],
2070
- },
2071
- };
2072
- return modifiedPermissions;
2073
- }
2074
- return newPermissions;
2075
- }, _SnapController_updatePermissions = function _SnapController_updatePermissions({ snapId, unusedPermissions = {}, newPermissions = {}, requestData, }) {
2076
- const unusedPermissionsKeys = Object.keys(unusedPermissions);
2077
- if (isNonEmptyArray(unusedPermissionsKeys)) {
2078
- this.messagingSystem.call('PermissionController:revokePermissions', {
2079
- [snapId]: unusedPermissionsKeys,
2080
- });
2243
+ ],
2244
+ },
2245
+ };
2246
+ return modifiedPermissions;
2247
+ }
2248
+ return newPermissions;
2081
2249
  }
2082
- if (isNonEmptyArray(Object.keys(newPermissions))) {
2083
- const approvedPermissions = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getPermissionsToGrant).call(this, snapId, newPermissions);
2084
- this.messagingSystem.call('PermissionController:grantPermissions', {
2085
- approvedPermissions,
2086
- subject: { origin: snapId },
2087
- requestData,
2088
- });
2250
+ /**
2251
+ * Update the permissions for a snap following an install, update or rollback.
2252
+ *
2253
+ * Grants newly requested permissions and revokes unused/revoked permissions.
2254
+ *
2255
+ * @param args - An options bag.
2256
+ * @param args.snapId - The snap ID.
2257
+ * @param args.newPermissions - New permissions to be granted.
2258
+ * @param args.unusedPermissions - Unused permissions to be revoked.
2259
+ * @param args.requestData - Optional request data from an approval.
2260
+ */
2261
+ #updatePermissions({ snapId, unusedPermissions = {}, newPermissions = {}, requestData, }) {
2262
+ const unusedPermissionsKeys = Object.keys(unusedPermissions);
2263
+ if (isNonEmptyArray(unusedPermissionsKeys)) {
2264
+ this.messagingSystem.call('PermissionController:revokePermissions', {
2265
+ [snapId]: unusedPermissionsKeys,
2266
+ });
2267
+ }
2268
+ if (isNonEmptyArray(Object.keys(newPermissions))) {
2269
+ const approvedPermissions = this.#getPermissionsToGrant(snapId, newPermissions);
2270
+ this.messagingSystem.call('PermissionController:grantPermissions', {
2271
+ approvedPermissions,
2272
+ subject: { origin: snapId },
2273
+ requestData,
2274
+ });
2275
+ }
2089
2276
  }
2090
- }, _SnapController_isValidUpdate = function _SnapController_isValidUpdate(snapId, newVersionRange) {
2091
- const existingSnap = this.getExpect(snapId);
2092
- if (satisfiesVersionRange(existingSnap.version, newVersionRange)) {
2093
- return false;
2277
+ /**
2278
+ * Checks if a snap will pass version validation checks
2279
+ * with the new version range that is requested. The first
2280
+ * check that is done is to check if the existing snap version
2281
+ * falls inside the requested range. If it does, we want to return
2282
+ * false because we do not care to create a rollback snapshot in
2283
+ * that scenario. The second check is to ensure that the current
2284
+ * snap version is not greater than all possible versions in
2285
+ * the requested version range. If it is, then we also want
2286
+ * to return false in that scenario.
2287
+ *
2288
+ * @param snapId - The snap id.
2289
+ * @param newVersionRange - The new version range being requested.
2290
+ * @returns `true` if validation checks pass and `false` if they do not.
2291
+ */
2292
+ #isValidUpdate(snapId, newVersionRange) {
2293
+ const existingSnap = this.getExpect(snapId);
2294
+ if (satisfiesVersionRange(existingSnap.version, newVersionRange)) {
2295
+ return false;
2296
+ }
2297
+ if (gtRange(existingSnap.version, newVersionRange)) {
2298
+ return false;
2299
+ }
2300
+ return true;
2094
2301
  }
2095
- if (gtRange(existingSnap.version, newVersionRange)) {
2096
- return false;
2302
+ /**
2303
+ * Call a lifecycle hook on a snap, if the snap has the
2304
+ * `endowment:lifecycle-hooks` permission. If the snap does not have the
2305
+ * permission, nothing happens.
2306
+ *
2307
+ * @param origin - The origin.
2308
+ * @param snapId - The snap ID.
2309
+ * @param handler - The lifecycle hook to call. This should be one of the
2310
+ * supported lifecycle hooks.
2311
+ * @private
2312
+ */
2313
+ async #callLifecycleHook(origin, snapId, handler) {
2314
+ const permissionName = handlerEndowments[handler];
2315
+ assert(permissionName, 'Lifecycle hook must have an endowment.');
2316
+ const hasPermission = this.messagingSystem.call('PermissionController:hasPermission', snapId, permissionName);
2317
+ if (!hasPermission) {
2318
+ return;
2319
+ }
2320
+ await this.handleRequest({
2321
+ snapId,
2322
+ handler,
2323
+ origin,
2324
+ request: {
2325
+ jsonrpc: '2.0',
2326
+ method: handler,
2327
+ },
2328
+ });
2097
2329
  }
2098
- return true;
2099
- }, _SnapController_callLifecycleHook =
2100
- /**
2101
- * Call a lifecycle hook on a snap, if the snap has the
2102
- * `endowment:lifecycle-hooks` permission. If the snap does not have the
2103
- * permission, nothing happens.
2104
- *
2105
- * @param origin - The origin.
2106
- * @param snapId - The snap ID.
2107
- * @param handler - The lifecycle hook to call. This should be one of the
2108
- * supported lifecycle hooks.
2109
- * @private
2110
- */
2111
- async function _SnapController_callLifecycleHook(origin, snapId, handler) {
2112
- const permissionName = handlerEndowments[handler];
2113
- assert(permissionName, 'Lifecycle hook must have an endowment.');
2114
- const hasPermission = this.messagingSystem.call('PermissionController:hasPermission', snapId, permissionName);
2115
- if (!hasPermission) {
2116
- return;
2117
- }
2118
- await this.handleRequest({
2119
- snapId,
2120
- handler,
2121
- origin,
2122
- request: {
2123
- jsonrpc: '2.0',
2124
- method: handler,
2125
- },
2126
- });
2127
- }, _SnapController_handleLock = function _SnapController_handleLock() {
2128
- for (const runtime of __classPrivateFieldGet(this, _SnapController_snapsRuntimeData, "f").values()) {
2129
- runtime.encryptionKey = null;
2130
- runtime.encryptionSalt = null;
2131
- runtime.state = undefined;
2330
+ /**
2331
+ * Handle the `KeyringController:lock` event.
2332
+ *
2333
+ * Currently this clears the cached encrypted state (if any) for all Snaps.
2334
+ */
2335
+ #handleLock() {
2336
+ for (const runtime of this.#snapsRuntimeData.values()) {
2337
+ runtime.encryptionKey = null;
2338
+ runtime.encryptionSalt = null;
2339
+ runtime.state = undefined;
2340
+ }
2132
2341
  }
2133
- };
2342
+ }
2134
2343
  //# sourceMappingURL=SnapController.mjs.map