@metamask/snaps-controllers 3.6.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 (55) hide show
  1. package/CHANGELOG.md +19 -1
  2. package/dist/cjs/cronjob/CronjobController.js +2 -2
  3. package/dist/cjs/cronjob/CronjobController.js.map +1 -1
  4. package/dist/cjs/services/ProxyPostMessageStream.js +3 -10
  5. package/dist/cjs/services/ProxyPostMessageStream.js.map +1 -1
  6. package/dist/cjs/services/node/NodeProcessExecutionService.js +13 -1
  7. package/dist/cjs/services/node/NodeProcessExecutionService.js.map +1 -1
  8. package/dist/cjs/services/node/NodeThreadExecutionService.js +14 -1
  9. package/dist/cjs/services/node/NodeThreadExecutionService.js.map +1 -1
  10. package/dist/cjs/services/offscreen/OffscreenExecutionService.js +36 -99
  11. package/dist/cjs/services/offscreen/OffscreenExecutionService.js.map +1 -1
  12. package/dist/cjs/services/proxy/ProxyExecutionService.js +110 -0
  13. package/dist/cjs/services/proxy/ProxyExecutionService.js.map +1 -0
  14. package/dist/cjs/snaps/SnapController.js +206 -96
  15. package/dist/cjs/snaps/SnapController.js.map +1 -1
  16. package/dist/cjs/snaps/endowments/enum.js +1 -0
  17. package/dist/cjs/snaps/endowments/enum.js.map +1 -1
  18. package/dist/cjs/snaps/endowments/index.js +12 -4
  19. package/dist/cjs/snaps/endowments/index.js.map +1 -1
  20. package/dist/cjs/snaps/endowments/signature-insight.js +106 -0
  21. package/dist/cjs/snaps/endowments/signature-insight.js.map +1 -0
  22. package/dist/cjs/utils.js +32 -0
  23. package/dist/cjs/utils.js.map +1 -1
  24. package/dist/esm/cronjob/CronjobController.js +2 -2
  25. package/dist/esm/cronjob/CronjobController.js.map +1 -1
  26. package/dist/esm/services/ProxyPostMessageStream.js +3 -10
  27. package/dist/esm/services/ProxyPostMessageStream.js.map +1 -1
  28. package/dist/esm/services/node/NodeProcessExecutionService.js +13 -1
  29. package/dist/esm/services/node/NodeProcessExecutionService.js.map +1 -1
  30. package/dist/esm/services/node/NodeThreadExecutionService.js +14 -1
  31. package/dist/esm/services/node/NodeThreadExecutionService.js.map +1 -1
  32. package/dist/esm/services/offscreen/OffscreenExecutionService.js +36 -99
  33. package/dist/esm/services/offscreen/OffscreenExecutionService.js.map +1 -1
  34. package/dist/esm/services/proxy/ProxyExecutionService.js +100 -0
  35. package/dist/esm/services/proxy/ProxyExecutionService.js.map +1 -0
  36. package/dist/esm/snaps/SnapController.js +208 -98
  37. package/dist/esm/snaps/SnapController.js.map +1 -1
  38. package/dist/esm/snaps/endowments/enum.js +1 -0
  39. package/dist/esm/snaps/endowments/enum.js.map +1 -1
  40. package/dist/esm/snaps/endowments/index.js +10 -4
  41. package/dist/esm/snaps/endowments/index.js.map +1 -1
  42. package/dist/esm/snaps/endowments/signature-insight.js +99 -0
  43. package/dist/esm/snaps/endowments/signature-insight.js.map +1 -0
  44. package/dist/esm/utils.js +37 -0
  45. package/dist/esm/utils.js.map +1 -1
  46. package/dist/types/cronjob/CronjobController.d.ts +2 -2
  47. package/dist/types/services/ProxyPostMessageStream.d.ts +1 -2
  48. package/dist/types/services/offscreen/OffscreenExecutionService.d.ts +5 -22
  49. package/dist/types/services/proxy/ProxyExecutionService.d.ts +39 -0
  50. package/dist/types/snaps/SnapController.d.ts +33 -20
  51. package/dist/types/snaps/endowments/enum.d.ts +1 -0
  52. package/dist/types/snaps/endowments/index.d.ts +12 -0
  53. package/dist/types/snaps/endowments/signature-insight.d.ts +39 -0
  54. package/dist/types/utils.d.ts +99 -0
  55. package/package.json +10 -10
