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