@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
@@ -151,7 +151,7 @@ var _closeAllConnections = /*#__PURE__*/ new WeakMap(), _dynamicPermissions = /*
151
151
  _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
152
152
  * Constructor helper for registering the controller's messaging system
153
153
  * actions.
154
- */ _registerMessageHandlers = /*#__PURE__*/ new WeakSet(), _pollForLastRequestStatus = /*#__PURE__*/ new WeakSet(), _blockSnap = /*#__PURE__*/ new WeakSet(), /**
154
+ */ _registerMessageHandlers = /*#__PURE__*/ new WeakSet(), _handlePreinstalledSnaps = /*#__PURE__*/ new WeakSet(), _pollForLastRequestStatus = /*#__PURE__*/ new WeakSet(), _blockSnap = /*#__PURE__*/ new WeakSet(), /**
155
155
  * Unblocks a snap so that it can be enabled and started again. Emits
156
156
  * {@link SnapUnblocked}. Does nothing if the snap is not installed or already
157
157
  * unblocked.
@@ -168,7 +168,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
168
168
  *
169
169
  * @param snapId - The id of the snap to transition.
170
170
  * @param event - The event enum to use to transition.
171
- */ _transition = /*#__PURE__*/ new WeakSet(), _terminateSnap = /*#__PURE__*/ new WeakSet(), /**
171
+ */ _transition = /*#__PURE__*/ new WeakSet(), _terminateSnap = /*#__PURE__*/ new WeakSet(), _handleInitialConnections = /*#__PURE__*/ new WeakSet(), _addSnapToSubject = /*#__PURE__*/ new WeakSet(), /**
172
172
  * Removes a snap's permission (caveat) from all subjects.
173
173
  *
174
174
  * @param snapId - The id of the Snap.
@@ -189,7 +189,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
189
189
  *
190
190
  * @param args - The add snap args.
191
191
  * @returns The resulting snap object.
192
- */ _set = /*#__PURE__*/ new WeakSet(), _fetchSnap = /*#__PURE__*/ new WeakSet(), _validateSnapPermissions = /*#__PURE__*/ new WeakSet(), /**
192
+ */ _set = /*#__PURE__*/ new WeakSet(), _validateSnapPermissions = /*#__PURE__*/ new WeakSet(), /**
193
193
  * Gets the RPC message handler for the given snap.
194
194
  *
195
195
  * @param snapId - The id of the Snap whose message handler to get.
@@ -207,6 +207,16 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
207
207
  * @throws {@link Error}. If the snap exists before creation or if creation fails.
208
208
  * @returns A `RollbackSnapshot`.
209
209
  */ _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(), /**
210
+ * Updates the permissions for a snap following an install, update or rollback.
211
+ *
212
+ * Grants newly requested permissions and revokes unused/revoked permissions.
213
+ *
214
+ * @param args - An options bag.
215
+ * @param args.snapId - The snap ID.
216
+ * @param args.newPermissions - New permissions to be granted.
217
+ * @param args.unusedPermissions - Unused permissions to be revoked.
218
+ * @param args.requestData - Optional request data from an approval.
219
+ */ _updatePermissions = /*#__PURE__*/ new WeakSet(), /**
210
220
  * Checks if a snap will pass version validation checks
211
221
  * with the new version range that is requested. The first
212
222
  * check that is done is to check if the existing snap version
@@ -492,6 +502,10 @@ class SnapController extends _basecontroller.BaseController {
492
502
  if (!Array.isArray(snapIds)) {
493
503
  throw new Error('Expected array of snap ids.');
494
504
  }
505
+ snapIds.forEach((snapId)=>{
506
+ const snap = this.getExpect(snapId);
507
+ (0, _utils.assert)(snap.removable !== false, `${snapId} is not removable.`);
508
+ });
495
509
  await Promise.all(snapIds.map(async (snapId)=>{
496
510
  const snap = this.getExpect(snapId);
497
511
  const truncated = this.getTruncatedExpect(snapId);
@@ -685,6 +699,7 @@ class SnapController extends _basecontroller.BaseController {
685
699
  snapId,
686
700
  type: SNAP_APPROVAL_INSTALL
687
701
  });
702
+ this.messagingSystem.publish('SnapController:snapInstallStarted', snapId, origin, false);
688
703
  // Existing snaps must be stopped before overwriting
689
704
  if (existingSnap && this.isRunning(snapId)) {
690
705
  await this.stopSnap(snapId, _snapsutils.SnapStatusEvents.Stop);
@@ -718,11 +733,13 @@ class SnapController extends _basecontroller.BaseController {
718
733
  return truncated;
719
734
  } catch (error) {
720
735
  (0, _snapsutils.logError)(`Error when adding ${snapId}.`, error);
736
+ const errorString = error instanceof Error ? error.message : error.toString();
721
737
  _class_private_method_get(this, _updateApproval, updateApproval).call(this, pendingApproval.id, {
722
738
  loading: false,
723
739
  type: SNAP_APPROVAL_INSTALL,
724
- error: error instanceof Error ? error.message : error.toString()
740
+ error: errorString
725
741
  });
742
+ this.messagingSystem.publish('SnapController:snapInstallFailed', snapId, origin, false, errorString);
726
743
  throw error;
727
744
  }
728
745
  }
@@ -754,9 +771,11 @@ class SnapController extends _basecontroller.BaseController {
754
771
  type: SNAP_APPROVAL_UPDATE
755
772
  });
756
773
  try {
774
+ this.messagingSystem.publish('SnapController:snapInstallStarted', snapId, origin, true);
757
775
  const snap = this.getExpect(snapId);
758
- const newSnap = await _class_private_method_get(this, _fetchSnap, fetchSnap).call(this, snapId, location);
759
- const { sourceCode: sourceCodeFile, manifest: manifestFile } = newSnap.files;
776
+ const oldManifest = snap.manifest;
777
+ const newSnap = await (0, _utils1.fetchSnap)(snapId, location);
778
+ const { sourceCode: sourceCodeFile, manifest: manifestFile } = newSnap;
760
779
  const manifest = manifestFile.result;
761
780
  const newVersion = manifest.version;
762
781
  if (!(0, _utils.gtVersion)(newVersion, snap.version)) {
@@ -793,28 +812,22 @@ class SnapController extends _basecontroller.BaseController {
793
812
  _class_private_method_get(this, _set, set).call(this, {
794
813
  origin,
795
814
  id: snapId,
796
- files: newSnap.files,
815
+ files: newSnap,
797
816
  isUpdate: true
798
817
  });
799
- const unusedPermissionsKeys = Object.keys(unusedPermissions);
800
- if ((0, _utils.isNonEmptyArray)(unusedPermissionsKeys)) {
801
- this.messagingSystem.call('PermissionController:revokePermissions', {
802
- [snapId]: unusedPermissionsKeys
803
- });
804
- }
805
- if ((0, _utils.isNonEmptyArray)(Object.keys(approvedNewPermissions))) {
806
- this.messagingSystem.call('PermissionController:grantPermissions', {
807
- approvedPermissions: approvedNewPermissions,
808
- subject: {
809
- origin: snapId
810
- },
811
- requestData
812
- });
818
+ _class_private_method_get(this, _updatePermissions, updatePermissions).call(this, {
819
+ snapId,
820
+ unusedPermissions,
821
+ newPermissions: approvedNewPermissions,
822
+ requestData
823
+ });
824
+ if (manifest.initialConnections) {
825
+ _class_private_method_get(this, _handleInitialConnections, handleInitialConnections).call(this, snapId, oldManifest.initialConnections ?? null, manifest.initialConnections);
813
826
  }
814
827
  const rollbackSnapshot = _class_private_method_get(this, _getRollbackSnapshot, getRollbackSnapshot).call(this, snapId);
815
828
  if (rollbackSnapshot !== undefined) {
816
829
  rollbackSnapshot.permissions.revoked = unusedPermissions;
817
- rollbackSnapshot.permissions.granted = Object.keys(approvedNewPermissions);
830
+ rollbackSnapshot.permissions.granted = approvedNewPermissions;
818
831
  rollbackSnapshot.permissions.requestData = requestData;
819
832
  }
820
833
  const sourceCode = sourceCodeFile.toString();
@@ -838,11 +851,13 @@ class SnapController extends _basecontroller.BaseController {
838
851
  return truncatedSnap;
839
852
  } catch (error) {
840
853
  (0, _snapsutils.logError)(`Error when updating ${snapId},`, error);
854
+ const errorString = error instanceof Error ? error.message : error.toString();
841
855
  _class_private_method_get(this, _updateApproval, updateApproval).call(this, pendingApproval.id, {
842
856
  loading: false,
843
- error: error instanceof Error ? error.message : error.toString(),
857
+ error: errorString,
844
858
  type: SNAP_APPROVAL_UPDATE
845
859
  });
860
+ this.messagingSystem.publish('SnapController:snapInstallFailed', snapId, origin, true, errorString);
846
861
  throw error;
847
862
  }
848
863
  }
@@ -877,14 +892,13 @@ class SnapController extends _basecontroller.BaseController {
877
892
  permissions: processedPermissions
878
893
  });
879
894
  const { permissions: approvedPermissions, ...requestData } = await pendingApproval.promise;
880
- if ((0, _utils.isNonEmptyArray)(Object.keys(approvedPermissions))) {
881
- this.messagingSystem.call('PermissionController:grantPermissions', {
882
- approvedPermissions,
883
- subject: {
884
- origin: snapId
885
- },
886
- requestData
887
- });
895
+ _class_private_method_get(this, _updatePermissions, updatePermissions).call(this, {
896
+ snapId,
897
+ newPermissions: approvedPermissions,
898
+ requestData
899
+ });
900
+ if (snap.manifest.initialConnections) {
901
+ _class_private_method_get(this, _handleInitialConnections, handleInitialConnections).call(this, snapId, null, snap.manifest.initialConnections);
888
902
  }
889
903
  } finally{
890
904
  const runtime = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
@@ -946,7 +960,7 @@ class SnapController extends _basecontroller.BaseController {
946
960
  }
947
961
  constructor({ closeAllConnections, messenger, state, dynamicPermissions = [
948
962
  'eth_accounts'
949
- ], environmentEndowmentPermissions = [], excludedPermissions = {}, idleTimeCheckInterval = (0, _utils.inMilliseconds)(5, _utils.Duration.Second), maxIdleTime = (0, _utils.inMilliseconds)(30, _utils.Duration.Second), maxRequestTime = (0, _utils.inMilliseconds)(60, _utils.Duration.Second), fetchFunction = globalThis.fetch.bind(globalThis), featureFlags = {}, detectSnapLocation: detectSnapLocationFunction = _location.detectSnapLocation }){
963
+ ], environmentEndowmentPermissions = [], excludedPermissions = {}, idleTimeCheckInterval = (0, _utils.inMilliseconds)(5, _utils.Duration.Second), maxIdleTime = (0, _utils.inMilliseconds)(30, _utils.Duration.Second), maxRequestTime = (0, _utils.inMilliseconds)(60, _utils.Duration.Second), fetchFunction = globalThis.fetch.bind(globalThis), featureFlags = {}, detectSnapLocation: detectSnapLocationFunction = _location.detectSnapLocation, preinstalledSnaps }){
950
964
  super({
951
965
  messenger,
952
966
  metadata: {
@@ -984,6 +998,7 @@ class SnapController extends _basecontroller.BaseController {
984
998
  });
985
999
  _class_private_method_init(this, _initializeStateMachine);
986
1000
  _class_private_method_init(this, _registerMessageHandlers);
1001
+ _class_private_method_init(this, _handlePreinstalledSnaps);
987
1002
  _class_private_method_init(this, _pollForLastRequestStatus);
988
1003
  /**
989
1004
  * Blocks an installed snap and prevents it from being started again. Emits
@@ -1001,6 +1016,8 @@ class SnapController extends _basecontroller.BaseController {
1001
1016
  *
1002
1017
  * @param snapId - The snap to terminate.
1003
1018
  */ _class_private_method_init(this, _terminateSnap);
