@metamask/snaps-controllers 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/dist/cjs/services/ProxyPostMessageStream.js +3 -10
  3. package/dist/cjs/services/ProxyPostMessageStream.js.map +1 -1
  4. package/dist/cjs/services/node/NodeProcessExecutionService.js +13 -1
  5. package/dist/cjs/services/node/NodeProcessExecutionService.js.map +1 -1
  6. package/dist/cjs/services/node/NodeThreadExecutionService.js +14 -1
  7. package/dist/cjs/services/node/NodeThreadExecutionService.js.map +1 -1
  8. package/dist/cjs/services/offscreen/OffscreenExecutionService.js +36 -99
  9. package/dist/cjs/services/offscreen/OffscreenExecutionService.js.map +1 -1
  10. package/dist/cjs/services/proxy/ProxyExecutionService.js +110 -0
  11. package/dist/cjs/services/proxy/ProxyExecutionService.js.map +1 -0
  12. package/dist/cjs/snaps/SnapController.js +198 -93
  13. package/dist/cjs/snaps/SnapController.js.map +1 -1
  14. package/dist/cjs/snaps/endowments/enum.js +1 -0
  15. package/dist/cjs/snaps/endowments/enum.js.map +1 -1
  16. package/dist/cjs/snaps/endowments/index.js +12 -4
  17. package/dist/cjs/snaps/endowments/index.js.map +1 -1
  18. package/dist/cjs/snaps/endowments/signature-insight.js +106 -0
  19. package/dist/cjs/snaps/endowments/signature-insight.js.map +1 -0
  20. package/dist/cjs/utils.js +32 -0
  21. package/dist/cjs/utils.js.map +1 -1
  22. package/dist/esm/services/ProxyPostMessageStream.js +3 -10
  23. package/dist/esm/services/ProxyPostMessageStream.js.map +1 -1
  24. package/dist/esm/services/node/NodeProcessExecutionService.js +13 -1
  25. package/dist/esm/services/node/NodeProcessExecutionService.js.map +1 -1
  26. package/dist/esm/services/node/NodeThreadExecutionService.js +14 -1
  27. package/dist/esm/services/node/NodeThreadExecutionService.js.map +1 -1
  28. package/dist/esm/services/offscreen/OffscreenExecutionService.js +36 -99
  29. package/dist/esm/services/offscreen/OffscreenExecutionService.js.map +1 -1
  30. package/dist/esm/services/proxy/ProxyExecutionService.js +100 -0
  31. package/dist/esm/services/proxy/ProxyExecutionService.js.map +1 -0
  32. package/dist/esm/snaps/SnapController.js +200 -95
  33. package/dist/esm/snaps/SnapController.js.map +1 -1
  34. package/dist/esm/snaps/endowments/enum.js +1 -0
  35. package/dist/esm/snaps/endowments/enum.js.map +1 -1
  36. package/dist/esm/snaps/endowments/index.js +10 -4
  37. package/dist/esm/snaps/endowments/index.js.map +1 -1
  38. package/dist/esm/snaps/endowments/signature-insight.js +99 -0
  39. package/dist/esm/snaps/endowments/signature-insight.js.map +1 -0
  40. package/dist/esm/utils.js +37 -0
  41. package/dist/esm/utils.js.map +1 -1
  42. package/dist/types/services/ProxyPostMessageStream.d.ts +1 -2
  43. package/dist/types/services/offscreen/OffscreenExecutionService.d.ts +5 -22
  44. package/dist/types/services/proxy/ProxyExecutionService.d.ts +39 -0
  45. package/dist/types/snaps/SnapController.d.ts +31 -3
  46. package/dist/types/snaps/endowments/enum.d.ts +1 -0
  47. package/dist/types/snaps/endowments/index.d.ts +12 -0
  48. package/dist/types/snaps/endowments/signature-insight.d.ts +39 -0
  49. package/dist/types/utils.d.ts +99 -0
  50. package/package.json +8 -8
@@ -66,13 +66,13 @@ import { SubjectType } from '@metamask/permission-controller';
66
66
  import { rpcErrors } from '@metamask/rpc-errors';
67
67
  import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/snaps-rpc-methods';
68
68
  import { AuxiliaryFileEncoding, getErrorMessage } from '@metamask/snaps-sdk';