@@ -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);
@@ -485,7 +499,6 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
485
499
  delete state.snaps[snapId];
486
500
  delete state.snapStates[snapId];
487
501
  });
488
- this.messagingSystem.publish(`SnapController:snapRemoved`, truncated);
489
502
  // If the snap has been fully installed before, also emit snapUninstalled.
490
503
  if (snap.status !== SnapStatus.Installing) {
491
504
  this.messagingSystem.publish(`SnapController:snapUninstalled`, truncated);
@@ -665,6 +678,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
665
678
  snapId,
666
679
  type: SNAP_APPROVAL_INSTALL
667
680
  });
681
+ this.messagingSystem.publish('SnapController:snapInstallStarted', snapId, origin, false);
668
682
  // Existing snaps must be stopped before overwriting
669
683
  if (existingSnap && this.isRunning(snapId)) {
670
684
  await this.stopSnap(snapId, SnapStatusEvents.Stop);
@@ -698,11 +712,13 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
698
712
  return truncated;
699
713
  } catch (error) {
700
714
  logError(`Error when adding ${snapId}.`, error);
715
+ const errorString = error instanceof Error ? error.message : error.toString();
701
716
  _class_private_method_get(this, _updateApproval, updateApproval).call(this, pendingApproval.id, {
702
717
  loading: false,
703
718
  type: SNAP_APPROVAL_INSTALL,
704
- error: error instanceof Error ? error.message : error.toString()
719
+ error: errorString
705
720
  });
721
+ this.messagingSystem.publish('SnapController:snapInstallFailed', snapId, origin, false, errorString);
706
722
  throw error;
707
723
  }
708
724
  }
@@ -734,9 +750,11 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
734
750
  type: SNAP_APPROVAL_UPDATE
735
751
  });
736
752
  try {
753
+ this.messagingSystem.publish('SnapController:snapInstallStarted', snapId, origin, true);
737
754
  const snap = this.getExpect(snapId);
738
- const newSnap = await _class_private_method_get(this, _fetchSnap, fetchSnap).call(this, snapId, location);
739
- 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;
740
758
  const manifest = manifestFile.result;
741
759
  const newVersion = manifest.version;
742
760
  if (!gtVersion(newVersion, snap.version)) {
@@ -773,28 +791,22 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
773
791
  _class_private_method_get(this, _set, set).call(this, {
774
792
  origin,
775
793
  id: snapId,
776
- files: newSnap.files,
794
+ files: newSnap,
777
795
  isUpdate: true
778
796
  });
779
- const unusedPermissionsKeys = Object.keys(unusedPermissions);
780
- if (isNonEmptyArray(unusedPermissionsKeys)) {
781
- this.messagingSystem.call('PermissionController:revokePermissions', {
782
- [snapId]: unusedPermissionsKeys
783
- });
784
- }
785
- if (isNonEmptyArray(Object.keys(approvedNewPermissions))) {
786
- this.messagingSystem.call('PermissionController:grantPermissions', {
787
- approvedPermissions: approvedNewPermissions,
788
- subject: {
789
- origin: snapId
790
- },
791
- requestData
792
- });
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);
793
805
  }
794
806
  const rollbackSnapshot = _class_private_method_get(this, _getRollbackSnapshot, getRollbackSnapshot).call(this, snapId);
795
807
  if (rollbackSnapshot !== undefined) {
796
808
  rollbackSnapshot.permissions.revoked = unusedPermissions;
797
- rollbackSnapshot.permissions.granted = Object.keys(approvedNewPermissions);
809
+ rollbackSnapshot.permissions.granted = approvedNewPermissions;
798
810
  rollbackSnapshot.permissions.requestData = requestData;
799
811
  }
800
812
  const sourceCode = sourceCodeFile.toString();
@@ -818,11 +830,13 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
818
830
  return truncatedSnap;
819
831
  } catch (error) {
820
832
  logError(`Error when updating ${snapId},`, error);
833
+ const errorString = error instanceof Error ? error.message : error.toString();
821
834
  _class_private_method_get(this, _updateApproval, updateApproval).call(this, pendingApproval.id, {
822
835
  loading: false,
823
- error: error instanceof Error ? error.message : error.toString(),
836
+ error: errorString,
824
837
  type: SNAP_APPROVAL_UPDATE
825
838
  });
839
+ this.messagingSystem.publish('SnapController:snapInstallFailed', snapId, origin, true, errorString);
826
840
  throw error;
827
841
  }
