@metamask/snaps-controllers 3.1.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [3.3.0]
10
+ ### Added
11
+ - Add manifest localization functionality ([#1889](https://github.com/MetaMask/snaps/pull/1889))
12
+ - Add support for unencrypted storage using `snap_manageState` ([#1902](https://github.com/MetaMask/snaps/pull/1902))
13
+ - Add `OnHomePage` export ([#1896](https://github.com/MetaMask/snaps/pull/1896))
14
+
15
+ ## [3.2.0]
16
+ ### Added
17
+ - Add support for links in custom UI and notifications ([#1814](https://github.com/MetaMask/snaps/pull/1814))
18
+
19
+ ### Fixed
20
+ - Fix an issue where snaps throwing a `SnapError` would be allowed to run for longer than expected ([#1897](https://github.com/MetaMask/snaps/pull/1897))
21
+
9
22
  ## [3.1.1]
10
23
  ### Fixed
11
24
  - Fix a few issues with allowlist version resolving ([#1888](https://github.com/MetaMask/snaps/pull/1888))
@@ -94,7 +107,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
94
107
  - The version of the package no longer needs to match the version of all other
95
108
  MetaMask Snaps packages.
96
109
 
97
- [Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@3.1.1...HEAD
110
+ [Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@3.3.0...HEAD
111
+ [3.3.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@3.2.0...@metamask/snaps-controllers@3.3.0
112
+ [3.2.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@3.1.1...@metamask/snaps-controllers@3.2.0
98
113
  [3.1.1]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@3.1.0...@metamask/snaps-controllers@3.1.1
99
114
  [3.1.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@3.0.0...@metamask/snaps-controllers@3.1.0
100
115
  [3.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@2.0.2...@metamask/snaps-controllers@3.0.0
@@ -29,6 +29,7 @@ const _basecontroller = require("@metamask/base-controller");
29
29
  const _permissioncontroller = require("@metamask/permission-controller");
30
30
  const _rpcerrors = require("@metamask/rpc-errors");
31
31
  const _snapsrpcmethods = require("@metamask/snaps-rpc-methods");
32
+ const _snapsui = require("@metamask/snaps-ui");
32
33
  const _snapsutils = require("@metamask/snaps-utils");
33
34
  const _utils = require("@metamask/utils");
34
35
  const _fsm = require("@xstate/fsm");
@@ -120,7 +121,8 @@ const TRUNCATED_SNAP_PROPERTIES = new Set([
120
121
  ]);
121
122
  const defaultState = {
122
123
  snaps: {},
123
- snapStates: {}
124
+ snapStates: {},
125
+ unencryptedSnapStates: {}
124
126
  };
125
127
  /**
126
128
  * Truncates the properties of a snap to only ones that are easily serializable.
@@ -192,7 +194,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
192
194
  *
193
195
  * @param snapId - The id of the Snap whose message handler to get.
194
196
  * @returns The RPC handler for the given snap.
195
- */ _getRpcRequestHandler = /*#__PURE__*/ new WeakSet(), _executeWithTimeout = /*#__PURE__*/ new WeakSet(), _recordSnapRpcRequestStart = /*#__PURE__*/ new WeakSet(), _recordSnapRpcRequestFinish = /*#__PURE__*/ new WeakSet(), /**
197
+ */ _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(), /**
196
198
  * Retrieves the rollback snapshot of a snap.
197
199
  *
198
200
  * @param snapId - The snap id.
@@ -401,9 +403,14 @@ class SnapController extends _basecontroller.BaseControllerV2 {
401
403
  *
402
404
  * @param snapId - The id of the Snap whose state should be updated.
403
405
  * @param newSnapState - The new state of the snap.
404
- */ async updateSnapState(snapId, newSnapState) {
406
+ * @param encrypted - A flag to indicate whether to use encrypted storage or not.
407
+ */ updateSnapState(snapId, newSnapState, encrypted) {
405
408
  this.update((state)=>{
406
- state.snapStates[snapId] = newSnapState;
409
+ if (encrypted) {
410
+ state.snapStates[snapId] = newSnapState;
411
+ } else {
412
+ state.unencryptedSnapStates[snapId] = newSnapState;
413
+ }
407
414
  });
408
415
  }
409
416
  /**
@@ -411,9 +418,14 @@ class SnapController extends _basecontroller.BaseControllerV2 {
411
418
  * This is distinct from the state MetaMask uses to manage snaps.
412
419
  *
413
420
  * @param snapId - The id of the Snap whose state should be cleared.
414
- */ clearSnapState(snapId) {
421
+ * @param encrypted - A flag to indicate whether to use encrypted storage or not.
422
+ */ clearSnapState(snapId, encrypted) {
415
423
  this.update((state)=>{
416
- state.snapStates[snapId] = null;
424
+ if (encrypted) {
425
+ state.snapStates[snapId] = null;
426
+ } else {
427
+ state.unencryptedSnapStates[snapId] = null;
428
+ }
417
429
  });
418
430
  }
419
431
  /**
@@ -421,10 +433,10 @@ class SnapController extends _basecontroller.BaseControllerV2 {
421
433
  * This is distinct from the state MetaMask uses to manage snaps.
422
434
  *
423
435
  * @param snapId - The id of the Snap whose state to get.
424
- * @returns A promise that resolves with the decrypted snap state or null if no state exists.
425
- * @throws If the snap state decryption fails.
426
- */ async getSnapState(snapId) {
427
- const state = this.state.snapStates[snapId];
436
+ * @param encrypted - A flag to indicate whether to use encrypted storage or not.
437
+ * @returns The requested snap state or null if no state exists.
438
+ */ getSnapState(snapId, encrypted) {
439
+ const state = encrypted ? this.state.snapStates[snapId] : this.state.unencryptedSnapStates[snapId];
428
440
  return state ?? null;
429
441
  }
430
442
  /**
@@ -941,6 +953,10 @@ class SnapController extends _basecontroller.BaseControllerV2 {
941
953
  persist: true,
942
954
  anonymous: false
943
955
  },
956
+ unencryptedSnapStates: {
957
+ persist: true,
958
+ anonymous: false
959
+ },
944
960
  snaps: {
945
961
  persist: (snaps)=>{
946
962
  return Object.values(snaps)// We should not persist snaps that are in the installing state,
@@ -1020,6 +1036,14 @@ class SnapController extends _basecontroller.BaseControllerV2 {
1020
1036
  */ _class_private_method_init(this, _fetchSnap);
1021
1037
  _class_private_method_init(this, _validateSnapPermissions);
1022
1038
  _class_private_method_init(this, _getRpcRequestHandler);
1039
+ _class_private_method_init(this, _triggerPhishingListUpdate);
1040
+ _class_private_method_init(this, _checkPhishingList);
1041
+ /**
1042
+ * Asserts that the returned result of a Snap RPC call is the expected shape.
1043
+ *
1044
+ * @param handlerType - The handler type of the RPC Request.
1045
+ * @param result - The result of the RPC request.
1046
+ */ _class_private_method_init(this, _assertSnapRpcRequestResult);
1023
1047
  /**
1024
1048
  * Awaits the specified promise and rejects if the promise doesn't resolve
1025
1049
  * before the timeout.
@@ -1212,11 +1236,11 @@ function initializeStateMachine() {
1212
1236
  function registerMessageHandlers() {
1213
1237
  this.messagingSystem.registerActionHandler(`${controllerName}:clearSnapState`, (...args)=>this.clearSnapState(...args));
1214
1238
  this.messagingSystem.registerActionHandler(`${controllerName}:get`, (...args)=>this.get(...args));
1215
- this.messagingSystem.registerActionHandler(`${controllerName}:getSnapState`, async (...args)=>this.getSnapState(...args));
1239
+ this.messagingSystem.registerActionHandler(`${controllerName}:getSnapState`, (...args)=>this.getSnapState(...args));
1216
1240
  this.messagingSystem.registerActionHandler(`${controllerName}:handleRequest`, async (...args)=>this.handleRequest(...args));
1217
1241
  this.messagingSystem.registerActionHandler(`${controllerName}:has`, (...args)=>this.has(...args));
1218
1242
  this.messagingSystem.registerActionHandler(`${controllerName}:updateBlockedSnaps`, async ()=>this.updateBlockedSnaps());
1219
- this.messagingSystem.registerActionHandler(`${controllerName}:updateSnapState`, async (...args)=>this.updateSnapState(...args));
1243
+ this.messagingSystem.registerActionHandler(`${controllerName}:updateSnapState`, (...args)=>this.updateSnapState(...args));
1220
1244
  this.messagingSystem.registerActionHandler(`${controllerName}:enable`, (...args)=>this.enableSnap(...args));
1221
1245
  this.messagingSystem.registerActionHandler(`${controllerName}:disable`, async (...args)=>this.disableSnap(...args));
1222
1246
  this.messagingSystem.registerActionHandler(`${controllerName}:remove`, async (...args)=>this.removeSnap(...args));
@@ -1425,7 +1449,7 @@ async function getEndowments(snapId) {
1425
1449
  }
1426
1450
  function set(args) {
1427
1451
  const { id: snapId, origin, files, isUpdate = false } = args;
1428
- const { manifest, sourceCode: sourceCodeFile, svgIcon, auxiliaryFiles: rawAuxiliaryFiles } = files;
1452
+ const { manifest, sourceCode: sourceCodeFile, svgIcon, auxiliaryFiles: rawAuxiliaryFiles, localizationFiles } = files;
1429
1453
  (0, _snapsutils.assertIsSnapManifest)(manifest.result);
1430
1454
  const { version } = manifest.result;
1431
1455
  const sourceCode = sourceCodeFile.toString();
@@ -1459,7 +1483,8 @@ function set(args) {
1459
1483
  sourceCode,
1460
1484
  version,
1461
1485
  versionHistory,
1462
- auxiliaryFiles
1486
+ auxiliaryFiles,
1487
+ localizationFiles: localizationFiles.map((file)=>file.result)
1463
1488
  };
1464
1489
  // If the snap was blocked, it isn't any longer
1465
1490
  delete snap.blockInformation;
@@ -1487,12 +1512,15 @@ async function fetchSnap(snapId, location) {
1487
1512
  const sourceCode = await location.fetch(manifest.result.source.location.npm.filePath);
1488
1513
  const { iconPath } = manifest.result.source.location.npm;
1489
1514
  const svgIcon = iconPath ? await location.fetch(iconPath) : undefined;
1490
- const auxiliaryFiles = manifest.result.source.files ? await Promise.all(manifest.result.source.files.map(async (filePath)=>location.fetch(filePath))) : [];
1515
+ const auxiliaryFiles = await (0, _utils1.getSnapFiles)(location, manifest.result.source.files);
1516
+ const localizationFiles = await (0, _utils1.getSnapFiles)(location, manifest.result.source.locales);
1517
+ const validatedLocalizationFiles = (0, _snapsutils.getValidatedLocalizationFiles)(localizationFiles);
1491
1518
  const files = {
1492
1519
  manifest,
1493
1520
  sourceCode,
1494
1521
  svgIcon,
1495
- auxiliaryFiles
1522
+ auxiliaryFiles,
1523
+ localizationFiles: validatedLocalizationFiles
1496
1524
  };
1497
1525
  (0, _snapsutils.validateFetchedSnap)(files);
1498
1526
  return {
@@ -1561,7 +1589,7 @@ function getRpcRequestHandler(snapId) {
1561
1589
  // This will either get the result or reject due to the timeout.
1562
1590
  try {
1563
1591
  const result = await _class_private_method_get(this, _executeWithTimeout, executeWithTimeout).call(this, handleRpcRequestPromise, timer);
1564
- _class_private_method_get(this, _recordSnapRpcRequestFinish, recordSnapRpcRequestFinish).call(this, snapId, request.id);
1592
+ await _class_private_method_get(this, _assertSnapRpcRequestResult, assertSnapRpcRequestResult).call(this, handlerType, result);
1565
1593
  return result;
1566
1594
  } catch (error) {
1567
1595
  const [jsonRpcError, handled] = (0, _snapsutils.unwrapError)(error);
@@ -1569,11 +1597,35 @@ function getRpcRequestHandler(snapId) {
1569
1597
  await this.stopSnap(snapId, _snapsutils.SnapStatusEvents.Crash);
1570
1598
  }
1571
1599
  throw jsonRpcError;
1600
+ } finally{
1601
+ _class_private_method_get(this, _recordSnapRpcRequestFinish, recordSnapRpcRequestFinish).call(this, snapId, request.id);
1572
1602
  }
1573
1603
  };
1574
1604
  runtime.rpcHandler = rpcHandler;
1575
1605
  return rpcHandler;
1576
1606
  }
1607
+ async function triggerPhishingListUpdate() {
1608
+ return this.messagingSystem.call('PhishingController:maybeUpdateState');
1609
+ }
1610
+ function checkPhishingList(origin) {
1611
+ return this.messagingSystem.call('PhishingController:testOrigin', origin).result;
1612
+ }
1613
+ async function assertSnapRpcRequestResult(handlerType, result) {
1614
+ switch(handlerType){
1615
+ case _snapsutils.HandlerType.OnTransaction:
1616
+ (0, _utils.assertStruct)(result, _snapsutils.OnTransactionResponseStruct);
1617
+ await _class_private_method_get(this, _triggerPhishingListUpdate, triggerPhishingListUpdate).call(this);
1618
+ (0, _snapsui.assertUILinksAreSafe)(result.content, _class_private_method_get(this, _checkPhishingList, checkPhishingList).bind(this));
1619
+ break;
1620
+ case _snapsutils.HandlerType.OnHomePage:
1621
+ (0, _utils.assertStruct)(result, _snapsutils.OnHomePageResponseStruct);
1622
+ await _class_private_method_get(this, _triggerPhishingListUpdate, triggerPhishingListUpdate).call(this);
1623
+ (0, _snapsui.assertUILinksAreSafe)(result.content, _class_private_method_get(this, _checkPhishingList, checkPhishingList).bind(this));
1624
+ break;
1625
+ default:
1626
+ break;
1627
+ }
1628
+ }
1577
1629
  async function executeWithTimeout(promise, timer) {
1578
1630
  const result = await (0, _utils1.withTimeout)(promise, timer ?? this.maxRequestTime);
1579
1631
  if (result === _utils1.hasTimedOut) {