69
- import { validateComponentLinks, assertIsSnapManifest, assertIsValidSnapId, DEFAULT_ENDOWMENTS, DEFAULT_REQUESTED_SNAP_VERSION, encodeAuxiliaryFile, HandlerType, isOriginAllowed, logError, normalizeRelative, OnTransactionResponseStruct, resolveVersionRange, SnapCaveatType, SnapStatus, SnapStatusEvents, validateFetchedSnap, unwrapError, OnHomePageResponseStruct, getValidatedLocalizationFiles, encodeBase64 } from '@metamask/snaps-utils';
69
+ import { validateComponentLinks, assertIsSnapManifest, assertIsValidSnapId, DEFAULT_ENDOWMENTS, DEFAULT_REQUESTED_SNAP_VERSION, encodeAuxiliaryFile, HandlerType, isOriginAllowed, logError, normalizeRelative, OnTransactionResponseStruct, OnSignatureResponseStruct, resolveVersionRange, SnapCaveatType, SnapStatus, SnapStatusEvents, unwrapError, OnHomePageResponseStruct, getValidatedLocalizationFiles, VirtualFile, NpmSnapFileNames } from '@metamask/snaps-utils';
70
70
  import { assert, assertIsJsonRpcRequest, assertStruct, Duration, gtRange, gtVersion, hasProperty, inMilliseconds, isNonEmptyArray, isValidSemVerRange, satisfiesVersionRange, timeSince } from '@metamask/utils';
71
71
  import { createMachine, interpret } from '@xstate/fsm';
72
72
  import { nanoid } from 'nanoid';
73
73
  import { forceStrict, validateMachine } from '../fsm';
74
74
  import { log } from '../logging';
75
- import { getSnapFiles, hasTimedOut, setDiff, withTimeout } from '../utils';
75
+ import { fetchSnap, hasTimedOut, setDiff, withTimeout } from '../utils';
76
76
  import { handlerEndowments, SnapEndowments } from './endowments';
77
77
  import { getKeyringCaveatOrigins } from './endowments/keyring';
78
78
  import { getRpcCaveatOrigins } from './endowments/rpc';