1019
+ _class_private_method_init(this, _handleInitialConnections);
1020
+ _class_private_method_init(this, _addSnapToSubject);
1004
1021
  _class_private_method_init(this, _removeSnapFromSubjects);
1005
1022
  _class_private_method_init(this, _revokeAllSnapPermissions);
1006
1023
  _class_private_method_init(this, _createApproval);
@@ -1028,13 +1045,6 @@ class SnapController extends _basecontroller.BaseController {
1028
1045
  * @returns An array of the names of the endowments.
1029
1046
  */ _class_private_method_init(this, _getEndowments);
1030
1047
  _class_private_method_init(this, _set);
1031
- /**
1032
- * Fetches the manifest and source code of a snap.
1033
- *
1034
- * @param snapId - The id of the Snap.
1035
- * @param location - Source from which snap will be fetched.
1036
- * @returns A tuple of the Snap manifest object and the Snap source code.
1037
- */ _class_private_method_init(this, _fetchSnap);
1038
1048
  _class_private_method_init(this, _validateSnapPermissions);
1039
1049
  _class_private_method_init(this, _getRpcRequestHandler);
1040
1050
  _class_private_method_init(this, _triggerPhishingListUpdate);
@@ -1079,6 +1089,7 @@ class SnapController extends _basecontroller.BaseController {
1079
1089
  _class_private_method_init(this, _getRuntimeExpect);
1080
1090
  _class_private_method_init(this, _setupRuntime);
1081
1091
  _class_private_method_init(this, _calculatePermissionsChange);
1092
+ _class_private_method_init(this, _updatePermissions);
1082
1093
  _class_private_method_init(this, _isValidUpdate);
1083
1094
  /**
1084
1095
  * Call a lifecycle hook on a snap, if the snap has the
@@ -1175,7 +1186,10 @@ class SnapController extends _basecontroller.BaseController {
1175
1186
  });
1176
1187
  _class_private_method_get(this, _initializeStateMachine, initializeStateMachine).call(this);
1177
1188
  _class_private_method_get(this, _registerMessageHandlers, registerMessageHandlers).call(this);
1178
- Object.values(state?.snaps ?? {}).forEach((snap)=>_class_private_method_get(this, _setupRuntime, setupRuntime).call(this, snap.id));
1189
+ if (preinstalledSnaps) {
1190
+ _class_private_method_get(this, _handlePreinstalledSnaps, handlePreinstalledSnaps).call(this, preinstalledSnaps);
1191
+ }
1192
+ Object.values(this.state?.snaps ?? {}).forEach((snap)=>_class_private_method_get(this, _setupRuntime, setupRuntime).call(this, snap.id));
1179
1193
  }
1180
1194
  }
1181
1195
  function initializeStateMachine() {
@@ -1252,6 +1266,63 @@ function registerMessageHandlers() {
1252
1266
  this.messagingSystem.registerActionHandler(`${controllerName}:revokeDynamicPermissions`, (...args)=>this.revokeDynamicSnapPermissions(...args));
1253
1267
  this.messagingSystem.registerActionHandler(`${controllerName}:getFile`, async (...args)=>this.getSnapFile(...args));
1254
1268
  }
1269
+ function handlePreinstalledSnaps(preinstalledSnaps) {
1270
+ for (const { snapId, manifest, files, removable } of preinstalledSnaps){
1271
+ const existingSnap = this.get(snapId);
1272
+ const isAlreadyInstalled = existingSnap !== undefined;
1273
+ const isUpdate = isAlreadyInstalled && (0, _utils.gtVersion)(manifest.version, existingSnap.version);
1274
+ // Disallow downgrades and overwriting non preinstalled snaps
1275
+ if (isAlreadyInstalled && (!isUpdate || existingSnap.preinstalled !== true)) {
1276
+ continue;
1277
+ }
1278
+ const manifestFile = new _snapsutils.VirtualFile({
1279
+ path: _snapsutils.NpmSnapFileNames.Manifest,
1280
+ value: JSON.stringify(manifest),
1281
+ result: manifest
1282
+ });
1283
+ const virtualFiles = files.map(({ path, value })=>new _snapsutils.VirtualFile({
1284
+ value,
1285
+ path
1286
+ }));
1287
+ const { filePath, iconPath } = manifest.source.location.npm;
1288
+ const sourceCode = virtualFiles.find((file)=>file.path === filePath);
1289
+ const svgIcon = iconPath ? virtualFiles.find((file)=>file.path === iconPath) : undefined;
1290
+ (0, _utils.assert)(sourceCode, 'Source code not provided for preinstalled snap.');
1291
+ (0, _utils.assert)(!iconPath || iconPath && svgIcon, 'Icon not provided for preinstalled snap.');
1292
+ (0, _utils.assert)(manifest.source.files === undefined, 'Auxiliary files are not currently supported for preinstalled snaps.');
1293
+ const localizationFiles = manifest.source.locales?.map((path)=>virtualFiles.find((file)=>file.path === path)) ?? [];
1294
+ const validatedLocalizationFiles = (0, _snapsutils.getValidatedLocalizationFiles)(localizationFiles.filter(Boolean));
1295
+ (0, _utils.assert)(localizationFiles.length === validatedLocalizationFiles.length, 'Missing localization files for preinstalled snap.');
1296
+ const filesObject = {
1297
+ manifest: manifestFile,
1298
+ sourceCode,
1299
+ svgIcon,
1300
+ auxiliaryFiles: [],
1301
+ localizationFiles: validatedLocalizationFiles
1302
+ };
1303
+ // Add snap to the SnapController state
1304
+ _class_private_method_get(this, _set, set).call(this, {
1305
+ id: snapId,
1306
+ origin: 'metamask',
1307
+ files: filesObject,
1308
+ removable,
1309
+ preinstalled: true
1310
+ });
1311
+ // Setup permissions
1312
+ const processedPermissions = (0, _permissions.processSnapPermissions)(manifest.initialPermissions);
1313
+ _class_private_method_get(this, _validateSnapPermissions, validateSnapPermissions).call(this, processedPermissions);
1314
+ const { newPermissions, unusedPermissions } = _class_private_method_get(this, _calculatePermissionsChange, calculatePermissionsChange).call(this, snapId, processedPermissions);
1315
+ _class_private_method_get(this, _updatePermissions, updatePermissions).call(this, {
1316
+ snapId,
1317
+ newPermissions,
1318
+ unusedPermissions
1319
+ });
1320
+ // Set status
1321
+ this.update((state)=>{
1322
+ state.snaps[snapId].status = _snapsutils.SnapStatus.Stopped;
1323
+ });
1324
+ }
1325
+ }
1255
1326
  function pollForLastRequestStatus() {
1256
1327
  _class_private_field_set(this, _timeoutForLastRequestStatus, setTimeout(()=>{
1257
1328
  _class_private_method_get(this, _stopSnapsLastRequestPastMax, stopSnapsLastRequestPastMax).call(this).catch((error)=>{
@@ -1314,6 +1385,52 @@ async function terminateSnap(snapId) {
1314
1385
  await this.messagingSystem.call('ExecutionService:terminateSnap', snapId);
1315
1386
  this.messagingSystem.publish('SnapController:snapTerminated', this.getTruncatedExpect(snapId));
1316
1387
  }
1388
+ function handleInitialConnections(snapId, previousInitialConnections, initialConnections) {
1389
+ if (previousInitialConnections) {
1390
+ const revokedInitialConnections = (0, _utils1.setDiff)(previousInitialConnections, initialConnections);
1391
+ for (const origin of Object.keys(revokedInitialConnections)){
1392
+ this.removeSnapFromSubject(origin, snapId);
1393
+ }
1394
+ }
1395
+ for (const origin of Object.keys(initialConnections)){
1396
+ _class_private_method_get(this, _addSnapToSubject, addSnapToSubject).call(this, origin, snapId);
1397
+ }
1398
+ }
1399
+ function addSnapToSubject(origin, snapId) {
1400
+ const subjectPermissions = this.messagingSystem.call('PermissionController:getPermissions', origin);
1401
+ const existingCaveat = subjectPermissions?.[_snapsrpcmethods.WALLET_SNAP_PERMISSION_KEY]?.caveats?.find((caveat)=>caveat.type === _snapsutils.SnapCaveatType.SnapIds);
1402
+ const subjectHasSnap = Boolean((existingCaveat?.value)?.[snapId]);
1403
+ // If the subject is already connected to the snap, this is a no-op.
1404
+ if (subjectHasSnap) {
1405
+ return;
1406
+ }
1407
+ // If an existing caveat exists, we add the snap to that.
1408
+ if (existingCaveat) {
1409
+ this.messagingSystem.call('PermissionController:updateCaveat', origin, _snapsrpcmethods.WALLET_SNAP_PERMISSION_KEY, _snapsutils.SnapCaveatType.SnapIds, {
1410
+ ...existingCaveat,
1411
+ [snapId]: {}
1412
+ });
1413
+ return;
1414
+ }
1415
+ const approvedPermissions = {
1416
+ [_snapsrpcmethods.WALLET_SNAP_PERMISSION_KEY]: {
1417
+ caveats: [
1418
+ {
1419
+ type: _snapsutils.SnapCaveatType.SnapIds,
1420
+ value: {
1421
+ [snapId]: {}
1422
+ }
1423
+ }
1424
+ ]
1425
+ }
1426
+ };
1427
+ this.messagingSystem.call('PermissionController:grantPermissions', {
1428
+ approvedPermissions,
1429
+ subject: {
1430
+ origin
1431
+ }
1432
+ });
1433
+ }
1317
1434
  function removeSnapFromSubjects(snapId) {
1318
1435
  const subjects = this.messagingSystem.call('PermissionController:getSubjectNames');
1319
1436
  for (const subject of subjects){
@@ -1371,8 +1488,8 @@ async function add(args) {
1371
1488
  // If fetching and setting the snap succeeds, this property will be set
1372
1489
  // to null in the authorize() method.
1373
1490
  runtime.installPromise = (async ()=>{
1374
- const fetchedSnap = await _class_private_method_get(this, _fetchSnap, fetchSnap).call(this, snapId, location);
1375
- const manifest = fetchedSnap.files.manifest.result;
1491
+ const fetchedSnap = await (0, _utils1.fetchSnap)(snapId, location);
1492
+ const manifest = fetchedSnap.manifest.result;
1376
1493
  if (!(0, _utils.satisfiesVersionRange)(manifest.version, versionRange)) {
1377
1494
  throw new Error(`Version mismatch. Manifest for "${snapId}" specifies version "${manifest.version}" which doesn't satisfy requested version range "${versionRange}".`);
1378
1495
  }
@@ -1382,7 +1499,7 @@ async function add(args) {
1382
1499
  });
1383
1500
  return _class_private_method_get(this, _set, set).call(this, {
1384
1501
  ...args,
1385
- ...fetchedSnap,
1502
+ files: fetchedSnap,
1386
1503
  id: snapId
1387
1504
  });
1388
1505
  })();
@@ -1445,7 +1562,7 @@ async function getEndowments(snapId) {
1445
1562
  return dedupedEndowments;
1446
1563
  }
1447
1564
  function set(args) {
1448
- const { id: snapId, origin, files, isUpdate = false } = args;
1565
+ const { id: snapId, origin, files, isUpdate = false, removable, preinstalled } = args;
1449
1566
  const { manifest, sourceCode: sourceCodeFile, svgIcon, auxiliaryFiles: rawAuxiliaryFiles, localizationFiles } = files;
1450
1567
  (0, _snapsutils.assertIsSnapManifest)(manifest.result);
1451
1568
  const { version, proposedName } = manifest.result;
@@ -1476,6 +1593,8 @@ function set(args) {
1476
1593
  // previous state.
1477
1594
  blocked: false,
1478
1595
  enabled: true,
1596
+ removable,
1597
+ preinstalled,
1479
1598
  id: snapId,
1480
1599
  initialPermissions: manifest.result.initialPermissions,
1481
1600
  manifest: manifest.result,
@@ -1512,36 +1631,6 @@ function set(args) {
1512
1631
  sourceCode
1513
1632
  };
1514
1633
  }
1515
- async function fetchSnap(snapId, location) {
1516
- try {
1517
- const manifest = await location.manifest();
1518
- const sourceCode = await location.fetch(manifest.result.source.location.npm.filePath);
1519
- const { iconPath } = manifest.result.source.location.npm;
1520
- const svgIcon = iconPath ? await location.fetch(iconPath) : undefined;
1521
- const auxiliaryFiles = await (0, _utils1.getSnapFiles)(location, manifest.result.source.files);
1522
- await Promise.all(auxiliaryFiles.map(async (file)=>{
1523
- // This should still be safe
1524
- // eslint-disable-next-line require-atomic-updates
1525
- file.data.base64 = await (0, _snapsutils.encodeBase64)(file);
1526
- }));
1527
- const localizationFiles = await (0, _utils1.getSnapFiles)(location, manifest.result.source.locales);
1528
- const validatedLocalizationFiles = (0, _snapsutils.getValidatedLocalizationFiles)(localizationFiles);
1529
- const files = {
1530
- manifest,
1531
- sourceCode,
1532
- svgIcon,
1533
- auxiliaryFiles,
1534
- localizationFiles: validatedLocalizationFiles
1535
- };
1536
- await (0, _snapsutils.validateFetchedSnap)(files);
1537
- return {
1538
- files,
1539
- location
1540
- };
1541
- } catch (error) {
1542
- throw new Error(`Failed to fetch snap "${snapId}": ${(0, _snapssdk.getErrorMessage)(error)}.`);
1543
- }
1544
- }
1545
1634
  function validateSnapPermissions(processedPermissions) {
1546
1635
  const permissionKeys = Object.keys(processedPermissions);
1547
1636
  const handlerPermissions = Array.from(new Set(Object.values(_endowments.handlerEndowments)));
@@ -1634,6 +1723,17 @@ async function assertSnapRpcRequestResult(handlerType, result) {
1634
1723
  (0, _snapsutils.validateComponentLinks)(result.content, _class_private_method_get(this, _checkPhishingList, checkPhishingList).bind(this));
1635
1724
  break;
1636
1725
  }
1726
+ case _snapsutils.HandlerType.OnSignature:
1727
+ {
1728
+ (0, _utils.assertStruct)(result, _snapsutils.OnSignatureResponseStruct);
1729
+ // Null is an allowed return value here
1730
+ if (result === null) {
1731
+ return;
1732
+ }
1733
+ await _class_private_method_get(this, _triggerPhishingListUpdate, triggerPhishingListUpdate).call(this);
1734
+ (0, _snapsutils.validateComponentLinks)(result.content, _class_private_method_get(this, _checkPhishingList, checkPhishingList).bind(this));
1735
+ break;
1736
+ }
1637
1737
  case _snapsutils.HandlerType.OnHomePage:
1638
1738
  (0, _utils.assertStruct)(result, _snapsutils.OnHomePageResponseStruct);
1639
1739
  await _class_private_method_get(this, _triggerPhishingListUpdate, triggerPhishingListUpdate).call(this);
@@ -1672,11 +1772,7 @@ function createRollbackSnapshot(snapId) {
1672
1772
  (0, _utils.assert)(_class_private_field_get(this, _rollbackSnapshots).get(snapId) === undefined, new Error(`Snap "${snapId}" rollback snapshot already exists.`));
1673
1773
  _class_private_field_get(this, _rollbackSnapshots).set(snapId, {
1674
1774
  statePatches: [],
1675
- permissions: {
1676
- revoked: null,
1677
- granted: [],
1678
- requestData: null
1679
- },
1775
+ permissions: {},
1680
1776
  newVersion: ''
1681
1777
  });
1682
1778
  const newRollbackSnapshot = _class_private_field_get(this, _rollbackSnapshots).get(snapId);
@@ -1704,20 +1800,12 @@ async function rollbackSnap(snapId) {
1704
1800
  state.snaps[snapId].status = _snapsutils.SnapStatus.Stopped;
1705
1801
  });
1706
1802
  }
1707
- if (permissions.revoked && Object.keys(permissions.revoked).length) {
1708
- this.messagingSystem.call('PermissionController:grantPermissions', {
1709
- approvedPermissions: permissions.revoked,
1710
- subject: {
1711
- origin: snapId
1712
- },
1713
- requestData: permissions.requestData
1714
- });
1715
- }
1716
- if (permissions.granted?.length) {
1717
- this.messagingSystem.call('PermissionController:revokePermissions', {
1718
- [snapId]: permissions.granted
1719
- });
1720
- }
1803
+ _class_private_method_get(this, _updatePermissions, updatePermissions).call(this, {
1804
+ snapId,
1805
+ unusedPermissions: permissions.granted,
1806
+ newPermissions: permissions.revoked,
1807
+ requestData: permissions.requestData
1808
+ });
1721
1809
  const truncatedSnap = this.getTruncatedExpect(snapId);
1722
1810
  this.messagingSystem.publish('SnapController:snapRolledback', truncatedSnap, rollbackSnapshot.newVersion);
1723
1811
  _class_private_field_get(this, _rollbackSnapshots).delete(snapId);
@@ -1773,6 +1861,23 @@ function calculatePermissionsChange(snapId, desiredPermissionsSet) {
1773
1861
  approvedPermissions
1774
1862
  };
1775
1863
  }
1864
+ function updatePermissions({ snapId, unusedPermissions = {}, newPermissions = {}, requestData }) {
1865
+ const unusedPermissionsKeys = Object.keys(unusedPermissions);
1866
+ if ((0, _utils.isNonEmptyArray)(unusedPermissionsKeys)) {
1867
+ this.messagingSystem.call('PermissionController:revokePermissions', {
1868
+ [snapId]: unusedPermissionsKeys
1869
+ });
1870
+ }
1871
+ if ((0, _utils.isNonEmptyArray)(Object.keys(newPermissions))) {
1872
+ this.messagingSystem.call('PermissionController:grantPermissions', {
1873
+ approvedPermissions: newPermissions,
1874
+ subject: {
1875
+ origin: snapId
1876
+ },
1877
+ requestData
1878
+ });
1879
+ }
1880
+ }
1776
1881
  function isValidUpdate(snapId, newVersionRange) {
1777
1882
  const existingSnap = this.getExpect(snapId);
1778
1883
  if ((0, _utils.satisfiesVersionRange)(existingSnap.version, newVersionRange)) {