@metamask/snaps-controllers 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/dist/cjs/cronjob/CronjobController.js +3 -3
  3. package/dist/cjs/cronjob/CronjobController.js.map +1 -1
  4. package/dist/cjs/index.js +1 -0
  5. package/dist/cjs/index.js.map +1 -1
  6. package/dist/cjs/interface/SnapInterfaceController.js +166 -0
  7. package/dist/cjs/interface/SnapInterfaceController.js.map +1 -0
  8. package/dist/cjs/interface/index.js +20 -0
  9. package/dist/cjs/interface/index.js.map +1 -0
  10. package/dist/cjs/interface/utils.js +59 -0
  11. package/dist/cjs/interface/utils.js.map +1 -0
  12. package/dist/cjs/services/ProxyPostMessageStream.js +3 -10
  13. package/dist/cjs/services/ProxyPostMessageStream.js.map +1 -1
  14. package/dist/cjs/services/browser.js +1 -0
  15. package/dist/cjs/services/browser.js.map +1 -1
  16. package/dist/cjs/services/index.js +1 -0
  17. package/dist/cjs/services/index.js.map +1 -1
  18. package/dist/cjs/services/node/NodeProcessExecutionService.js +13 -1
  19. package/dist/cjs/services/node/NodeProcessExecutionService.js.map +1 -1
  20. package/dist/cjs/services/node/NodeThreadExecutionService.js +14 -1
  21. package/dist/cjs/services/node/NodeThreadExecutionService.js.map +1 -1
  22. package/dist/cjs/services/offscreen/OffscreenExecutionService.js +36 -99
  23. package/dist/cjs/services/offscreen/OffscreenExecutionService.js.map +1 -1
  24. package/dist/cjs/services/proxy/ProxyExecutionService.js +110 -0
  25. package/dist/cjs/services/proxy/ProxyExecutionService.js.map +1 -0
  26. package/dist/cjs/services/webview/WebViewExecutionService.js +99 -0
  27. package/dist/cjs/services/webview/WebViewExecutionService.js.map +1 -0
  28. package/dist/cjs/services/webview/WebViewMessageStream.js +127 -0
  29. package/dist/cjs/services/webview/WebViewMessageStream.js.map +1 -0
  30. package/dist/cjs/services/webview/index.js +20 -0
  31. package/dist/cjs/services/webview/index.js.map +1 -0
  32. package/dist/cjs/snaps/SnapController.js +303 -138
  33. package/dist/cjs/snaps/SnapController.js.map +1 -1
  34. package/dist/cjs/snaps/constants.js +25 -0
  35. package/dist/cjs/snaps/constants.js.map +1 -0
  36. package/dist/cjs/snaps/index.js +0 -2
  37. package/dist/cjs/snaps/index.js.map +1 -1
  38. package/dist/cjs/snaps/location/npm.js +13 -2
  39. package/dist/cjs/snaps/location/npm.js.map +1 -1
  40. package/dist/cjs/utils.js +32 -0
  41. package/dist/cjs/utils.js.map +1 -1
  42. package/dist/esm/cronjob/CronjobController.js +2 -2
  43. package/dist/esm/cronjob/CronjobController.js.map +1 -1
  44. package/dist/esm/index.js +1 -0
  45. package/dist/esm/index.js.map +1 -1
  46. package/dist/esm/interface/SnapInterfaceController.js +158 -0
  47. package/dist/esm/interface/SnapInterfaceController.js.map +1 -0
  48. package/dist/esm/interface/index.js +3 -0
  49. package/dist/esm/interface/index.js.map +1 -0
  50. package/dist/esm/interface/utils.js +62 -0
  51. package/dist/esm/interface/utils.js.map +1 -0
  52. package/dist/esm/services/ProxyPostMessageStream.js +3 -10
  53. package/dist/esm/services/ProxyPostMessageStream.js.map +1 -1
  54. package/dist/esm/services/browser.js +1 -0
  55. package/dist/esm/services/browser.js.map +1 -1
  56. package/dist/esm/services/index.js +1 -0
  57. package/dist/esm/services/index.js.map +1 -1
  58. package/dist/esm/services/node/NodeProcessExecutionService.js +13 -1
  59. package/dist/esm/services/node/NodeProcessExecutionService.js.map +1 -1
  60. package/dist/esm/services/node/NodeThreadExecutionService.js +14 -1
  61. package/dist/esm/services/node/NodeThreadExecutionService.js.map +1 -1
  62. package/dist/esm/services/offscreen/OffscreenExecutionService.js +36 -99
  63. package/dist/esm/services/offscreen/OffscreenExecutionService.js.map +1 -1
  64. package/dist/esm/services/proxy/ProxyExecutionService.js +100 -0
  65. package/dist/esm/services/proxy/ProxyExecutionService.js.map +1 -0
  66. package/dist/esm/services/webview/WebViewExecutionService.js +89 -0
  67. package/dist/esm/services/webview/WebViewExecutionService.js.map +1 -0
  68. package/dist/esm/services/webview/WebViewMessageStream.js +119 -0
  69. package/dist/esm/services/webview/WebViewMessageStream.js.map +1 -0
  70. package/dist/esm/services/webview/index.js +3 -0
  71. package/dist/esm/services/webview/index.js.map +1 -0
  72. package/dist/esm/snaps/SnapController.js +299 -134
  73. package/dist/esm/snaps/SnapController.js.map +1 -1
  74. package/dist/esm/snaps/constants.js +16 -0
  75. package/dist/esm/snaps/constants.js.map +1 -0
  76. package/dist/esm/snaps/index.js +0 -2
  77. package/dist/esm/snaps/index.js.map +1 -1
  78. package/dist/esm/snaps/location/npm.js +13 -2
  79. package/dist/esm/snaps/location/npm.js.map +1 -1
  80. package/dist/esm/utils.js +37 -0
  81. package/dist/esm/utils.js.map +1 -1
  82. package/dist/types/index.d.ts +1 -0
  83. package/dist/types/interface/SnapInterfaceController.d.ts +85 -0
  84. package/dist/types/interface/index.d.ts +1 -0
  85. package/dist/types/interface/utils.d.ts +36 -0
  86. package/dist/types/services/ProxyPostMessageStream.d.ts +1 -2
  87. package/dist/types/services/browser.d.ts +1 -0
  88. package/dist/types/services/index.d.ts +1 -0
  89. package/dist/types/services/offscreen/OffscreenExecutionService.d.ts +5 -22
  90. package/dist/types/services/proxy/ProxyExecutionService.d.ts +39 -0
  91. package/dist/types/services/webview/WebViewExecutionService.d.ts +20 -0
  92. package/dist/types/services/webview/WebViewMessageStream.d.ts +32 -0
  93. package/dist/types/services/webview/index.d.ts +1 -0
  94. package/dist/types/snaps/SnapController.d.ts +37 -6
  95. package/dist/types/snaps/constants.d.ts +1 -0
  96. package/dist/types/snaps/index.d.ts +0 -2
  97. package/dist/types/utils.d.ts +119 -0
  98. package/package.json +14 -14
  99. package/dist/cjs/snaps/endowments/cronjob.js +0 -100
  100. package/dist/cjs/snaps/endowments/cronjob.js.map +0 -1
  101. package/dist/cjs/snaps/endowments/enum.js +0 -25
  102. package/dist/cjs/snaps/endowments/enum.js.map +0 -1
  103. package/dist/cjs/snaps/endowments/ethereum-provider.js +0 -43
  104. package/dist/cjs/snaps/endowments/ethereum-provider.js.map +0 -1
  105. package/dist/cjs/snaps/endowments/home-page.js +0 -37
  106. package/dist/cjs/snaps/endowments/home-page.js.map +0 -1
  107. package/dist/cjs/snaps/endowments/index.js +0 -99
  108. package/dist/cjs/snaps/endowments/index.js.map +0 -1
  109. package/dist/cjs/snaps/endowments/keyring.js +0 -100
  110. package/dist/cjs/snaps/endowments/keyring.js.map +0 -1
  111. package/dist/cjs/snaps/endowments/lifecycle-hooks.js +0 -37
  112. package/dist/cjs/snaps/endowments/lifecycle-hooks.js.map +0 -1
  113. package/dist/cjs/snaps/endowments/name-lookup.js +0 -106
  114. package/dist/cjs/snaps/endowments/name-lookup.js.map +0 -1
  115. package/dist/cjs/snaps/endowments/network-access.js +0 -44
  116. package/dist/cjs/snaps/endowments/network-access.js.map +0 -1
  117. package/dist/cjs/snaps/endowments/rpc.js +0 -99
  118. package/dist/cjs/snaps/endowments/rpc.js.map +0 -1
  119. package/dist/cjs/snaps/endowments/transaction-insight.js +0 -106
  120. package/dist/cjs/snaps/endowments/transaction-insight.js.map +0 -1
  121. package/dist/cjs/snaps/endowments/web-assembly.js +0 -42
  122. package/dist/cjs/snaps/endowments/web-assembly.js.map +0 -1
  123. package/dist/cjs/snaps/permissions.js +0 -61
  124. package/dist/cjs/snaps/permissions.js.map +0 -1
  125. package/dist/esm/snaps/endowments/cronjob.js +0 -99
  126. package/dist/esm/snaps/endowments/cronjob.js.map +0 -1
  127. package/dist/esm/snaps/endowments/enum.js +0 -15
  128. package/dist/esm/snaps/endowments/enum.js.map +0 -1
  129. package/dist/esm/snaps/endowments/ethereum-provider.js +0 -33
  130. package/dist/esm/snaps/endowments/ethereum-provider.js.map +0 -1
  131. package/dist/esm/snaps/endowments/home-page.js +0 -27
  132. package/dist/esm/snaps/endowments/home-page.js.map +0 -1
  133. package/dist/esm/snaps/endowments/index.js +0 -54
  134. package/dist/esm/snaps/endowments/index.js.map +0 -1
  135. package/dist/esm/snaps/endowments/keyring.js +0 -91
  136. package/dist/esm/snaps/endowments/keyring.js.map +0 -1
  137. package/dist/esm/snaps/endowments/lifecycle-hooks.js +0 -27
  138. package/dist/esm/snaps/endowments/lifecycle-hooks.js.map +0 -1
  139. package/dist/esm/snaps/endowments/name-lookup.js +0 -98
  140. package/dist/esm/snaps/endowments/name-lookup.js.map +0 -1
  141. package/dist/esm/snaps/endowments/network-access.js +0 -34
  142. package/dist/esm/snaps/endowments/network-access.js.map +0 -1
  143. package/dist/esm/snaps/endowments/rpc.js +0 -88
  144. package/dist/esm/snaps/endowments/rpc.js.map +0 -1
  145. package/dist/esm/snaps/endowments/transaction-insight.js +0 -99
  146. package/dist/esm/snaps/endowments/transaction-insight.js.map +0 -1
  147. package/dist/esm/snaps/endowments/web-assembly.js +0 -32
  148. package/dist/esm/snaps/endowments/web-assembly.js.map +0 -1
  149. package/dist/esm/snaps/permissions.js +0 -50
  150. package/dist/esm/snaps/permissions.js.map +0 -1
  151. package/dist/types/snaps/endowments/cronjob.d.ts +0 -51
  152. package/dist/types/snaps/endowments/enum.d.ts +0 -12
  153. package/dist/types/snaps/endowments/ethereum-provider.d.ts +0 -14
  154. package/dist/types/snaps/endowments/home-page.d.ts +0 -15
  155. package/dist/types/snaps/endowments/index.d.ts +0 -115
  156. package/dist/types/snaps/endowments/keyring.d.ts +0 -39
  157. package/dist/types/snaps/endowments/lifecycle-hooks.d.ts +0 -15
  158. package/dist/types/snaps/endowments/name-lookup.d.ts +0 -38
  159. package/dist/types/snaps/endowments/network-access.d.ts +0 -14
  160. package/dist/types/snaps/endowments/rpc.d.ts +0 -38
  161. package/dist/types/snaps/endowments/transaction-insight.d.ts +0 -39
  162. package/dist/types/snaps/endowments/web-assembly.d.ts +0 -14
  163. package/dist/types/snaps/permissions.d.ts +0 -16