@@ -125,7 +125,7 @@ var _closeAllConnections = /*#__PURE__*/ new WeakMap(), _dynamicPermissions = /*
125
125
  _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
126
126
  * Constructor helper for registering the controller's messaging system
127
127
  * actions.
128
- */ _registerMessageHandlers = /*#__PURE__*/ new WeakSet(), _pollForLastRequestStatus = /*#__PURE__*/ new WeakSet(), _blockSnap = /*#__PURE__*/ new WeakSet(), /**
128
+ */ _registerMessageHandlers = /*#__PURE__*/ new WeakSet(), _handlePreinstalledSnaps = /*#__PURE__*/ new WeakSet(), _pollForLastRequestStatus = /*#__PURE__*/ new WeakSet(), _blockSnap = /*#__PURE__*/ new WeakSet(), /**
129
129
  * Unblocks a snap so that it can be enabled and started again. Emits
130
130
  * {@link SnapUnblocked}. Does nothing if the snap is not installed or already
131
131
  * unblocked.
@@ -142,7 +142,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
142
142
  *
143
143
  * @param snapId - The id of the snap to transition.
144
144
  * @param event - The event enum to use to transition.
145
- */ _transition = /*#__PURE__*/ new WeakSet(), _terminateSnap = /*#__PURE__*/ new WeakSet(), /**
145
+ */ _transition = /*#__PURE__*/ new WeakSet(), _terminateSnap = /*#__PURE__*/ new WeakSet(), _handleInitialConnections = /*#__PURE__*/ new WeakSet(), _addSnapToSubject = /*#__PURE__*/ new WeakSet(), /**
146
146
  * Removes a snap's permission (caveat) from all subjects.
147
147
  *
148
148
  * @param snapId - The id of the Snap.
@@ -163,7 +163,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
163
163
  *
164
164
  * @param args - The add snap args.
165
165
  * @returns The resulting snap object.
166
- */ _set = /*#__PURE__*/ new WeakSet(), _fetchSnap = /*#__PURE__*/ new WeakSet(), _validateSnapPermissions = /*#__PURE__*/ new WeakSet(), /**
166
+ */ _set = /*#__PURE__*/ new WeakSet(), _validateSnapPermissions = /*#__PURE__*/ new WeakSet(), /**
167
167
  * Gets the RPC message handler for the given snap.
168
168
  *
169
169
  * @param snapId - The id of the Snap whose message handler to get.
@@ -181,6 +181,16 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
181
181
  * @throws {@link Error}. If the snap exists before creation or if creation fails.
182
182
  * @returns A `RollbackSnapshot`.
183
183
  */ _createRollbackSnapshot = /*#__PURE__*/ new WeakSet(), _rollbackSnap = /*#__PURE__*/ new WeakSet(), _rollbackSnaps = /*#__PURE__*/ new WeakSet(), _getRuntime = /*#__PURE__*/ new WeakSet(), _getRuntimeExpect = /*#__PURE__*/ new WeakSet(), _setupRuntime = /*#__PURE__*/ new WeakSet(), _calculatePermissionsChange = /*#__PURE__*/ new WeakSet(), /**
184
+ * Updates the permissions for a snap following an install, update or rollback.
185
+ *
186
+ * Grants newly requested permissions and revokes unused/revoked permissions.
187
+ *
188
+ * @param args - An options bag.
189
+ * @param args.snapId - The snap ID.
190
+ * @param args.newPermissions - New permissions to be granted.
191
+ * @param args.unusedPermissions - Unused permissions to be revoked.
192
+ * @param args.requestData - Optional request data from an approval.
193
+ */ _updatePermissions = /*#__PURE__*/ new WeakSet(), /**
184
194
  * Checks if a snap will pass version validation checks
185
195
  * with the new version range that is requested. The first
186
196
  * check that is done is to check if the existing snap version
@@ -471,6 +481,10 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
471
481
  if (!Array.isArray(snapIds)) {
472
482
  throw new Error('Expected array of snap ids.');
473
483
  }
484
+ snapIds.forEach((snapId)=>{
485
+ const snap = this.getExpect(snapId);
486
+ assert(snap.removable !== false, `${snapId} is not removable.`);
487
+ });
474
488
  await Promise.all(snapIds.map(async (snapId)=>{
475
489
  const snap = this.getExpect(snapId);
476
490
  const truncated = this.getTruncatedExpect(snapId);
@@ -664,6 +678,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
664
678
  snapId,
665
679
  type: SNAP_APPROVAL_INSTALL
666
680
  });
681
+ this.messagingSystem.publish('SnapController:snapInstallStarted', snapId, origin, false);
667
682
  // Existing snaps must be stopped before overwriting
668
683
  if (existingSnap && this.isRunning(snapId)) {
669
684
  await this.stopSnap(snapId, SnapStatusEvents.Stop);
@@ -697,11 +712,13 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
697
712
  return truncated;
698
713
  } catch (error) {
699
714
  logError(`Error when adding ${snapId}.`, error);
715
+ const errorString = error instanceof Error ? error.message : error.toString();
700
716
  _class_private_method_get(this, _updateApproval, updateApproval).call(this, pendingApproval.id, {
701
717
  loading: false,
702
718
  type: SNAP_APPROVAL_INSTALL,
703
- error: error instanceof Error ? error.message : error.toString()
719
+ error: errorString
704
720
  });
721
+ this.messagingSystem.publish('SnapController:snapInstallFailed', snapId, origin, false, errorString);
705
722
  throw error;
706
723
  }
707
724
  }
@@ -733,9 +750,11 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
733
750
  type: SNAP_APPROVAL_UPDATE
734
751
  });
735
752
  try {
753
+ this.messagingSystem.publish('SnapController:snapInstallStarted', snapId, origin, true);
736
754
  const snap = this.getExpect(snapId);
737
- const newSnap = await _class_private_method_get(this, _fetchSnap, fetchSnap).call(this, snapId, location);
738
- const { sourceCode: sourceCodeFile, manifest: manifestFile } = newSnap.files;
755
+ const oldManifest = snap.manifest;
756
+ const newSnap = await fetchSnap(snapId, location);
757
+ const { sourceCode: sourceCodeFile, manifest: manifestFile } = newSnap;
739
758
  const manifest = manifestFile.result;
740
759
  const newVersion = manifest.version;
741
760
  if (!gtVersion(newVersion, snap.version)) {
@@ -772,28 +791,22 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
772
791
  _class_private_method_get(this, _set, set).call(this, {
773
792
  origin,
774
793
  id: snapId,
775
- files: newSnap.files,
794
+ files: newSnap,
776
795
  isUpdate: true
777
796
  });
778
- const unusedPermissionsKeys = Object.keys(unusedPermissions);
779
- if (isNonEmptyArray(unusedPermissionsKeys)) {
780
- this.messagingSystem.call('PermissionController:revokePermissions', {
781
- [snapId]: unusedPermissionsKeys
782
- });
783
- }
784
- if (isNonEmptyArray(Object.keys(approvedNewPermissions))) {
785
- this.messagingSystem.call('PermissionController:grantPermissions', {
786
- approvedPermissions: approvedNewPermissions,
787
- subject: {
788
- origin: snapId
789
- },
790
- requestData
791
- });
797
+ _class_private_method_get(this, _updatePermissions, updatePermissions).call(this, {
798
+ snapId,
799
+ unusedPermissions,
800
+ newPermissions: approvedNewPermissions,
801
+ requestData
802
+ });
803
+ if (manifest.initialConnections) {
804
+ _class_private_method_get(this, _handleInitialConnections, handleInitialConnections).call(this, snapId, oldManifest.initialConnections ?? null, manifest.initialConnections);
792
805
  }
793
806
  const rollbackSnapshot = _class_private_method_get(this, _getRollbackSnapshot, getRollbackSnapshot).call(this, snapId);
794
807
  if (rollbackSnapshot !== undefined) {
795
808
  rollbackSnapshot.permissions.revoked = unusedPermissions;
796
- rollbackSnapshot.permissions.granted = Object.keys(approvedNewPermissions);
809
+ rollbackSnapshot.permissions.granted = approvedNewPermissions;
797
810
  rollbackSnapshot.permissions.requestData = requestData;
798
811
  }
799
812
  const sourceCode = sourceCodeFile.toString();
@@ -817,11 +830,13 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
817
830
  return truncatedSnap;
818
831
  } catch (error) {
819
832
  logError(`Error when updating ${snapId},`, error);
833
+ const errorString = error instanceof Error ? error.message : error.toString();
820
834
  _class_private_method_get(this, _updateApproval, updateApproval).call(this, pendingApproval.id, {
821
835
  loading: false,
822
- error: error instanceof Error ? error.message : error.toString(),
836
+ error: errorString,
823
837
  type: SNAP_APPROVAL_UPDATE
824
838
  });
839
+ this.messagingSystem.publish('SnapController:snapInstallFailed', snapId, origin, true, errorString);
825
840
  throw error;
826
841
  }
827
842
  }
@@ -856,14 +871,13 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
856
871
  permissions: processedPermissions
857
872
  });
858
873
  const { permissions: approvedPermissions, ...requestData } = await pendingApproval.promise;
859
- if (isNonEmptyArray(Object.keys(approvedPermissions))) {
860
- this.messagingSystem.call('PermissionController:grantPermissions', {
861
- approvedPermissions,
862
- subject: {
863
- origin: snapId
864
- },
865
- requestData
866
- });
874
+ _class_private_method_get(this, _updatePermissions, updatePermissions).call(this, {
875
+ snapId,
876
+ newPermissions: approvedPermissions,
877
+ requestData
878
+ });
879
+ if (snap.manifest.initialConnections) {
880
+ _class_private_method_get(this, _handleInitialConnections, handleInitialConnections).call(this, snapId, null, snap.manifest.initialConnections);
867
881
  }
868
882
  } finally{
869
883
  const runtime = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
@@ -925,7 +939,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
925
939
  }
926
940
  constructor({ closeAllConnections, messenger, state, dynamicPermissions = [
927
941
  'eth_accounts'
928
- ], environmentEndowmentPermissions = [], excludedPermissions = {}, idleTimeCheckInterval = inMilliseconds(5, Duration.Second), maxIdleTime = inMilliseconds(30, Duration.Second), maxRequestTime = inMilliseconds(60, Duration.Second), fetchFunction = globalThis.fetch.bind(globalThis), featureFlags = {}, detectSnapLocation: detectSnapLocationFunction = detectSnapLocation }){
942
+ ], environmentEndowmentPermissions = [], excludedPermissions = {}, idleTimeCheckInterval = inMilliseconds(5, Duration.Second), maxIdleTime = inMilliseconds(30, Duration.Second), maxRequestTime = inMilliseconds(60, Duration.Second), fetchFunction = globalThis.fetch.bind(globalThis), featureFlags = {}, detectSnapLocation: detectSnapLocationFunction = detectSnapLocation, preinstalledSnaps }){
929
943
  super({
930
944
  messenger,
931
945
  metadata: {
@@ -963,6 +977,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
963
977
  });
964
978
  _class_private_method_init(this, _initializeStateMachine);
965
979
  _class_private_method_init(this, _registerMessageHandlers);
980
+ _class_private_method_init(this, _handlePreinstalledSnaps);
966
981
  _class_private_method_init(this, _pollForLastRequestStatus);
967
982
  /**
968
983
  * Blocks an installed snap and prevents it from being started again. Emits
@@ -980,6 +995,8 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
980
995
  *
981
996
  * @param snapId - The snap to terminate.
982
997
  */ _class_private_method_init(this, _terminateSnap);