828
842
  }
@@ -857,14 +871,13 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
857
871
  permissions: processedPermissions
858
872
  });
859
873
  const { permissions: approvedPermissions, ...requestData } = await pendingApproval.promise;
860
- if (isNonEmptyArray(Object.keys(approvedPermissions))) {
861
- this.messagingSystem.call('PermissionController:grantPermissions', {
862
- approvedPermissions,
863
- subject: {
864
- origin: snapId
865
- },
866
- requestData
867
- });
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);
868
881
  }
869
882
  } finally{
870
883
  const runtime = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
@@ -926,7 +939,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
926
939
  }
927
940
  constructor({ closeAllConnections, messenger, state, dynamicPermissions = [
928
941
  'eth_accounts'
929
- ], 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 }){
930
943
  super({
931
944
  messenger,
932
945
  metadata: {
@@ -964,6 +977,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
964
977
  });
965
978
  _class_private_method_init(this, _initializeStateMachine);
966
979
  _class_private_method_init(this, _registerMessageHandlers);
980
+ _class_private_method_init(this, _handlePreinstalledSnaps);
967
981
  _class_private_method_init(this, _pollForLastRequestStatus);
968
982
  /**
969
983
  * Blocks an installed snap and prevents it from being started again. Emits
@@ -981,6 +995,8 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
981
995
  *
982
996
  * @param snapId - The snap to terminate.
983
997
  */ _class_private_method_init(this, _terminateSnap);
998
+ _class_private_method_init(this, _handleInitialConnections);
999
+ _class_private_method_init(this, _addSnapToSubject);
984
1000
  _class_private_method_init(this, _removeSnapFromSubjects);
985
1001
  _class_private_method_init(this, _revokeAllSnapPermissions);
986
1002
  _class_private_method_init(this, _createApproval);
@@ -1008,13 +1024,6 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
1008
1024
  * @returns An array of the names of the endowments.
1009
1025
  */ _class_private_method_init(this, _getEndowments);
1010
1026
  _class_private_method_init(this, _set);
1011
- /**
1012
- * Fetches the manifest and source code of a snap.
1013
- *
1014
- * @param snapId - The id of the Snap.
1015
- * @param location - Source from which snap will be fetched.
1016
- * @returns A tuple of the Snap manifest object and the Snap source code.
1017
- */ _class_private_method_init(this, _fetchSnap);
1018
1027
  _class_private_method_init(this, _validateSnapPermissions);
1019
1028
  _class_private_method_init(this, _getRpcRequestHandler);
1020
1029
  _class_private_method_init(this, _triggerPhishingListUpdate);
@@ -1059,6 +1068,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
1059
1068
  _class_private_method_init(this, _getRuntimeExpect);
1060
1069
  _class_private_method_init(this, _setupRuntime);
1061
1070
  _class_private_method_init(this, _calculatePermissionsChange);
1071
+ _class_private_method_init(this, _updatePermissions);
1062
1072
  _class_private_method_init(this, _isValidUpdate);
1063
1073
  /**
1064
1074
  * Call a lifecycle hook on a snap, if the snap has the
@@ -1155,7 +1165,10 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
1155
1165
  });
1156
1166
  _class_private_method_get(this, _initializeStateMachine, initializeStateMachine).call(this);
1157
1167
  _class_private_method_get(this, _registerMessageHandlers, registerMessageHandlers).call(this);
1158
- 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));
1159
1172
  }
1160
1173
  }
1161
1174
  function initializeStateMachine() {
@@ -1232,6 +1245,63 @@ function registerMessageHandlers() {
1232
1245
  this.messagingSystem.registerActionHandler(`${controllerName}:revokeDynamicPermissions`, (...args)=>this.revokeDynamicSnapPermissions(...args));
1233
1246
  this.messagingSystem.registerActionHandler(`${controllerName}:getFile`, async (...args)=>this.getSnapFile(...args));
1234
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
+ }
1235
1305
  function pollForLastRequestStatus() {
1236
1306
  _class_private_field_set(this, _timeoutForLastRequestStatus, setTimeout(()=>{
1237
1307
  _class_private_method_get(this, _stopSnapsLastRequestPastMax, stopSnapsLastRequestPastMax).call(this).catch((error)=>{
@@ -1294,6 +1364,52 @@ async function terminateSnap(snapId) {
1294
1364
  await this.messagingSystem.call('ExecutionService:terminateSnap', snapId);
1295
1365
  this.messagingSystem.publish('SnapController:snapTerminated', this.getTruncatedExpect(snapId));
1296
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
+ }
1297
1413
  function removeSnapFromSubjects(snapId) {
1298
1414
  const subjects = this.messagingSystem.call('PermissionController:getSubjectNames');
1299
1415
  for (const subject of subjects){
@@ -1351,8 +1467,8 @@ async function add(args) {
1351
1467
  // If fetching and setting the snap succeeds, this property will be set
1352
1468
  // to null in the authorize() method.
1353
1469
  runtime.installPromise = (async ()=>{
1354
- const fetchedSnap = await _class_private_method_get(this, _fetchSnap, fetchSnap).call(this, snapId, location);
1355
- const manifest = fetchedSnap.files.manifest.result;
1470
+ const fetchedSnap = await fetchSnap(snapId, location);
1471
+ const manifest = fetchedSnap.manifest.result;
1356
1472
  if (!satisfiesVersionRange(manifest.version, versionRange)) {
1357
1473
  throw new Error(`Version mismatch. Manifest for "${snapId}" specifies version "${manifest.version}" which doesn't satisfy requested version range "${versionRange}".`);
1358
1474
  }
@@ -1362,7 +1478,7 @@ async function add(args) {
1362
1478
  });
1363
1479
  return _class_private_method_get(this, _set, set).call(this, {
1364
1480
  ...args,
1365
- ...fetchedSnap,
1481
+ files: fetchedSnap,
1366
1482
  id: snapId
1367
1483
  });
1368
1484
  })();
@@ -1425,10 +1541,10 @@ async function getEndowments(snapId) {
1425
1541
  return dedupedEndowments;
1426
1542
  }
1427
1543
  function set(args) {
1428
- const { id: snapId, origin, files, isUpdate = false } = args;
1544
+ const { id: snapId, origin, files, isUpdate = false, removable, preinstalled } = args;
1429
1545
  const { manifest, sourceCode: sourceCodeFile, svgIcon, auxiliaryFiles: rawAuxiliaryFiles, localizationFiles } = files;
1430
1546
  assertIsSnapManifest(manifest.result);
1431
- const { version } = manifest.result;
1547
+ const { version, proposedName } = manifest.result;
1432
1548
  const sourceCode = sourceCodeFile.toString();
1433
1549
  assert(typeof sourceCode === 'string' && sourceCode.length > 0, `Invalid source code for snap "${snapId}".`);
1434
1550
  const auxiliaryFiles = rawAuxiliaryFiles.map((file)=>{
@@ -1456,6 +1572,8 @@ function set(args) {
1456
1572
  // previous state.
1457
1573
  blocked: false,
1458
1574
  enabled: true,
1575
+ removable,
1576
+ preinstalled,
1459
1577
  id: snapId,
1460
1578
  initialPermissions: manifest.result.initialPermissions,
1461
1579
  manifest: manifest.result,
@@ -1480,42 +1598,18 @@ function set(args) {
1480
1598
  rollbackSnapshot.statePatches = inversePatches;
1481
1599
  }
1482
1600
  }
1483
- this.messagingSystem.publish(`SnapController:snapAdded`, snap, svgIcon?.toString());
1601
+ this.messagingSystem.call('SubjectMetadataController:addSubjectMetadata', {
1602
+ subjectType: SubjectType.Snap,
1603
+ name: proposedName,
1604
+ origin: snap.id,
1605
+ version,
1606
+ svgIcon: svgIcon?.toString() ?? null
1607
+ });
1484
1608
  return {
1485
1609
  ...snap,
1486
1610
  sourceCode
1487
1611
  };
1488
1612
  }
1489
- async function fetchSnap(snapId, location) {
1490
- try {
1491
- const manifest = await location.manifest();
1492
- const sourceCode = await location.fetch(manifest.result.source.location.npm.filePath);
1493
- const { iconPath } = manifest.result.source.location.npm;
1494
- const svgIcon = iconPath ? await location.fetch(iconPath) : undefined;
1495
- const auxiliaryFiles = await getSnapFiles(location, manifest.result.source.files);
1496
- await Promise.all(auxiliaryFiles.map(async (file)=>{
1497
- // This should still be safe
1498
- // eslint-disable-next-line require-atomic-updates
1499
- file.data.base64 = await encodeBase64(file);
1500
- }));
1501
- const localizationFiles = await getSnapFiles(location, manifest.result.source.locales);
1502
- const validatedLocalizationFiles = getValidatedLocalizationFiles(localizationFiles);
1503
- const files = {
1504
- manifest,
1505
- sourceCode,
1506
- svgIcon,
1507
- auxiliaryFiles,
1508
- localizationFiles: validatedLocalizationFiles
1509
- };
1510
- await validateFetchedSnap(files);
1511
- return {
1512
- files,
1513
- location
1514
- };
1515
- } catch (error) {
1516
- throw new Error(`Failed to fetch snap "${snapId}": ${getErrorMessage(error)}.`);
1517
- }
1518
- }
1519
1613
  function validateSnapPermissions(processedPermissions) {
1520
1614
  const permissionKeys = Object.keys(processedPermissions);
1521
1615
  const handlerPermissions = Array.from(new Set(Object.values(handlerEndowments)));
@@ -1608,6 +1702,17 @@ async function assertSnapRpcRequestResult(handlerType, result) {
1608
1702
  validateComponentLinks(result.content, _class_private_method_get(this, _checkPhishingList, checkPhishingList).bind(this));
1609
1703
  break;
1610
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
+ }
1611
1716
  case HandlerType.OnHomePage:
1612
1717
  assertStruct(result, OnHomePageResponseStruct);
1613
1718
  await _class_private_method_get(this, _triggerPhishingListUpdate, triggerPhishingListUpdate).call(this);
@@ -1646,11 +1751,7 @@ function createRollbackSnapshot(snapId) {
1646
1751
  assert(_class_private_field_get(this, _rollbackSnapshots).get(snapId) === undefined, new Error(`Snap "${snapId}" rollback snapshot already exists.`));
1647
1752
  _class_private_field_get(this, _rollbackSnapshots).set(snapId, {
1648
1753
  statePatches: [],
1649
- permissions: {
1650
- revoked: null,
1651
- granted: [],
1652
- requestData: null
1653
- },
1754
+ permissions: {},
1654
1755
  newVersion: ''
1655
1756
  });
1656
1757
  const newRollbackSnapshot = _class_private_field_get(this, _rollbackSnapshots).get(snapId);
@@ -1678,20 +1779,12 @@ async function rollbackSnap(snapId) {
1678
1779
  state.snaps[snapId].status = SnapStatus.Stopped;
1679
1780
  });
1680
1781
  }
1681
- if (permissions.revoked && Object.keys(permissions.revoked).length) {
1682
- this.messagingSystem.call('PermissionController:grantPermissions', {
1683
- approvedPermissions: permissions.revoked,
1684
- subject: {
1685
- origin: snapId
1686
- },
1687
- requestData: permissions.requestData
1688
- });
1689
- }
1690
- if (permissions.granted?.length) {
1691
- this.messagingSystem.call('PermissionController:revokePermissions', {
1692
- [snapId]: permissions.granted
1693
- });
1694
- }
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
+ });
1695
1788
  const truncatedSnap = this.getTruncatedExpect(snapId);
1696
1789
  this.messagingSystem.publish('SnapController:snapRolledback', truncatedSnap, rollbackSnapshot.newVersion);
1697
1790
  _class_private_field_get(this, _rollbackSnapshots).delete(snapId);
@@ -1747,6 +1840,23 @@ function calculatePermissionsChange(snapId, desiredPermissionsSet) {
1747
1840
  approvedPermissions
1748
1841
  };
1749
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
+ }
1750
1860
  function isValidUpdate(snapId, newVersionRange) {
1751
1861
  const existingSnap = this.getExpect(snapId);
1752
1862
  if (satisfiesVersionRange(existingSnap.version, newVersionRange)) {