@@ -64,20 +64,17 @@ function _define_property(obj, key, value) {
64
64
  import { BaseController } from '@metamask/base-controller';
65
65
  import { SubjectType } from '@metamask/permission-controller';
66
66
  import { rpcErrors } from '@metamask/rpc-errors';
67
- import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/snaps-rpc-methods';
67
+ import { WALLET_SNAP_PERMISSION_KEY, getMaxRequestTimeCaveat, handlerEndowments, SnapEndowments, getKeyringCaveatOrigins, getRpcCaveatOrigins, processSnapPermissions } 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 { assertIsSnapManifest, assertIsValidSnapId, DEFAULT_ENDOWMENTS, DEFAULT_REQUESTED_SNAP_VERSION, encodeAuxiliaryFile, HandlerType, isOriginAllowed, logError, normalizeRelative, OnTransactionResponseStruct, OnSignatureResponseStruct, resolveVersionRange, SnapCaveatType, SnapStatus, SnapStatusEvents, unwrapError, OnHomePageResponseStruct, getValidatedLocalizationFiles, VirtualFile, NpmSnapFileNames, OnNameLookupResponseStruct, getLocalizedSnapManifest } 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';
76
- import { handlerEndowments, SnapEndowments } from './endowments';
77
- import { getKeyringCaveatOrigins } from './endowments/keyring';
78
- import { getRpcCaveatOrigins } from './endowments/rpc';
75
+ import { fetchSnap, hasTimedOut, setDiff, withTimeout } from '../utils';
76
+ import { ALLOWED_PERMISSIONS } from './constants';
79
77
  import { detectSnapLocation } from './location';
80
- import { processSnapPermissions } from './permissions';
81
78
  import { SnapsRegistryStatus } from './registry';
82
79
  import { RequestQueue } from './RequestQueue';
83
80
  import { Timer } from './Timer';
@@ -125,7 +122,7 @@ var _closeAllConnections = /*#__PURE__*/ new WeakMap(), _dynamicPermissions = /*
125
122
  _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
126
123
  * Constructor helper for registering the controller's messaging system
127
124
  * actions.
128
- */ _registerMessageHandlers = /*#__PURE__*/ new WeakSet(), _pollForLastRequestStatus = /*#__PURE__*/ new WeakSet(), _blockSnap = /*#__PURE__*/ new WeakSet(), /**
125
+ */ _registerMessageHandlers = /*#__PURE__*/ new WeakSet(), _handlePreinstalledSnaps = /*#__PURE__*/ new WeakSet(), _pollForLastRequestStatus = /*#__PURE__*/ new WeakSet(), _blockSnap = /*#__PURE__*/ new WeakSet(), /**
129
126
  * Unblocks a snap so that it can be enabled and started again. Emits
130
127
  * {@link SnapUnblocked}. Does nothing if the snap is not installed or already
131
128
  * unblocked.
@@ -142,7 +139,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
142
139
  *
143
140
  * @param snapId - The id of the snap to transition.
144
141
  * @param event - The event enum to use to transition.
145
- */ _transition = /*#__PURE__*/ new WeakSet(), _terminateSnap = /*#__PURE__*/ new WeakSet(), /**
142
+ */ _transition = /*#__PURE__*/ new WeakSet(), _terminateSnap = /*#__PURE__*/ new WeakSet(), _handleInitialConnections = /*#__PURE__*/ new WeakSet(), _addSnapToSubject = /*#__PURE__*/ new WeakSet(), /**
146
143
  * Removes a snap's permission (caveat) from all subjects.
147
144
  *
148
145
  * @param snapId - The id of the Snap.
@@ -163,12 +160,20 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
163
160
  *
164
161
  * @param args - The add snap args.
165
162
  * @returns The resulting snap object.
166
- */ _set = /*#__PURE__*/ new WeakSet(), _fetchSnap = /*#__PURE__*/ new WeakSet(), _validateSnapPermissions = /*#__PURE__*/ new WeakSet(), /**
163
+ */ _set = /*#__PURE__*/ new WeakSet(), _validateSnapPermissions = /*#__PURE__*/ new WeakSet(), /**
164
+ * Determine the execution timeout for a given handler permission.
165
+ *
166
+ * If no permission is specified or the permission itself has no execution timeout defined
167
+ * the constructor argument `maxRequestTime` will be used.
168
+ *
169
+ * @param permission - An optional permission constraint for the handler being called.
170
+ * @returns The execution timeout for the given handler.
171
+ */ _getExecutionTimeout = /*#__PURE__*/ new WeakSet(), /**
167
172
  * Gets the RPC message handler for the given snap.
168
173
  *
169
174
  * @param snapId - The id of the Snap whose message handler to get.
170
175
  * @returns The RPC handler for the given snap.
171
- */ _getRpcRequestHandler = /*#__PURE__*/ new WeakSet(), _triggerPhishingListUpdate = /*#__PURE__*/ new WeakSet(), _checkPhishingList = /*#__PURE__*/ new WeakSet(), _assertSnapRpcRequestResult = /*#__PURE__*/ new WeakSet(), _executeWithTimeout = /*#__PURE__*/ new WeakSet(), _recordSnapRpcRequestStart = /*#__PURE__*/ new WeakSet(), _recordSnapRpcRequestFinish = /*#__PURE__*/ new WeakSet(), /**
176
+ */ _getRpcRequestHandler = /*#__PURE__*/ new WeakSet(), _createInterface = /*#__PURE__*/ new WeakSet(), _assertInterfaceExists = /*#__PURE__*/ new WeakSet(), _transformSnapRpcRequestResult = /*#__PURE__*/ new WeakSet(), _assertSnapRpcRequestResult = /*#__PURE__*/ new WeakSet(), _executeWithTimeout = /*#__PURE__*/ new WeakSet(), _recordSnapRpcRequestStart = /*#__PURE__*/ new WeakSet(), _recordSnapRpcRequestFinish = /*#__PURE__*/ new WeakSet(), /**
172
177
  * Retrieves the rollback snapshot of a snap.
173
178
  *
174
179
  * @param snapId - The snap id.
@@ -181,6 +186,16 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
181
186
  * @throws {@link Error}. If the snap exists before creation or if creation fails.
182
187
  * @returns A `RollbackSnapshot`.
183
188
  */ _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(), /**
189
+ * Updates the permissions for a snap following an install, update or rollback.
190
+ *
191
+ * Grants newly requested permissions and revokes unused/revoked permissions.
192
+ *
193
+ * @param args - An options bag.
194
+ * @param args.snapId - The snap ID.
195
+ * @param args.newPermissions - New permissions to be granted.
196
+ * @param args.unusedPermissions - Unused permissions to be revoked.
197
+ * @param args.requestData - Optional request data from an approval.
198
+ */ _updatePermissions = /*#__PURE__*/ new WeakSet(), /**
184
199
  * Checks if a snap will pass version validation checks
185
200
  * with the new version range that is requested. The first
186
201
  * check that is done is to check if the existing snap version
@@ -471,6 +486,10 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
471
486
  if (!Array.isArray(snapIds)) {
472
487
  throw new Error('Expected array of snap ids.');
473
488
  }
489
+ snapIds.forEach((snapId)=>{
490
+ const snap = this.getExpect(snapId);
491
+ assert(snap.removable !== false, `${snapId} is not removable.`);
492
+ });
474
493
  await Promise.all(snapIds.map(async (snapId)=>{
475
494
  const snap = this.getExpect(snapId);
476
495
  const truncated = this.getTruncatedExpect(snapId);
@@ -664,6 +683,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
664
683
  snapId,
665
684
  type: SNAP_APPROVAL_INSTALL
666
685
  });
686
+ this.messagingSystem.publish('SnapController:snapInstallStarted', snapId, origin, false);
667
687
  // Existing snaps must be stopped before overwriting
668
688
  if (existingSnap && this.isRunning(snapId)) {
669
689
  await this.stopSnap(snapId, SnapStatusEvents.Stop);
@@ -697,11 +717,13 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
697
717
  return truncated;
698
718
  } catch (error) {
699
719
  logError(`Error when adding ${snapId}.`, error);
720
+ const errorString = error instanceof Error ? error.message : error.toString();
700
721
  _class_private_method_get(this, _updateApproval, updateApproval).call(this, pendingApproval.id, {
701
722
  loading: false,
702
723
  type: SNAP_APPROVAL_INSTALL,
703
- error: error instanceof Error ? error.message : error.toString()
724
+ error: errorString
704
725
  });
726
+ this.messagingSystem.publish('SnapController:snapInstallFailed', snapId, origin, false, errorString);
705
727
  throw error;
706
728
  }
707
729
  }
@@ -733,9 +755,11 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
733
755
  type: SNAP_APPROVAL_UPDATE
734
756
  });
735
757
  try {
758
+ this.messagingSystem.publish('SnapController:snapInstallStarted', snapId, origin, true);
736
759
  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;
760
+ const oldManifest = snap.manifest;
761
+ const newSnap = await fetchSnap(snapId, location);
762
+ const { sourceCode: sourceCodeFile, manifest: manifestFile } = newSnap;
739
763
  const manifest = manifestFile.result;
740
764
  const newVersion = manifest.version;
741
765
  if (!gtVersion(newVersion, snap.version)) {
@@ -746,7 +770,8 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
746
770
  }
747
771
  await _class_private_method_get(this, _assertIsInstallAllowed, assertIsInstallAllowed).call(this, snapId, {
748
772
  version: newVersion,
749
- checksum: manifest.source.shasum
773
+ checksum: manifest.source.shasum,
774
+ permissions: manifest.initialPermissions
750
775
  });
751
776
  const processedPermissions = processSnapPermissions(manifest.initialPermissions);
752
777
  _class_private_method_get(this, _validateSnapPermissions, validateSnapPermissions).call(this, processedPermissions);
@@ -772,28 +797,22 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
772
797
  _class_private_method_get(this, _set, set).call(this, {
773
798
  origin,
774
799
  id: snapId,
775
- files: newSnap.files,
800
+ files: newSnap,
776
801
  isUpdate: true
777
802
  });
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
- });
803
+ _class_private_method_get(this, _updatePermissions, updatePermissions).call(this, {
804
+ snapId,
805
+ unusedPermissions,
806
+ newPermissions: approvedNewPermissions,
807
+ requestData
808
+ });
809
+ if (manifest.initialConnections) {
810
+ _class_private_method_get(this, _handleInitialConnections, handleInitialConnections).call(this, snapId, oldManifest.initialConnections ?? null, manifest.initialConnections);
792
811
  }
793
812
  const rollbackSnapshot = _class_private_method_get(this, _getRollbackSnapshot, getRollbackSnapshot).call(this, snapId);
794
813
  if (rollbackSnapshot !== undefined) {
795
814
  rollbackSnapshot.permissions.revoked = unusedPermissions;
796
- rollbackSnapshot.permissions.granted = Object.keys(approvedNewPermissions);
815
+ rollbackSnapshot.permissions.granted = approvedNewPermissions;
797
816
  rollbackSnapshot.permissions.requestData = requestData;
798
817
  }
799
818
  const sourceCode = sourceCodeFile.toString();
@@ -817,11 +836,13 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
817
836
  return truncatedSnap;
818
837
  } catch (error) {
819
838
  logError(`Error when updating ${snapId},`, error);
839
+ const errorString = error instanceof Error ? error.message : error.toString();
820
840
  _class_private_method_get(this, _updateApproval, updateApproval).call(this, pendingApproval.id, {
821
841
  loading: false,
822
- error: error instanceof Error ? error.message : error.toString(),
842
+ error: errorString,
823
843
  type: SNAP_APPROVAL_UPDATE
824
844
  });
845
+ this.messagingSystem.publish('SnapController:snapInstallFailed', snapId, origin, true, errorString);
825
846
  throw error;
826
847
  }
827
848
  }
@@ -856,14 +877,13 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
856
877
  permissions: processedPermissions
857
878
  });
858
879
  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
- });
880
+ _class_private_method_get(this, _updatePermissions, updatePermissions).call(this, {
881
+ snapId,
882
+ newPermissions: approvedPermissions,
883
+ requestData
884
+ });
885
+ if (snap.manifest.initialConnections) {
886
+ _class_private_method_get(this, _handleInitialConnections, handleInitialConnections).call(this, snapId, null, snap.manifest.initialConnections);
867
887
  }
868
888
  } finally{
869
889
  const runtime = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
@@ -898,34 +918,37 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
898
918
  };
899
919
  assertIsJsonRpcRequest(request);
900
920
  const permissionName = handlerEndowments[handlerType];
901
- const hasPermission = this.messagingSystem.call('PermissionController:hasPermission', snapId, permissionName);
902
- if (!hasPermission) {
921
+ assert(typeof permissionName === 'string' || permissionName === null, "'permissionName' must be either a string or null.");
922
+ const permissions = this.messagingSystem.call('PermissionController:getPermissions', snapId);
923
+ // If permissionName is null, the handler does not require a permission.
924
+ if (permissionName !== null && (!permissions || !hasProperty(permissions, permissionName))) {
903
925
  throw new Error(`Snap "${snapId}" is not permitted to use "${permissionName}".`);
904
926
  }
927
+ const handlerPermissions = permissionName ? permissions[permissionName] : undefined;
905
928
  if (permissionName === SnapEndowments.Rpc || permissionName === SnapEndowments.Keyring) {
906
- const subject = this.messagingSystem.call('SubjectMetadataController:getSubjectMetadata', origin);
907
- const permissions = this.messagingSystem.call('PermissionController:getPermissions', snapId);
908
- const handlerPermissions = permissions?.[permissionName];
909
929
  assert(handlerPermissions);
930
+ const subject = this.messagingSystem.call('SubjectMetadataController:getSubjectMetadata', origin);
910
931
  const origins = permissionName === SnapEndowments.Rpc ? getRpcCaveatOrigins(handlerPermissions) : getKeyringCaveatOrigins(handlerPermissions);
911
932
  assert(origins);
912
933
  if (!isOriginAllowed(origins, subject?.subjectType ?? SubjectType.Website, origin)) {
913
934
  throw new Error(`Snap "${snapId}" is not permitted to handle requests from "${origin}".`);
914
935
  }
915
936
  }
916
- const handler = await _class_private_method_get(this, _getRpcRequestHandler, getRpcRequestHandler).call(this, snapId);
937
+ const handler = _class_private_method_get(this, _getRpcRequestHandler, getRpcRequestHandler).call(this, snapId);
917
938
  if (!handler) {
918
939
  throw new Error(`Snap RPC message handler not found for snap "${snapId}".`);
919
940
  }
941
+ const timeout = _class_private_method_get(this, _getExecutionTimeout, getExecutionTimeout).call(this, handlerPermissions);
920
942
  return handler({
921
943
  origin,
922
944
  handler: handlerType,
923
- request
945
+ request,
946
+ timeout
924
947
  });
925
948
  }
926
949
  constructor({ closeAllConnections, messenger, state, dynamicPermissions = [
927
950
  '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 }){
951
+ ], 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
952
  super({
930
953
  messenger,
931
954
  metadata: {
@@ -963,6 +986,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
963
986
  });
964
987
  _class_private_method_init(this, _initializeStateMachine);
965
988
  _class_private_method_init(this, _registerMessageHandlers);
989
+ _class_private_method_init(this, _handlePreinstalledSnaps);
966
990
  _class_private_method_init(this, _pollForLastRequestStatus);
967
991
  /**
968
992
  * Blocks an installed snap and prevents it from being started again. Emits
@@ -980,6 +1004,8 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
980
1004
  *
981
1005
  * @param snapId - The snap to terminate.
982
1006
  */ _class_private_method_init(this, _terminateSnap);
1007
+ _class_private_method_init(this, _handleInitialConnections);
1008
+ _class_private_method_init(this, _addSnapToSubject);
983
1009
  _class_private_method_init(this, _removeSnapFromSubjects);
984
1010
  _class_private_method_init(this, _revokeAllSnapPermissions);
985
1011
  _class_private_method_init(this, _createApproval);
@@ -1007,20 +1033,29 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
1007
1033
  * @returns An array of the names of the endowments.
1008
1034
  */ _class_private_method_init(this, _getEndowments);
1009
1035
  _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
1036
  _class_private_method_init(this, _validateSnapPermissions);
1037
+ _class_private_method_init(this, _getExecutionTimeout);
1018
1038
  _class_private_method_init(this, _getRpcRequestHandler);
1019
- _class_private_method_init(this, _triggerPhishingListUpdate);
1020
- _class_private_method_init(this, _checkPhishingList);
1021
1039
  /**
1022
- * Asserts that the returned result of a Snap RPC call is the expected shape.
1040
+ * Create a dynamic interface in the SnapInterfaceController.
1041
+ *
1042
+ * @param snapId - The snap ID.
1043
+ * @param content - The initial interface content.
1044
+ * @returns An identifier that can be used to identify the interface.
1045
+ */ _class_private_method_init(this, _createInterface);
1046
+ _class_private_method_init(this, _assertInterfaceExists);
1047
+ /**
1048
+ * Transform a RPC request result if necessary.
1049
+ *
1050
+ * @param snapId - The snap ID of the snap that produced the result.
1051
+ * @param handlerType - The handler type that produced the result.
1052
+ * @param result - The result.
1053
+ * @returns The transformed result if applicable, otherwise the original result.
1054
+ */ _class_private_method_init(this, _transformSnapRpcRequestResult);
1055
+ /**
1056
+ * Assert that the returned result of a Snap RPC call is the expected shape.
1023
1057
  *
1058
+ * @param snapId - The snap ID.
1024
1059
  * @param handlerType - The handler type of the RPC Request.
1025
1060
  * @param result - The result of the RPC request.
1026
1061
  */ _class_private_method_init(this, _assertSnapRpcRequestResult);
@@ -1058,6 +1093,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
1058
1093
  _class_private_method_init(this, _getRuntimeExpect);
1059
1094
  _class_private_method_init(this, _setupRuntime);
1060
1095
  _class_private_method_init(this, _calculatePermissionsChange);
1096
+ _class_private_method_init(this, _updatePermissions);
1061
1097
  _class_private_method_init(this, _isValidUpdate);
1062
1098
  /**
1063
1099
  * Call a lifecycle hook on a snap, if the snap has the
@@ -1154,7 +1190,10 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
1154
1190
  });
1155
1191
  _class_private_method_get(this, _initializeStateMachine, initializeStateMachine).call(this);
1156
1192
  _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));
1193
+ if (preinstalledSnaps) {
1194
+ _class_private_method_get(this, _handlePreinstalledSnaps, handlePreinstalledSnaps).call(this, preinstalledSnaps);
1195
+ }
1196
+ Object.values(this.state?.snaps ?? {}).forEach((snap)=>_class_private_method_get(this, _setupRuntime, setupRuntime).call(this, snap.id));
1158
1197
  }
1159
1198
  }
1160
1199
  function initializeStateMachine() {
@@ -1231,6 +1270,63 @@ function registerMessageHandlers() {
1231
1270
  this.messagingSystem.registerActionHandler(`${controllerName}:revokeDynamicPermissions`, (...args)=>this.revokeDynamicSnapPermissions(...args));
1232
1271
  this.messagingSystem.registerActionHandler(`${controllerName}:getFile`, async (...args)=>this.getSnapFile(...args));
1233
1272
  }
1273
+ function handlePreinstalledSnaps(preinstalledSnaps) {
1274
+ for (const { snapId, manifest, files, removable } of preinstalledSnaps){
1275
+ const existingSnap = this.get(snapId);
1276
+ const isAlreadyInstalled = existingSnap !== undefined;
1277
+ const isUpdate = isAlreadyInstalled && gtVersion(manifest.version, existingSnap.version);
1278
+ // Disallow downgrades and overwriting non preinstalled snaps
1279
+ if (isAlreadyInstalled && (!isUpdate || existingSnap.preinstalled !== true)) {
1280
+ continue;
1281
+ }
1282
+ const manifestFile = new VirtualFile({
1283
+ path: NpmSnapFileNames.Manifest,
1284
+ value: JSON.stringify(manifest),
1285
+ result: manifest
1286
+ });
1287
+ const virtualFiles = files.map(({ path, value })=>new VirtualFile({
1288
+ value,
1289
+ path
1290
+ }));
1291
+ const { filePath, iconPath } = manifest.source.location.npm;
1292
+ const sourceCode = virtualFiles.find((file)=>file.path === filePath);
1293
+ const svgIcon = iconPath ? virtualFiles.find((file)=>file.path === iconPath) : undefined;
1294
+ assert(sourceCode, 'Source code not provided for preinstalled snap.');
1295
+ assert(!iconPath || iconPath && svgIcon, 'Icon not provided for preinstalled snap.');
1296
+ assert(manifest.source.files === undefined, 'Auxiliary files are not currently supported for preinstalled snaps.');
1297
+ const localizationFiles = manifest.source.locales?.map((path)=>virtualFiles.find((file)=>file.path === path)) ?? [];
1298
+ const validatedLocalizationFiles = getValidatedLocalizationFiles(localizationFiles.filter(Boolean));
1299
+ assert(localizationFiles.length === validatedLocalizationFiles.length, 'Missing localization files for preinstalled snap.');
1300
+ const filesObject = {
1301
+ manifest: manifestFile,
1302
+ sourceCode,
1303
+ svgIcon,
1304
+ auxiliaryFiles: [],
1305
+ localizationFiles: validatedLocalizationFiles
1306
+ };
1307
+ // Add snap to the SnapController state
1308
+ _class_private_method_get(this, _set, set).call(this, {
1309
+ id: snapId,
1310
+ origin: 'metamask',
1311
+ files: filesObject,
1312
+ removable,
1313
+ preinstalled: true
1314
+ });
1315
+ // Setup permissions
1316
+ const processedPermissions = processSnapPermissions(manifest.initialPermissions);
1317
+ _class_private_method_get(this, _validateSnapPermissions, validateSnapPermissions).call(this, processedPermissions);
1318
+ const { newPermissions, unusedPermissions } = _class_private_method_get(this, _calculatePermissionsChange, calculatePermissionsChange).call(this, snapId, processedPermissions);
1319
+ _class_private_method_get(this, _updatePermissions, updatePermissions).call(this, {
1320
+ snapId,
1321
+ newPermissions,
1322
+ unusedPermissions
1323
+ });
1324
+ // Set status
1325
+ this.update((state)=>{
1326
+ state.snaps[snapId].status = SnapStatus.Stopped;
1327
+ });
1328
+ }
1329
+ }
1234
1330
  function pollForLastRequestStatus() {
1235
1331
  _class_private_field_set(this, _timeoutForLastRequestStatus, setTimeout(()=>{
1236
1332
  _class_private_method_get(this, _stopSnapsLastRequestPastMax, stopSnapsLastRequestPastMax).call(this).catch((error)=>{
@@ -1272,7 +1368,9 @@ async function assertIsInstallAllowed(snapId, snapInfo) {
1272
1368
  const result = results[snapId];
1273
1369
  if (result.status === SnapsRegistryStatus.Blocked) {
1274
1370
  throw new Error(`Cannot install version "${snapInfo.version}" of snap "${snapId}": The version is blocked. ${result.reason?.explanation ?? ''}`);
1275
- } else if (_class_private_field_get(this, _featureFlags).requireAllowlist && result.status !== SnapsRegistryStatus.Verified) {
1371
+ }
1372
+ const isAllowlistingRequired = Object.keys(snapInfo.permissions).some((permission)=>!ALLOWED_PERMISSIONS.includes(permission));
1373
+ if (_class_private_field_get(this, _featureFlags).requireAllowlist && isAllowlistingRequired && result.status !== SnapsRegistryStatus.Verified) {
1276
1374
  throw new Error(`Cannot install version "${snapInfo.version}" of snap "${snapId}": The snap is not on the allowlist.`);
1277
1375
  }
1278
1376
  }
@@ -1293,6 +1391,52 @@ async function terminateSnap(snapId) {
1293
1391
  await this.messagingSystem.call('ExecutionService:terminateSnap', snapId);
1294
1392
  this.messagingSystem.publish('SnapController:snapTerminated', this.getTruncatedExpect(snapId));
1295
1393
  }
1394
+ function handleInitialConnections(snapId, previousInitialConnections, initialConnections) {
1395
+ if (previousInitialConnections) {
1396
+ const revokedInitialConnections = setDiff(previousInitialConnections, initialConnections);
1397
+ for (const origin of Object.keys(revokedInitialConnections)){
1398
+ this.removeSnapFromSubject(origin, snapId);
1399
+ }
1400
+ }
1401
+ for (const origin of Object.keys(initialConnections)){
1402
+ _class_private_method_get(this, _addSnapToSubject, addSnapToSubject).call(this, origin, snapId);
1403
+ }
1404
+ }
1405
+ function addSnapToSubject(origin, snapId) {
1406
+ const subjectPermissions = this.messagingSystem.call('PermissionController:getPermissions', origin);
1407
+ const existingCaveat = subjectPermissions?.[WALLET_SNAP_PERMISSION_KEY]?.caveats?.find((caveat)=>caveat.type === SnapCaveatType.SnapIds);
1408
+ const subjectHasSnap = Boolean((existingCaveat?.value)?.[snapId]);
1409
+ // If the subject is already connected to the snap, this is a no-op.
1410
+ if (subjectHasSnap) {
1411
+ return;
1412
+ }
1413
+ // If an existing caveat exists, we add the snap to that.
1414
+ if (existingCaveat) {
1415
+ this.messagingSystem.call('PermissionController:updateCaveat', origin, WALLET_SNAP_PERMISSION_KEY, SnapCaveatType.SnapIds, {
1416
+ ...existingCaveat,
1417
+ [snapId]: {}
1418
+ });
1419
+ return;
1420
+ }
1421
+ const approvedPermissions = {
1422
+ [WALLET_SNAP_PERMISSION_KEY]: {
1423
+ caveats: [
1424
+ {
1425
+ type: SnapCaveatType.SnapIds,
1426
+ value: {
1427
+ [snapId]: {}
1428
+ }
1429
+ }
1430
+ ]
1431
+ }
1432
+ };
1433
+ this.messagingSystem.call('PermissionController:grantPermissions', {
1434
+ approvedPermissions,
1435
+ subject: {
1436
+ origin
1437
+ }
1438
+ });
1439
+ }
1296
1440
  function removeSnapFromSubjects(snapId) {
1297
1441
  const subjects = this.messagingSystem.call('PermissionController:getSubjectNames');
1298
1442
  for (const subject of subjects){
@@ -1350,18 +1494,19 @@ async function add(args) {
1350
1494
  // If fetching and setting the snap succeeds, this property will be set
1351
1495
  // to null in the authorize() method.
1352
1496
  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;
1497
+ const fetchedSnap = await fetchSnap(snapId, location);
1498
+ const manifest = fetchedSnap.manifest.result;
1355
1499
  if (!satisfiesVersionRange(manifest.version, versionRange)) {
1356
1500
  throw new Error(`Version mismatch. Manifest for "${snapId}" specifies version "${manifest.version}" which doesn't satisfy requested version range "${versionRange}".`);
1357
1501
  }
1358
1502
  await _class_private_method_get(this, _assertIsInstallAllowed, assertIsInstallAllowed).call(this, snapId, {
1359
1503
  version: manifest.version,
1360
- checksum: manifest.source.shasum
1504
+ checksum: manifest.source.shasum,
1505
+ permissions: manifest.initialPermissions
1361
1506
  });
1362
1507
  return _class_private_method_get(this, _set, set).call(this, {
1363
1508
  ...args,
1364
- ...fetchedSnap,
1509
+ files: fetchedSnap,
1365
1510
  id: snapId
1366
1511
  });
1367
1512
  })();
@@ -1424,10 +1569,10 @@ async function getEndowments(snapId) {
1424
1569
  return dedupedEndowments;
1425
1570
  }
1426
1571
  function set(args) {
1427
- const { id: snapId, origin, files, isUpdate = false } = args;
1572
+ const { id: snapId, origin, files, isUpdate = false, removable, preinstalled } = args;
1428
1573
  const { manifest, sourceCode: sourceCodeFile, svgIcon, auxiliaryFiles: rawAuxiliaryFiles, localizationFiles } = files;
1429
1574
  assertIsSnapManifest(manifest.result);
1430
- const { version, proposedName } = manifest.result;
1575
+ const { version } = manifest.result;
1431
1576
  const sourceCode = sourceCodeFile.toString();
1432
1577
  assert(typeof sourceCode === 'string' && sourceCode.length > 0, `Invalid source code for snap "${snapId}".`);
1433
1578
  const auxiliaryFiles = rawAuxiliaryFiles.map((file)=>{
@@ -1448,6 +1593,7 @@ function set(args) {
1448
1593
  origin
1449
1594
  }
1450
1595
  ];
1596
+ const localizedFiles = localizationFiles.map((file)=>file.result);
1451
1597
  const snap = {
1452
1598
  // Restore relevant snap state if it exists
1453
1599
  ...existingSnap,
@@ -1455,6 +1601,8 @@ function set(args) {
1455
1601
  // previous state.
1456
1602
  blocked: false,
1457
1603
  enabled: true,
1604
+ removable,
1605
+ preinstalled,
1458
1606
  id: snapId,
1459
1607
  initialPermissions: manifest.result.initialPermissions,
1460
1608
  manifest: manifest.result,
@@ -1463,7 +1611,7 @@ function set(args) {
1463
1611
  version,
1464
1612
  versionHistory,
1465
1613
  auxiliaryFiles,
1466
- localizationFiles: localizationFiles.map((file)=>file.result)
1614
+ localizationFiles: localizedFiles
1467
1615
  };
1468
1616
  // If the snap was blocked, it isn't any longer
1469
1617
  delete snap.blockInformation;
@@ -1479,6 +1627,9 @@ function set(args) {
1479
1627
  rollbackSnapshot.statePatches = inversePatches;
1480
1628
  }
1481
1629
  }
1630
+ // In case the Snap uses a localized manifest, we need to get the
1631
+ // proposed name from the localized manifest.
1632
+ const { proposedName } = getLocalizedSnapManifest(manifest.result, 'en', localizedFiles);
1482
1633
  this.messagingSystem.call('SubjectMetadataController:addSubjectMetadata', {
1483
1634
  subjectType: SubjectType.Snap,
1484
1635
  name: proposedName,
@@ -1491,40 +1642,10 @@ function set(args) {
1491
1642
  sourceCode
1492
1643
  };
1493
1644
  }
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
1645
  function validateSnapPermissions(processedPermissions) {
1525
1646
  const permissionKeys = Object.keys(processedPermissions);
1526
1647
  const handlerPermissions = Array.from(new Set(Object.values(handlerEndowments)));
1527
- assert(permissionKeys.some((key)=>handlerPermissions.includes(key)), `A snap must request at least one of the following permissions: ${handlerPermissions.join(', ')}.`);
1648
+ assert(permissionKeys.some((key)=>handlerPermissions.includes(key)), `A snap must request at least one of the following permissions: ${handlerPermissions.filter((handler)=>handler !== null).join(', ')}.`);
1528
1649
  const excludedPermissionErrors = permissionKeys.reduce((errors, permission)=>{
1529
1650
  if (hasProperty(_class_private_field_get(this, _excludedPermissions), permission)) {
1530
1651
  errors.push(_class_private_field_get(this, _excludedPermissions)[permission]);
@@ -1533,6 +1654,9 @@ function validateSnapPermissions(processedPermissions) {
1533
1654
  }, []);
1534
1655
  assert(excludedPermissionErrors.length === 0, `One or more permissions are not allowed:\n${excludedPermissionErrors.join('\n')}`);
1535
1656
  }
1657
+ function getExecutionTimeout(permission) {
1658
+ return getMaxRequestTimeCaveat(permission) ?? this.maxRequestTime;
1659
+ }
1536
1660
  function getRpcRequestHandler(snapId) {
1537
1661
  const runtime = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
1538
1662
  const existingHandler = runtime.rpcHandler;
@@ -1543,7 +1667,7 @@ function getRpcRequestHandler(snapId) {
1543
1667
  // We need to set up this promise map to map snapIds to their respective startPromises,
1544
1668
  // because otherwise we would lose context on the correct startPromise.
1545
1669
  const startPromises = new Map();
1546
- const rpcHandler = async ({ origin, handler: handlerType, request })=>{
1670
+ const rpcHandler = async ({ origin, handler: handlerType, request, timeout })=>{
1547
1671
  if (this.state.snaps[snapId].enabled === false) {
1548
1672
  throw new Error(`Snap "${snapId}" is disabled.`);
1549
1673
  }
@@ -1569,7 +1693,7 @@ function getRpcRequestHandler(snapId) {
1569
1693
  }
1570
1694
  }
1571
1695
  }
1572
- const timer = new Timer(this.maxRequestTime);
1696
+ const timer = new Timer(timeout);
1573
1697
  _class_private_method_get(this, _recordSnapRpcRequestStart, recordSnapRpcRequestStart).call(this, snapId, request.id, timer);
1574
1698
  const handleRpcRequestPromise = this.messagingSystem.call('ExecutionService:handleRpcRequest', snapId, {
1575
1699
  origin,
@@ -1579,8 +1703,8 @@ function getRpcRequestHandler(snapId) {
1579
1703
  // This will either get the result or reject due to the timeout.
1580
1704
  try {
1581
1705
  const result = await _class_private_method_get(this, _executeWithTimeout, executeWithTimeout).call(this, handleRpcRequestPromise, timer);
1582
- await _class_private_method_get(this, _assertSnapRpcRequestResult, assertSnapRpcRequestResult).call(this, handlerType, result);
1583
- return result;
1706
+ await _class_private_method_get(this, _assertSnapRpcRequestResult, assertSnapRpcRequestResult).call(this, snapId, handlerType, result);
1707
+ return _class_private_method_get(this, _transformSnapRpcRequestResult, transformSnapRpcRequestResult).call(this, snapId, handlerType, result);
1584
1708
  } catch (error) {
1585
1709
  const [jsonRpcError, handled] = unwrapError(error);
1586
1710
  if (!handled) {
@@ -1594,29 +1718,64 @@ function getRpcRequestHandler(snapId) {
1594
1718
  runtime.rpcHandler = rpcHandler;
1595
1719
  return rpcHandler;
1596
1720
  }
1597
- async function triggerPhishingListUpdate() {
1598
- return this.messagingSystem.call('PhishingController:maybeUpdateState');
1721
+ async function createInterface(snapId, content) {
1722
+ return this.messagingSystem.call('SnapInterfaceController:createInterface', snapId, content);
1723
+ }
1724
+ function assertInterfaceExists(snapId, id) {
1725
+ // This will throw if the interface isn't accessible, but we assert nevertheless.
1726
+ assert(this.messagingSystem.call('SnapInterfaceController:getInterface', snapId, id));
1599
1727
  }
1600
- function checkPhishingList(origin) {
1601
- return this.messagingSystem.call('PhishingController:testOrigin', origin).result;
1728
+ async function transformSnapRpcRequestResult(snapId, handlerType, result) {
1729
+ switch(handlerType){
1730
+ case HandlerType.OnTransaction:
1731
+ case HandlerType.OnSignature:
1732
+ case HandlerType.OnHomePage:
1733
+ {
1734
+ // Since this type has been asserted earlier we can cast
1735
+ const castResult = result;
1736
+ // If a handler returns static content, we turn it into a dynamic UI
1737
+ if (castResult && hasProperty(castResult, 'content')) {
1738
+ const { content, ...rest } = castResult;
1739
+ const id = await _class_private_method_get(this, _createInterface, createInterface).call(this, snapId, content);
1740
+ return {
1741
+ ...rest,
1742
+ id
1743
+ };
1744
+ }
1745
+ return result;
1746
+ }
1747
+ default:
1748
+ return result;
1749
+ }
1602
1750
  }
1603
- async function assertSnapRpcRequestResult(handlerType, result) {
1751
+ async function assertSnapRpcRequestResult(snapId, handlerType, result) {
1604
1752
  switch(handlerType){
1605
1753
  case HandlerType.OnTransaction:
1606
1754
  {
1607
1755
  assertStruct(result, OnTransactionResponseStruct);
1608
- // Null is an allowed return value here
1609
- if (result === null) {
1610
- return;
1756
+ if (result && hasProperty(result, 'id')) {
1757
+ _class_private_method_get(this, _assertInterfaceExists, assertInterfaceExists).call(this, snapId, result.id);
1758
+ }
1759
+ break;
1760
+ }
1761
+ case HandlerType.OnSignature:
1762
+ {
1763
+ assertStruct(result, OnSignatureResponseStruct);
1764
+ if (result && hasProperty(result, 'id')) {
1765
+ _class_private_method_get(this, _assertInterfaceExists, assertInterfaceExists).call(this, snapId, result.id);
1611
1766
  }
1612
- await _class_private_method_get(this, _triggerPhishingListUpdate, triggerPhishingListUpdate).call(this);
1613
- validateComponentLinks(result.content, _class_private_method_get(this, _checkPhishingList, checkPhishingList).bind(this));
1614
1767
  break;
1615
1768
  }
1616
1769
  case HandlerType.OnHomePage:
1617
- assertStruct(result, OnHomePageResponseStruct);
1618
- await _class_private_method_get(this, _triggerPhishingListUpdate, triggerPhishingListUpdate).call(this);
1619
- validateComponentLinks(result.content, _class_private_method_get(this, _checkPhishingList, checkPhishingList).bind(this));
1770
+ {
1771
+ assertStruct(result, OnHomePageResponseStruct);
1772
+ if (result && hasProperty(result, 'id')) {
1773
+ _class_private_method_get(this, _assertInterfaceExists, assertInterfaceExists).call(this, snapId, result.id);
1774
+ }
1775
+ break;
1776
+ }
1777
+ case HandlerType.OnNameLookup:
1778
+ assertStruct(result, OnNameLookupResponseStruct);
1620
1779
  break;
1621
1780
  default:
1622
1781
  break;
@@ -1651,11 +1810,7 @@ function createRollbackSnapshot(snapId) {
1651
1810
  assert(_class_private_field_get(this, _rollbackSnapshots).get(snapId) === undefined, new Error(`Snap "${snapId}" rollback snapshot already exists.`));
1652
1811
  _class_private_field_get(this, _rollbackSnapshots).set(snapId, {
1653
1812
  statePatches: [],
1654
- permissions: {
1655
- revoked: null,
1656
- granted: [],
1657
- requestData: null
1658
- },
1813
+ permissions: {},
1659
1814
  newVersion: ''
1660
1815
  });
1661
1816
  const newRollbackSnapshot = _class_private_field_get(this, _rollbackSnapshots).get(snapId);
@@ -1683,20 +1838,12 @@ async function rollbackSnap(snapId) {
1683
1838
  state.snaps[snapId].status = SnapStatus.Stopped;
1684
1839
  });
1685
1840
  }
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
- }
1841
+ _class_private_method_get(this, _updatePermissions, updatePermissions).call(this, {
1842
+ snapId,
1843
+ unusedPermissions: permissions.granted,
1844
+ newPermissions: permissions.revoked,
1845
+ requestData: permissions.requestData
1846
+ });
1700
1847
  const truncatedSnap = this.getTruncatedExpect(snapId);
1701
1848
  this.messagingSystem.publish('SnapController:snapRolledback', truncatedSnap, rollbackSnapshot.newVersion);
1702
1849
  _class_private_field_get(this, _rollbackSnapshots).delete(snapId);
@@ -1752,6 +1899,23 @@ function calculatePermissionsChange(snapId, desiredPermissionsSet) {
1752
1899
  approvedPermissions
1753
1900
  };
1754
1901
  }
1902
+ function updatePermissions({ snapId, unusedPermissions = {}, newPermissions = {}, requestData }) {
1903
+ const unusedPermissionsKeys = Object.keys(unusedPermissions);
1904
+ if (isNonEmptyArray(unusedPermissionsKeys)) {
1905
+ this.messagingSystem.call('PermissionController:revokePermissions', {
1906
+ [snapId]: unusedPermissionsKeys
1907
+ });
1908
+ }
1909
+ if (isNonEmptyArray(Object.keys(newPermissions))) {
1910
+ this.messagingSystem.call('PermissionController:grantPermissions', {
1911
+ approvedPermissions: newPermissions,
1912
+ subject: {
1913
+ origin: snapId
1914
+ },
1915
+ requestData
1916
+ });
1917
+ }
1918
+ }
1755
1919
  function isValidUpdate(snapId, newVersionRange) {
1756
1920
  const existingSnap = this.getExpect(snapId);
1757
1921
  if (satisfiesVersionRange(existingSnap.version, newVersionRange)) {
@@ -1764,6 +1928,7 @@ function isValidUpdate(snapId, newVersionRange) {
1764
1928
  }
1765
1929
  async function callLifecycleHook(snapId, handler) {
1766
1930
  const permissionName = handlerEndowments[handler];
1931
+ assert(permissionName, 'Lifecycle hook must have an endowment.');
1767
1932
  const hasPermission = this.messagingSystem.call('PermissionController:hasPermission', snapId, permissionName);
1768
1933
  if (!hasPermission) {
1769
1934
  return;