998
+ _class_private_method_init(this, _handleInitialConnections);
999
+ _class_private_method_init(this, _addSnapToSubject);
983
1000
  _class_private_method_init(this, _removeSnapFromSubjects);
984
1001
  _class_private_method_init(this, _revokeAllSnapPermissions);
985
1002
  _class_private_method_init(this, _createApproval);
@@ -1007,13 +1024,6 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
1007
1024
  * @returns An array of the names of the endowments.
1008
1025
  */ _class_private_method_init(this, _getEndowments);
1009
1026
  _class_private_method_init(this, _set);
1010
- /**
1011
- * Fetches the manifest and source code of a snap.
1012
- *
1013
- * @param snapId - The id of the Snap.
1014
- * @param location - Source from which snap will be fetched.
1015
- * @returns A tuple of the Snap manifest object and the Snap source code.
1016
- */ _class_private_method_init(this, _fetchSnap);
1017
1027
  _class_private_method_init(this, _validateSnapPermissions);
1018
1028
  _class_private_method_init(this, _getRpcRequestHandler);
1019
1029
  _class_private_method_init(this, _triggerPhishingListUpdate);
@@ -1058,6 +1068,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
1058
1068
  _class_private_method_init(this, _getRuntimeExpect);
1059
1069
  _class_private_method_init(this, _setupRuntime);
