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