1060
1070
  _class_private_method_init(this, _calculatePermissionsChange);
1071
+ _class_private_method_init(this, _updatePermissions);
1061
1072
  _class_private_method_init(this, _isValidUpdate);
1062
1073
  /**
1063
1074
  * Call a lifecycle hook on a snap, if the snap has the
@@ -1154,7 +1165,10 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
1154
1165
  });
1155
1166
  _class_private_method_get(this, _initializeStateMachine, initializeStateMachine).call(this);
1156
1167
  _class_private_method_get(this, _registerMessageHandlers, registerMessageHandlers).call(this);
1157
- Object.values(state?.snaps ?? {}).forEach((snap)=>_class_private_method_get(this, _setupRuntime, setupRuntime).call(this, snap.id));
1168
+ if (preinstalledSnaps) {
1169
+ _class_private_method_get(this, _handlePreinstalledSnaps, handlePreinstalledSnaps).call(this, preinstalledSnaps);
1170
+ }
1171
+ Object.values(this.state?.snaps ?? {}).forEach((snap)=>_class_private_method_get(this, _setupRuntime, setupRuntime).call(this, snap.id));
1158
1172
  }
1159
1173
  }
1160
1174
  function initializeStateMachine() {
@@ -1231,6 +1245,63 @@ function registerMessageHandlers() {
1231
1245
  this.messagingSystem.registerActionHandler(`${controllerName}:revokeDynamicPermissions`, (...args)=>this.revokeDynamicSnapPermissions(...args));
1232
1246
  this.messagingSystem.registerActionHandler(`${controllerName}:getFile`, async (...args)=>this.getSnapFile(...args));
1233
1247
  }
1248
+ function handlePreinstalledSnaps(preinstalledSnaps) {
1249
+ for (const { snapId, manifest, files, removable } of preinstalledSnaps){
1250
+ const existingSnap = this.get(snapId);
1251
+ const isAlreadyInstalled = existingSnap !== undefined;
1252
+ const isUpdate = isAlreadyInstalled && gtVersion(manifest.version, existingSnap.version);
1253
+ // Disallow downgrades and overwriting non preinstalled snaps
1254
+ if (isAlreadyInstalled && (!isUpdate || existingSnap.preinstalled !== true)) {
1255
+ continue;
1256
+ }
1257
+ const manifestFile = new VirtualFile({
1258
+ path: NpmSnapFileNames.Manifest,
1259
+ value: JSON.stringify(manifest),
1260
+ result: manifest
1261
+ });
1262
+ const virtualFiles = files.map(({ path, value })=>new VirtualFile({
1263
+ value,
1264
+ path
1265
+ }));
1266
+ const { filePath, iconPath } = manifest.source.location.npm;
1267
+ const sourceCode = virtualFiles.find((file)=>file.path === filePath);
1268
+ const svgIcon = iconPath ? virtualFiles.find((file)=>file.path === iconPath) : undefined;
1269
+ assert(sourceCode, 'Source code not provided for preinstalled snap.');
1270
+ assert(!iconPath || iconPath && svgIcon, 'Icon not provided for preinstalled snap.');
1271
+ assert(manifest.source.files === undefined, 'Auxiliary files are not currently supported for preinstalled snaps.');
1272
+ const localizationFiles = manifest.source.locales?.map((path)=>virtualFiles.find((file)=>file.path === path)) ?? [];
1273
+ const validatedLocalizationFiles = getValidatedLocalizationFiles(localizationFiles.filter(Boolean));
1274
+ assert(localizationFiles.length === validatedLocalizationFiles.length, 'Missing localization files for preinstalled snap.');
1275
+ const filesObject = {
1276
+ manifest: manifestFile,
1277
+ sourceCode,
1278
+ svgIcon,
1279
+ auxiliaryFiles: [],
1280
+ localizationFiles: validatedLocalizationFiles
1281
+ };
1282
+ // Add snap to the SnapController state
1283
+ _class_private_method_get(this, _set, set).call(this, {
1284
+ id: snapId,
1285
+ origin: 'metamask',
1286
+ files: filesObject,
1287
+ removable,
1288
+ preinstalled: true
1289
+ });
1290
+ // Setup permissions
1291
+ const processedPermissions = processSnapPermissions(manifest.initialPermissions);
1292
+ _class_private_method_get(this, _validateSnapPermissions, validateSnapPermissions).call(this, processedPermissions);
1293
+ const { newPermissions, unusedPermissions } = _class_private_method_get(this, _calculatePermissionsChange, calculatePermissionsChange).call(this, snapId, processedPermissions);
1294
+ _class_private_method_get(this, _updatePermissions, updatePermissions).call(this, {
1295
+ snapId,
1296
+ newPermissions,
1297
+ unusedPermissions
1298
+ });
1299
+ // Set status
1300
+ this.update((state)=>{
1301
+ state.snaps[snapId].status = SnapStatus.Stopped;
1302
+ });
1303
+ }
1304
+ }
1234
1305
  function pollForLastRequestStatus() {
1235
1306
  _class_private_field_set(this, _timeoutForLastRequestStatus, setTimeout(()=>{
1236
1307
  _class_private_method_get(this, _stopSnapsLastRequestPastMax, stopSnapsLastRequestPastMax).call(this).catch((error)=>{
@@ -1293,6 +1364,52 @@ async function terminateSnap(snapId) {
1293
1364
  await this.messagingSystem.call('ExecutionService:terminateSnap', snapId);
1294
1365
  this.messagingSystem.publish('SnapController:snapTerminated', this.getTruncatedExpect(snapId));
1295
1366
  }
1367
+ function handleInitialConnections(snapId, previousInitialConnections, initialConnections) {
1368
+ if (previousInitialConnections) {
1369
+ const revokedInitialConnections = setDiff(previousInitialConnections, initialConnections);
1370
+ for (const origin of Object.keys(revokedInitialConnections)){
1371
+ this.removeSnapFromSubject(origin, snapId);
1372
+ }
1373
+ }
1374
+ for (const origin of Object.keys(initialConnections)){
1375
+ _class_private_method_get(this, _addSnapToSubject, addSnapToSubject).call(this, origin, snapId);
1376
+ }
1377
+ }
1378
+ function addSnapToSubject(origin, snapId) {
1379
+ const subjectPermissions = this.messagingSystem.call('PermissionController:getPermissions', origin);
1380
+ const existingCaveat = subjectPermissions?.[WALLET_SNAP_PERMISSION_KEY]?.caveats?.find((caveat)=>caveat.type === SnapCaveatType.SnapIds);
1381
+ const subjectHasSnap = Boolean((existingCaveat?.value)?.[snapId]);
1382
+ // If the subject is already connected to the snap, this is a no-op.
1383
+ if (subjectHasSnap) {
1384
+ return;
1385
+ }
1386
+ // If an existing caveat exists, we add the snap to that.
1387
+ if (existingCaveat) {
1388
+ this.messagingSystem.call('PermissionController:updateCaveat', origin, WALLET_SNAP_PERMISSION_KEY, SnapCaveatType.SnapIds, {
1389
+ ...existingCaveat,
1390
+ [snapId]: {}
1391
+ });
1392
+ return;
1393
+ }
1394
+ const approvedPermissions = {
1395
+ [WALLET_SNAP_PERMISSION_KEY]: {
1396
+ caveats: [
1397
+ {
1398
+ type: SnapCaveatType.SnapIds,
1399
+ value: {
1400
+ [snapId]: {}
1401
+ }
1402
+ }
1403
+ ]
1404
+ }
1405
+ };
1406
+ this.messagingSystem.call('PermissionController:grantPermissions', {
1407
+ approvedPermissions,
1408
+ subject: {
1409
+ origin
1410
+ }
1411
+ });
1412
+ }
1296
1413
  function removeSnapFromSubjects(snapId) {
1297
1414
  const subjects = this.messagingSystem.call('PermissionController:getSubjectNames');
1298
1415
  for (const subject of subjects){
@@ -1350,8 +1467,8 @@ async function add(args) {
1350
1467
  // If fetching and setting the snap succeeds, this property will be set
1351
1468
  // to null in the authorize() method.
1352
1469
  runtime.installPromise = (async ()=>{
1353
- const fetchedSnap = await _class_private_method_get(this, _fetchSnap, fetchSnap).call(this, snapId, location);
1354
- const manifest = fetchedSnap.files.manifest.result;
1470
+ const fetchedSnap = await fetchSnap(snapId, location);
1471
+ const manifest = fetchedSnap.manifest.result;
1355
1472
  if (!satisfiesVersionRange(manifest.version, versionRange)) {
1356
1473
  throw new Error(`Version mismatch. Manifest for "${snapId}" specifies version "${manifest.version}" which doesn't satisfy requested version range "${versionRange}".`);
1357
1474
  }
@@ -1361,7 +1478,7 @@ async function add(args) {
1361
1478
  });
1362
1479
  return _class_private_method_get(this, _set, set).call(this, {
1363
1480
  ...args,
1364
- ...fetchedSnap,
1481
+ files: fetchedSnap,
1365
1482
  id: snapId
1366
1483
  });
1367
1484
  })();
@@ -1424,7 +1541,7 @@ async function getEndowments(snapId) {
1424
1541
  return dedupedEndowments;
1425
1542
  }
1426
1543
  function set(args) {
1427
- const { id: snapId, origin, files, isUpdate = false } = args;
1544
+ const { id: snapId, origin, files, isUpdate = false, removable, preinstalled } = args;
1428
1545
  const { manifest, sourceCode: sourceCodeFile, svgIcon, auxiliaryFiles: rawAuxiliaryFiles, localizationFiles } = files;
1429
1546
  assertIsSnapManifest(manifest.result);
1430
1547
  const { version, proposedName } = manifest.result;
@@ -1455,6 +1572,8 @@ function set(args) {
1455
1572
  // previous state.
1456
1573
  blocked: false,
1457
1574
  enabled: true,
1575
+ removable,
1576
+ preinstalled,
1458
1577
  id: snapId,
1459
1578
  initialPermissions: manifest.result.initialPermissions,
1460
1579
  manifest: manifest.result,
@@ -1491,36 +1610,6 @@ function set(args) {
1491
1610
  sourceCode
1492
1611
  };
1493
1612
  }
1494
- async function fetchSnap(snapId, location) {
1495
- try {
1496
- const manifest = await location.manifest();
1497
- const sourceCode = await location.fetch(manifest.result.source.location.npm.filePath);
1498
- const { iconPath } = manifest.result.source.location.npm;
1499
- const svgIcon = iconPath ? await location.fetch(iconPath) : undefined;
1500
- const auxiliaryFiles = await getSnapFiles(location, manifest.result.source.files);
1501
- await Promise.all(auxiliaryFiles.map(async (file)=>{
1502
- // This should still be safe
1503
- // eslint-disable-next-line require-atomic-updates
1504
- file.data.base64 = await encodeBase64(file);
1505
- }));
1506
- const localizationFiles = await getSnapFiles(location, manifest.result.source.locales);
1507
- const validatedLocalizationFiles = getValidatedLocalizationFiles(localizationFiles);
1508
- const files = {
1509
- manifest,
1510
- sourceCode,
1511
- svgIcon,
1512
- auxiliaryFiles,
1513
- localizationFiles: validatedLocalizationFiles
1514
- };
1515
- await validateFetchedSnap(files);
1516
- return {
1517
- files,
1518
- location
1519
- };
1520
- } catch (error) {
1521
- throw new Error(`Failed to fetch snap "${snapId}": ${getErrorMessage(error)}.`);
1522
- }
1523
- }
1524
1613
  function validateSnapPermissions(processedPermissions) {
1525
1614
  const permissionKeys = Object.keys(processedPermissions);
1526
1615
  const handlerPermissions = Array.from(new Set(Object.values(handlerEndowments)));
@@ -1613,6 +1702,17 @@ async function assertSnapRpcRequestResult(handlerType, result) {
1613
1702
  validateComponentLinks(result.content, _class_private_method_get(this, _checkPhishingList, checkPhishingList).bind(this));
1614
1703
  break;
1615
1704
  }
1705
+ case HandlerType.OnSignature:
1706
+ {
1707
+ assertStruct(result, OnSignatureResponseStruct);
1708
+ // Null is an allowed return value here
1709
+ if (result === null) {
1710
+ return;
1711
+ }
1712
+ await _class_private_method_get(this, _triggerPhishingListUpdate, triggerPhishingListUpdate).call(this);
1713
+ validateComponentLinks(result.content, _class_private_method_get(this, _checkPhishingList, checkPhishingList).bind(this));
1714
+ break;
1715
+ }
1616
1716
  case HandlerType.OnHomePage:
1617
1717
  assertStruct(result, OnHomePageResponseStruct);
1618
1718
  await _class_private_method_get(this, _triggerPhishingListUpdate, triggerPhishingListUpdate).call(this);
@@ -1651,11 +1751,7 @@ function createRollbackSnapshot(snapId) {
1651
1751
  assert(_class_private_field_get(this, _rollbackSnapshots).get(snapId) === undefined, new Error(`Snap "${snapId}" rollback snapshot already exists.`));
1652
1752
  _class_private_field_get(this, _rollbackSnapshots).set(snapId, {
1653
1753
  statePatches: [],
1654
- permissions: {
1655
- revoked: null,
1656
- granted: [],
1657
- requestData: null
1658
- },
1754
+ permissions: {},
1659
1755
  newVersion: ''
1660
1756
  });
1661
1757
  const newRollbackSnapshot = _class_private_field_get(this, _rollbackSnapshots).get(snapId);
@@ -1683,20 +1779,12 @@ async function rollbackSnap(snapId) {
1683
1779
  state.snaps[snapId].status = SnapStatus.Stopped;
1684
1780
  });
1685
1781
  }
1686
- if (permissions.revoked && Object.keys(permissions.revoked).length) {
1687
- this.messagingSystem.call('PermissionController:grantPermissions', {
1688
- approvedPermissions: permissions.revoked,
1689
- subject: {
1690
- origin: snapId
1691
- },
1692
- requestData: permissions.requestData
1693
- });
1694
- }
1695
- if (permissions.granted?.length) {
1696
- this.messagingSystem.call('PermissionController:revokePermissions', {
1697
- [snapId]: permissions.granted
1698
- });
1699
- }
1782
+ _class_private_method_get(this, _updatePermissions, updatePermissions).call(this, {
1783
+ snapId,
1784
+ unusedPermissions: permissions.granted,
1785
+ newPermissions: permissions.revoked,
1786
+ requestData: permissions.requestData
1787
+ });
1700
1788
  const truncatedSnap = this.getTruncatedExpect(snapId);
1701
1789
  this.messagingSystem.publish('SnapController:snapRolledback', truncatedSnap, rollbackSnapshot.newVersion);
1702
1790
  _class_private_field_get(this, _rollbackSnapshots).delete(snapId);
@@ -1752,6 +1840,23 @@ function calculatePermissionsChange(snapId, desiredPermissionsSet) {
1752
1840
  approvedPermissions
1753
1841
  };
1754
1842
  }
1843
+ function updatePermissions({ snapId, unusedPermissions = {}, newPermissions = {}, requestData }) {
1844
+ const unusedPermissionsKeys = Object.keys(unusedPermissions);
1845
+ if (isNonEmptyArray(unusedPermissionsKeys)) {
1846
+ this.messagingSystem.call('PermissionController:revokePermissions', {
1847
+ [snapId]: unusedPermissionsKeys
1848
+ });
1849
+ }
1850
+ if (isNonEmptyArray(Object.keys(newPermissions))) {
1851
+ this.messagingSystem.call('PermissionController:grantPermissions', {
1852
+ approvedPermissions: newPermissions,
1853
+ subject: {
1854
+ origin: snapId
1855
+ },
1856
+ requestData
1857
+ });
1858
+ }
1859
+ }
1755
1860
  function isValidUpdate(snapId, newVersionRange) {
1756
1861
  const existingSnap = this.getExpect(snapId);
1757
1862
  if (satisfiesVersionRange(existingSnap.version, newVersionRange)) {