@metamask/snaps-controllers 0.25.0 → 0.26.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.
@@ -21,7 +21,7 @@ var __rest = (this && this.__rest) || function (s, e) {
21
21
  }
22
22
  return t;
23
23
  };
24
- var _SnapController_instances, _SnapController_closeAllConnections, _SnapController_environmentEndowmentPermissions, _SnapController_featureFlags, _SnapController_fetchFunction, _SnapController_idleTimeCheckInterval, _SnapController_checkSnapBlockList, _SnapController_maxIdleTime, _SnapController_npmRegistryUrl, _SnapController_getAppKey, _SnapController_timeoutForLastRequestStatus, _SnapController_statusMachine, _SnapController_initializeStateMachine, _SnapController_registerMessageHandlers, _SnapController_pollForLastRequestStatus, _SnapController_blockSnap, _SnapController_unblockSnap, _SnapController_assertIsUnblocked, _SnapController_stopSnapsLastRequestPastMax, _SnapController_transition, _SnapController_terminateSnap, _SnapController_getEncryptionKey, _SnapController_encryptSnapState, _SnapController_decryptSnapState, _SnapController_add, _SnapController_startSnap, _SnapController_getEndowments, _SnapController_set, _SnapController_fetchNpmSnap, _SnapController_fetchLocalSnap, _SnapController_processSnapPermissions, _SnapController_getRpcRequestHandler, _SnapController_executeWithTimeout, _SnapController_recordSnapRpcRequestStart, _SnapController_recordSnapRpcRequestFinish, _SnapController_getRuntime, _SnapController_getRuntimeExpect, _SnapController_setupRuntime, _SnapController_calculatePermissionsChange;
24
+ var _SnapController_instances, _SnapController_closeAllConnections, _SnapController_environmentEndowmentPermissions, _SnapController_featureFlags, _SnapController_fetchFunction, _SnapController_idleTimeCheckInterval, _SnapController_checkSnapBlockList, _SnapController_maxIdleTime, _SnapController_npmRegistryUrl, _SnapController_detectSnapLocation, _SnapController_rollbackSnapshots, _SnapController_getAppKey, _SnapController_timeoutForLastRequestStatus, _SnapController_statusMachine, _SnapController_initializeStateMachine, _SnapController_registerMessageHandlers, _SnapController_pollForLastRequestStatus, _SnapController_blockSnap, _SnapController_unblockSnap, _SnapController_assertIsUnblocked, _SnapController_stopSnapsLastRequestPastMax, _SnapController_transition, _SnapController_terminateSnap, _SnapController_getEncryptionKey, _SnapController_encryptSnapState, _SnapController_decryptSnapState, _SnapController_add, _SnapController_startSnap, _SnapController_getEndowments, _SnapController_set, _SnapController_fetchSnap, _SnapController_processSnapPermissions, _SnapController_getRpcRequestHandler, _SnapController_executeWithTimeout, _SnapController_recordSnapRpcRequestStart, _SnapController_recordSnapRpcRequestFinish, _SnapController_getRollbackSnapshot, _SnapController_createRollbackSnapshot, _SnapController_rollbackSnap, _SnapController_rollbackSnaps, _SnapController_getRuntime, _SnapController_getRuntimeExpect, _SnapController_setupRuntime, _SnapController_calculatePermissionsChange, _SnapController_isValidUpdate;
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.SnapController = exports.AppKeyType = exports.SNAP_APPROVAL_UPDATE = exports.SNAP_APPROVAL_INSTALL = exports.controllerName = void 0;
27
27
  const base_controller_1 = require("@metamask/base-controller");
@@ -37,9 +37,9 @@ const fsm_2 = require("../fsm");
37
37
  const utils_2 = require("../utils");
38
38
  const endowments_1 = require("./endowments");
39
39
  const rpc_1 = require("./endowments/rpc");
40
+ const location_1 = require("./location");
40
41
  const RequestQueue_1 = require("./RequestQueue");
41
42
  const Timer_1 = require("./Timer");
42
- const utils_3 = require("./utils");
43
43
  exports.controllerName = 'SnapController';
44
44
  // TODO: Figure out how to name these
45
45
  exports.SNAP_APPROVAL_INSTALL = 'wallet_installSnap';
@@ -85,7 +85,7 @@ const name = 'SnapController';
85
85
  * - Start: Initializes the snap in its SES realm with the authorized permissions.
86
86
  */
87
87
  class SnapController extends base_controller_1.BaseControllerV2 {
88
- constructor({ closeAllConnections, messenger, state, getAppKey, environmentEndowmentPermissions = [], npmRegistryUrl, idleTimeCheckInterval = (0, utils_1.inMilliseconds)(5, utils_1.Duration.Second), checkBlockList, maxIdleTime = (0, utils_1.inMilliseconds)(30, utils_1.Duration.Second), maxRequestTime = (0, utils_1.inMilliseconds)(60, utils_1.Duration.Second), fetchFunction = globalThis.fetch.bind(globalThis), featureFlags = {}, }) {
88
+ constructor({ closeAllConnections, messenger, state, getAppKey, environmentEndowmentPermissions = [], npmRegistryUrl, idleTimeCheckInterval = (0, utils_1.inMilliseconds)(5, utils_1.Duration.Second), checkBlockList, maxIdleTime = (0, utils_1.inMilliseconds)(30, utils_1.Duration.Second), maxRequestTime = (0, utils_1.inMilliseconds)(60, utils_1.Duration.Second), fetchFunction = globalThis.fetch.bind(globalThis), featureFlags = {}, detectSnapLocation: detectSnapLocationFunction = location_1.detectSnapLocation, }) {
89
89
  var _a, _b;
90
90
  super({
91
91
  messenger,
@@ -136,6 +136,8 @@ class SnapController extends base_controller_1.BaseControllerV2 {
136
136
  _SnapController_checkSnapBlockList.set(this, void 0);
137
137
  _SnapController_maxIdleTime.set(this, void 0);
138
138
  _SnapController_npmRegistryUrl.set(this, void 0);
139
+ _SnapController_detectSnapLocation.set(this, void 0);
140
+ _SnapController_rollbackSnapshots.set(this, void 0);
139
141
  _SnapController_getAppKey.set(this, void 0);
140
142
  _SnapController_timeoutForLastRequestStatus.set(this, void 0);
141
143
  _SnapController_statusMachine.set(this, void 0);
@@ -149,9 +151,11 @@ class SnapController extends base_controller_1.BaseControllerV2 {
149
151
  __classPrivateFieldSet(this, _SnapController_maxIdleTime, maxIdleTime, "f");
150
152
  this.maxRequestTime = maxRequestTime;
151
153
  __classPrivateFieldSet(this, _SnapController_npmRegistryUrl, npmRegistryUrl, "f");
154
+ __classPrivateFieldSet(this, _SnapController_detectSnapLocation, detectSnapLocationFunction, "f");
152
155
  this._onUnhandledSnapError = this._onUnhandledSnapError.bind(this);
153
156
  this._onOutboundRequest = this._onOutboundRequest.bind(this);
154
157
  this._onOutboundResponse = this._onOutboundResponse.bind(this);
158
+ __classPrivateFieldSet(this, _SnapController_rollbackSnapshots, new Map(), "f");
155
159
  this.snapsRuntimeData = new Map();
156
160
  __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_pollForLastRequestStatus).call(this);
157
161
  /* eslint-disable @typescript-eslint/unbound-method */
@@ -561,27 +565,46 @@ class SnapController extends base_controller_1.BaseControllerV2 {
561
565
  */
562
566
  async installSnaps(origin, requestedSnaps) {
563
567
  const result = {};
564
- await Promise.all(Object.entries(requestedSnaps).map(async ([snapId, { version: rawVersion }]) => {
565
- const [error, version] = (0, snaps_utils_1.resolveVersionRange)(rawVersion);
566
- if (error) {
567
- result[snapId] = {
568
- error: eth_rpc_errors_1.ethErrors.rpc.invalidParams(`The "version" field must be a valid SemVer version range if specified. Received: "${version}".`),
569
- };
570
- return;
571
- }
572
- const permissionName = (0, snaps_utils_1.getSnapPermissionName)(snapId);
573
- if (this.messagingSystem.call('PermissionController:hasPermission', origin, permissionName)) {
574
- // Attempt to install and run the snap, storing any errors that
575
- // occur during the process.
576
- result[snapId] = Object.assign({}, (await this.processRequestedSnap(origin, snapId, version)));
577
- }
578
- else {
579
- // only allow the installation of permitted snaps
580
- result[snapId] = {
581
- error: eth_rpc_errors_1.ethErrors.provider.unauthorized(`Not authorized to install snap "${snapId}". Request the permission for the snap before attempting to install it.`),
582
- };
568
+ const snapIds = Object.keys(requestedSnaps);
569
+ // Existing snaps may need to be updated
570
+ const pendingUpdates = snapIds.filter((snapId) => this.has(snapId));
571
+ // Non-existing snaps will need to be installed
572
+ const pendingInstalls = snapIds.filter((snapId) => !pendingUpdates.includes(snapId));
573
+ try {
574
+ for (const [snapId, { version: rawVersion }] of Object.entries(requestedSnaps)) {
575
+ const [error, version] = (0, snaps_utils_1.resolveVersionRange)(rawVersion);
576
+ if (error) {
577
+ throw eth_rpc_errors_1.ethErrors.rpc.invalidParams(`The "version" field must be a valid SemVer version range if specified. Received: "${rawVersion}".`);
578
+ }
579
+ const permissionName = (0, snaps_utils_1.getSnapPermissionName)(snapId);
580
+ if (!this.messagingSystem.call('PermissionController:hasPermission', origin, permissionName)) {
581
+ throw eth_rpc_errors_1.ethErrors.provider.unauthorized(`Not authorized to install snap "${snapId}". Request the permission for the snap before attempting to install it.`);
582
+ }
583
+ const isUpdate = pendingUpdates.includes(snapId);
584
+ if (isUpdate && __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_isValidUpdate).call(this, snapId, version)) {
585
+ let rollbackSnapshot = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRollbackSnapshot).call(this, snapId);
586
+ if (rollbackSnapshot === undefined) {
587
+ const prevSourceCode = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId).sourceCode;
588
+ rollbackSnapshot = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_createRollbackSnapshot).call(this, snapId);
589
+ rollbackSnapshot.sourceCode = prevSourceCode;
590
+ rollbackSnapshot.newVersion = version;
591
+ }
592
+ else {
593
+ throw new Error('This snap is already being updated.');
594
+ }
595
+ }
596
+ result[snapId] = await this.processRequestedSnap(origin, snapId, version);
583
597
  }
584
- }));
598
+ snapIds.forEach((snapId) => __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").delete(snapId));
599
+ }
600
+ catch (error) {
601
+ const installed = pendingInstalls.filter((snapId) => this.has(snapId));
602
+ await this.removeSnaps(installed);
603
+ const snapshottedSnaps = [...__classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").keys()];
604
+ const snapsToRollback = pendingUpdates.filter((snapId) => snapshottedSnaps.includes(snapId));
605
+ await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_rollbackSnaps).call(this, snapsToRollback);
606
+ throw error;
607
+ }
585
608
  return result;
586
609
  }
587
610
  /**
@@ -594,39 +617,22 @@ class SnapController extends base_controller_1.BaseControllerV2 {
594
617
  * @returns The resulting snap object, or an error if something went wrong.
595
618
  */
596
619
  async processRequestedSnap(origin, snapId, versionRange) {
597
- try {
598
- (0, snaps_utils_1.validateSnapId)(snapId);
599
- }
600
- catch (error) {
601
- return {
602
- error: eth_rpc_errors_1.ethErrors.rpc.invalidParams(`"${snapId}" is not a valid snap id.`),
603
- };
604
- }
620
+ (0, snaps_utils_1.validateSnapId)(snapId);
621
+ const location = __classPrivateFieldGet(this, _SnapController_detectSnapLocation, "f").call(this, snapId, { versionRange });
605
622
  const existingSnap = this.getTruncated(snapId);
606
623
  // For devX we always re-install local snaps.
607
- if (existingSnap && (0, snaps_utils_1.getSnapPrefix)(snapId) !== snaps_utils_1.SnapIdPrefixes.local) {
624
+ if (existingSnap && !location.shouldAlwaysReload) {
608
625
  if ((0, snaps_utils_1.satisfiesVersionRange)(existingSnap.version, versionRange)) {
609
626
  return existingSnap;
610
627
  }
611
628
  if (__classPrivateFieldGet(this, _SnapController_featureFlags, "f").dappsCanUpdateSnaps === true) {
612
- try {
613
- const updateResult = await this.updateSnap(origin, snapId, versionRange);
614
- if (updateResult === null) {
615
- return {
616
- error: eth_rpc_errors_1.ethErrors.rpc.invalidParams(`Snap "${snapId}@${existingSnap.version}" is already installed, couldn't update to a version inside requested "${versionRange}" range.`),
617
- };
618
- }
619
- return updateResult;
620
- }
621
- catch (error) {
622
- return { error: (0, eth_rpc_errors_1.serializeError)(error) };
629
+ const updateResult = await this.updateSnap(origin, snapId, versionRange, location);
630
+ if (updateResult === null) {
631
+ throw eth_rpc_errors_1.ethErrors.rpc.invalidParams(`Snap "${snapId}@${existingSnap.version}" is already installed. Couldn't update to a version inside requested "${versionRange}" range.`);
623
632
  }
633
+ return updateResult;
624
634
  }
625
- else {
626
- return {
627
- error: eth_rpc_errors_1.ethErrors.rpc.invalidParams(`Version mismatch with already installed snap. ${snapId}@${existingSnap.version} doesn't satisfy requested version ${versionRange}`),
628
- };
629
- }
635
+ throw eth_rpc_errors_1.ethErrors.rpc.invalidParams(`Version mismatch with already installed snap. ${snapId}@${existingSnap.version} doesn't satisfy requested version ${versionRange}.`);
630
636
  }
631
637
  // Existing snaps must be stopped before overwriting
632
638
  if (existingSnap && this.isRunning(snapId)) {
@@ -636,7 +642,7 @@ class SnapController extends base_controller_1.BaseControllerV2 {
636
642
  const { sourceCode } = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_add).call(this, {
637
643
  origin,
638
644
  id: snapId,
639
- versionRange,
645
+ location,
640
646
  });
641
647
  await this.authorize(origin, snapId);
642
648
  await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_startSnap).call(this, {
@@ -649,10 +655,7 @@ class SnapController extends base_controller_1.BaseControllerV2 {
649
655
  }
650
656
  catch (error) {
651
657
  console.error(`Error when adding snap.`, error);
652
- if (this.has(snapId)) {
653
- await this.removeSnap(snapId);
654
- }
655
- return { error: (0, eth_rpc_errors_1.serializeError)(error) };
658
+ throw error;
656
659
  }
657
660
  }
658
661
  /**
@@ -670,27 +673,29 @@ class SnapController extends base_controller_1.BaseControllerV2 {
670
673
  * @param origin - The origin requesting the snap update.
671
674
  * @param snapId - The id of the Snap to be updated.
672
675
  * @param newVersionRange - A semver version range in which the maximum version will be chosen.
676
+ * @param location - Optional location that was already used during installation flow.
673
677
  * @returns The snap metadata if updated, `null` otherwise.
674
678
  */
675
- async updateSnap(origin, snapId, newVersionRange = snaps_utils_1.DEFAULT_REQUESTED_SNAP_VERSION) {
679
+ async updateSnap(origin, snapId, newVersionRange = snaps_utils_1.DEFAULT_REQUESTED_SNAP_VERSION, location) {
680
+ var _a;
676
681
  const snap = this.getExpect(snapId);
677
682
  if (!(0, snaps_utils_1.isValidSemVerRange)(newVersionRange)) {
678
683
  throw new Error(`Received invalid snap version range: "${newVersionRange}".`);
679
684
  }
680
- const newSnap = await this.fetchSnap(snapId, newVersionRange);
681
- const newVersion = newSnap.manifest.version;
685
+ const newSnap = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_fetchSnap).call(this, snapId, location !== null && location !== void 0 ? location : __classPrivateFieldGet(this, _SnapController_detectSnapLocation, "f").call(this, snapId, { versionRange: newVersionRange }));
686
+ const newVersion = newSnap.manifest.result.version;
682
687
  if (!(0, snaps_utils_1.gtVersion)(newVersion, snap.version)) {
683
688
  console.warn(`Tried updating snap "${snapId}" within "${newVersionRange}" version range, but newer version "${snap.version}" is already installed`);
684
689
  return null;
685
690
  }
686
691
  await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertIsUnblocked).call(this, snapId, {
687
692
  version: newVersion,
688
- shasum: newSnap.manifest.source.shasum,
693
+ shasum: newSnap.manifest.result.source.shasum,
689
694
  });
690
- const processedPermissions = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_processSnapPermissions).call(this, newSnap.manifest.initialPermissions);
695
+ const processedPermissions = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_processSnapPermissions).call(this, newSnap.manifest.result.initialPermissions);
691
696
  const { newPermissions, unusedPermissions, approvedPermissions } = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_calculatePermissionsChange).call(this, snapId, processedPermissions);
692
697
  const id = (0, nanoid_1.nanoid)();
693
- const _a = (await this.messagingSystem.call('ApprovalController:addRequest', {
698
+ const _b = (await this.messagingSystem.call('ApprovalController:addRequest', {
694
699
  origin,
695
700
  id,
696
701
  type: exports.SNAP_APPROVAL_UPDATE,
@@ -699,12 +704,12 @@ class SnapController extends base_controller_1.BaseControllerV2 {
699
704
  metadata: { id, origin: snapId, dappOrigin: origin },
700
705
  permissions: newPermissions,
701
706
  snapId,
702
- newVersion: newSnap.manifest.version,
707
+ newVersion: newSnap.manifest.result.version,
703
708
  newPermissions,
704
709
  approvedPermissions,
705
710
  unusedPermissions,
706
711
  },
707
- }, true)), { permissions: approvedNewPermissions } = _a, requestData = __rest(_a, ["permissions"]);
712
+ }, true)), { permissions: approvedNewPermissions } = _b, requestData = __rest(_b, ["permissions"]);
708
713
  if (this.isRunning(snapId)) {
709
714
  await this.stopSnap(snapId, snaps_utils_1.SnapStatusEvents.Stop);
710
715
  }
@@ -713,8 +718,9 @@ class SnapController extends base_controller_1.BaseControllerV2 {
713
718
  origin,
714
719
  id: snapId,
715
720
  manifest: newSnap.manifest,
716
- sourceCode: newSnap.sourceCode,
721
+ files: newSnap.files,
717
722
  versionRange: newVersionRange,
723
+ isUpdate: true,
718
724
  });
719
725
  const unusedPermissionsKeys = Object.keys(unusedPermissions);
720
726
  if ((0, utils_1.isNonEmptyArray)(unusedPermissionsKeys)) {
@@ -729,37 +735,24 @@ class SnapController extends base_controller_1.BaseControllerV2 {
729
735
  requestData,
730
736
  });
731
737
  }
732
- await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_startSnap).call(this, { snapId, sourceCode: newSnap.sourceCode });
733
- const truncatedSnap = this.getTruncatedExpect(snapId);
734
- this.messagingSystem.publish('SnapController:snapUpdated', truncatedSnap, snap.version);
735
- return truncatedSnap;
736
- }
737
- /**
738
- * Fetches the manifest and source code of a snap.
739
- *
740
- * This function is not hash private yet because of tests.
741
- *
742
- * @param snapId - The id of the Snap.
743
- * @param versionRange - The SemVer version of the Snap to fetch.
744
- * @returns A tuple of the Snap manifest object and the Snap source code.
745
- */
746
- async fetchSnap(snapId, versionRange = snaps_utils_1.DEFAULT_REQUESTED_SNAP_VERSION) {
738
+ const rollbackSnapshot = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRollbackSnapshot).call(this, snapId);
739
+ if (rollbackSnapshot !== undefined) {
740
+ rollbackSnapshot.permissions.revoked = unusedPermissions;
741
+ rollbackSnapshot.permissions.granted = Object.keys(approvedNewPermissions);
742
+ rollbackSnapshot.permissions.requestData = requestData;
743
+ }
744
+ const sourceCode = (_a = newSnap.files
745
+ .find((file) => file.path === newSnap.manifest.result.source.location.npm.filePath)) === null || _a === void 0 ? void 0 : _a.toString();
746
+ (0, utils_1.assert)(sourceCode !== undefined);
747
747
  try {
748
- const snapPrefix = (0, snaps_utils_1.getSnapPrefix)(snapId);
749
- switch (snapPrefix) {
750
- case snaps_utils_1.SnapIdPrefixes.local:
751
- return __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_fetchLocalSnap).call(this, snapId.replace(snaps_utils_1.SnapIdPrefixes.local, ''));
752
- case snaps_utils_1.SnapIdPrefixes.npm:
753
- return __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_fetchNpmSnap).call(this, snapId.replace(snaps_utils_1.SnapIdPrefixes.npm, ''), versionRange);
754
- /* istanbul ignore next */
755
- default:
756
- // This whill fail to compile if the above switch is not fully exhaustive
757
- return (0, utils_1.assertExhaustive)(snapPrefix);
758
- }
748
+ await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_startSnap).call(this, { snapId, sourceCode });
759
749
  }
760
- catch (error) {
761
- throw new Error(`Failed to fetch Snap "${snapId}": ${error.message}`);
750
+ catch (_c) {
751
+ throw new Error(`Snap ${snapId} crashed with updated source code.`);
762
752
  }
753
+ const truncatedSnap = this.getTruncatedExpect(snapId);
754
+ this.messagingSystem.publish('SnapController:snapUpdated', truncatedSnap, snap.version);
755
+ return truncatedSnap;
763
756
  }
764
757
  /**
765
758
  * Initiates a request for the given snap's initial permissions.
@@ -850,7 +843,7 @@ class SnapController extends base_controller_1.BaseControllerV2 {
850
843
  }
851
844
  }
852
845
  exports.SnapController = SnapController;
853
- _SnapController_closeAllConnections = new WeakMap(), _SnapController_environmentEndowmentPermissions = new WeakMap(), _SnapController_featureFlags = new WeakMap(), _SnapController_fetchFunction = new WeakMap(), _SnapController_idleTimeCheckInterval = new WeakMap(), _SnapController_checkSnapBlockList = new WeakMap(), _SnapController_maxIdleTime = new WeakMap(), _SnapController_npmRegistryUrl = new WeakMap(), _SnapController_getAppKey = new WeakMap(), _SnapController_timeoutForLastRequestStatus = new WeakMap(), _SnapController_statusMachine = new WeakMap(), _SnapController_instances = new WeakSet(), _SnapController_initializeStateMachine = function _SnapController_initializeStateMachine() {
846
+ _SnapController_closeAllConnections = new WeakMap(), _SnapController_environmentEndowmentPermissions = new WeakMap(), _SnapController_featureFlags = new WeakMap(), _SnapController_fetchFunction = new WeakMap(), _SnapController_idleTimeCheckInterval = new WeakMap(), _SnapController_checkSnapBlockList = new WeakMap(), _SnapController_maxIdleTime = new WeakMap(), _SnapController_npmRegistryUrl = new WeakMap(), _SnapController_detectSnapLocation = new WeakMap(), _SnapController_rollbackSnapshots = new WeakMap(), _SnapController_getAppKey = new WeakMap(), _SnapController_timeoutForLastRequestStatus = new WeakMap(), _SnapController_statusMachine = new WeakMap(), _SnapController_instances = new WeakSet(), _SnapController_initializeStateMachine = function _SnapController_initializeStateMachine() {
854
847
  const disableGuard = ({ snapId }) => {
855
848
  return this.getExpect(snapId).enabled;
856
849
  };
@@ -1020,15 +1013,8 @@ async function _SnapController_terminateSnap(snapId) {
1020
1013
  * @returns The resulting snap object.
1021
1014
  */
1022
1015
  async function _SnapController_add(args) {
1023
- const { id: snapId } = args;
1016
+ const { id: snapId, location } = args;
1024
1017
  (0, snaps_utils_1.validateSnapId)(snapId);
1025
- if (!args ||
1026
- !('origin' in args) ||
1027
- !('id' in args) ||
1028
- (!('manifest' in args) && 'sourceCode' in args) ||
1029
- ('manifest' in args && !('sourceCode' in args))) {
1030
- throw new Error(`Invalid add snap args for snap "${snapId}".`);
1031
- }
1032
1018
  __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_setupRuntime).call(this, snapId, { sourceCode: null, state: null });
1033
1019
  const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1034
1020
  if (!runtime.installPromise) {
@@ -1036,13 +1022,10 @@ async function _SnapController_add(args) {
1036
1022
  // If fetching and setting the snap succeeds, this property will be set
1037
1023
  // to null in the authorize() method.
1038
1024
  runtime.installPromise = (async () => {
1039
- if ('manifest' in args && 'sourceCode' in args) {
1040
- return __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_set).call(this, Object.assign(Object.assign({}, args), { id: snapId }));
1041
- }
1042
- const fetchedSnap = await this.fetchSnap(snapId, args.versionRange);
1025
+ const fetchedSnap = await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_fetchSnap).call(this, snapId, location);
1043
1026
  await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_assertIsUnblocked).call(this, snapId, {
1044
- version: fetchedSnap.manifest.version,
1045
- shasum: fetchedSnap.manifest.source.shasum,
1027
+ version: fetchedSnap.manifest.result.version,
1028
+ shasum: fetchedSnap.manifest.result.source.shasum,
1046
1029
  });
1047
1030
  return __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_set).call(this, Object.assign(Object.assign(Object.assign({}, args), fetchedSnap), { id: snapId }));
1048
1031
  })();
@@ -1109,25 +1092,23 @@ async function _SnapController_getEndowments(snapId) {
1109
1092
  }
1110
1093
  return dedupedEndowments;
1111
1094
  }, _SnapController_set = function _SnapController_set(args) {
1112
- var _a;
1113
- const { id: snapId, origin, manifest, sourceCode, svgIcon, versionRange = snaps_utils_1.DEFAULT_REQUESTED_SNAP_VERSION, } = args;
1114
- (0, snaps_utils_1.assertIsSnapManifest)(manifest);
1115
- const { version } = manifest;
1095
+ var _a, _b;
1096
+ const { id: snapId, origin, manifest, files, versionRange = snaps_utils_1.DEFAULT_REQUESTED_SNAP_VERSION, isUpdate = false, } = args;
1097
+ (0, snaps_utils_1.assertIsSnapManifest)(manifest.result);
1098
+ const { version } = manifest.result;
1116
1099
  if (!(0, snaps_utils_1.satisfiesVersionRange)(version, versionRange)) {
1117
1100
  throw new Error(`Version mismatch. Manifest for "${snapId}" specifies version "${version}" which doesn't satisfy requested version range "${versionRange}"`);
1118
1101
  }
1102
+ const sourceCode = (_a = files
1103
+ .find((file) => file.path === manifest.result.source.location.npm.filePath)) === null || _a === void 0 ? void 0 : _a.toString();
1104
+ const svgIcon = files.find((file) => file.path === manifest.result.source.location.npm.iconPath);
1105
+ (0, utils_1.assert)(sourceCode !== undefined);
1119
1106
  if (typeof sourceCode !== 'string' || sourceCode.length === 0) {
1120
1107
  throw new Error(`Invalid source code for snap "${snapId}".`);
1121
1108
  }
1122
- const initialPermissions = manifest === null || manifest === void 0 ? void 0 : manifest.initialPermissions;
1123
- if (!initialPermissions ||
1124
- typeof initialPermissions !== 'object' ||
1125
- Array.isArray(initialPermissions)) {
1126
- throw new Error(`Invalid initial permissions for snap "${snapId}".`);
1127
- }
1128
1109
  const snapsState = this.state.snaps;
1129
1110
  const existingSnap = snapsState[snapId];
1130
- const previousVersionHistory = (_a = existingSnap === null || existingSnap === void 0 ? void 0 : existingSnap.versionHistory) !== null && _a !== void 0 ? _a : [];
1111
+ const previousVersionHistory = (_b = existingSnap === null || existingSnap === void 0 ? void 0 : existingSnap.versionHistory) !== null && _b !== void 0 ? _b : [];
1131
1112
  const versionHistory = [
1132
1113
  ...previousVersionHistory,
1133
1114
  {
@@ -1141,52 +1122,54 @@ async function _SnapController_getEndowments(snapId) {
1141
1122
  // previous state.
1142
1123
  blocked: false, enabled: true,
1143
1124
  // So we can easily correlate the snap with its permission
1144
- permissionName: (0, snaps_utils_1.getSnapPermissionName)(snapId), id: snapId, initialPermissions,
1145
- manifest, status: __classPrivateFieldGet(this, _SnapController_statusMachine, "f").config.initial, version,
1125
+ permissionName: (0, snaps_utils_1.getSnapPermissionName)(snapId), id: snapId, initialPermissions: manifest.result.initialPermissions, manifest: manifest.result, status: __classPrivateFieldGet(this, _SnapController_statusMachine, "f").config.initial, version,
1146
1126
  versionHistory });
1147
1127
  // If the snap was blocked, it isn't any longer
1148
1128
  delete snap.blockInformation;
1149
1129
  // store the snap back in state
1150
- this.update((state) => {
1130
+ const { inversePatches } = this.update((state) => {
1151
1131
  state.snaps[snapId] = snap;
1152
1132
  });
1133
+ // checking for isUpdate here as this function is also used in
1134
+ // the install flow, we do not care to create snapshots for installs
1135
+ if (isUpdate) {
1136
+ const rollbackSnapshot = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRollbackSnapshot).call(this, snapId);
1137
+ if (rollbackSnapshot !== undefined) {
1138
+ rollbackSnapshot.statePatches = inversePatches;
1139
+ }
1140
+ }
1153
1141
  const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1154
1142
  runtime.sourceCode = sourceCode;
1155
- this.messagingSystem.publish(`SnapController:snapAdded`, snap, svgIcon);
1143
+ this.messagingSystem.publish(`SnapController:snapAdded`, snap, svgIcon === null || svgIcon === void 0 ? void 0 : svgIcon.toString());
1156
1144
  return Object.assign(Object.assign({}, snap), { sourceCode });
1157
- }, _SnapController_fetchNpmSnap = async function _SnapController_fetchNpmSnap(packageName, versionRange) {
1158
- if (!(0, snaps_utils_1.isValidSemVerRange)(versionRange)) {
1159
- throw new Error(`Received invalid Snap version range: "${versionRange}".`);
1160
- }
1161
- const { manifest, sourceCode, svgIcon } = await (0, utils_3.fetchNpmSnap)(packageName, versionRange, __classPrivateFieldGet(this, _SnapController_npmRegistryUrl, "f"), __classPrivateFieldGet(this, _SnapController_fetchFunction, "f"));
1162
- return { manifest, sourceCode, svgIcon };
1163
- }, _SnapController_fetchLocalSnap =
1145
+ }, _SnapController_fetchSnap =
1164
1146
  /**
1165
- * Fetches the manifest and source code of a local snap.
1147
+ * Fetches the manifest and source code of a snap.
1148
+ *
1149
+ * This function is not hash private yet because of tests.
1166
1150
  *
1167
- * @param localhostUrl - The localhost URL to download from.
1168
- * @returns The validated manifest and the source code.
1151
+ * @param snapId - The id of the Snap.
1152
+ * @param location - Source from which snap will be fetched.
1153
+ * @returns A tuple of the Snap manifest object and the Snap source code.
1169
1154
  */
1170
- async function _SnapController_fetchLocalSnap(localhostUrl) {
1171
- // Local snaps are mostly used for development purposes. Fetches were cached in the browser and were not requested
1172
- // afterwards which lead to confusing development where old versions of snaps were installed.
1173
- // Thus we disable caching
1174
- const fetchOptions = { cache: 'no-cache' };
1175
- const manifestUrl = new URL(snaps_utils_1.NpmSnapFileNames.Manifest, localhostUrl);
1176
- if (!snaps_utils_1.LOCALHOST_HOSTNAMES.has(manifestUrl.hostname)) {
1177
- throw new Error(`Invalid URL: Locally hosted Snaps must be hosted on localhost. Received URL: "${manifestUrl.toString()}"`);
1178
- }
1179
- const manifest = await (await __classPrivateFieldGet(this, _SnapController_fetchFunction, "f").call(this, manifestUrl.toString(), fetchOptions)).json();
1180
- (0, snaps_utils_1.assertIsSnapManifest)(manifest);
1181
- const { source: { location: { npm: { filePath, iconPath }, }, }, } = manifest;
1182
- const [sourceCode, svgIcon] = await Promise.all([
1183
- (await __classPrivateFieldGet(this, _SnapController_fetchFunction, "f").call(this, new URL(filePath, localhostUrl).toString(), fetchOptions)).text(),
1184
- iconPath
1185
- ? (await __classPrivateFieldGet(this, _SnapController_fetchFunction, "f").call(this, new URL(iconPath, localhostUrl).toString(), fetchOptions)).text()
1186
- : undefined,
1187
- ]);
1188
- (0, snaps_utils_1.validateSnapShasum)(manifest, sourceCode);
1189
- return { manifest, sourceCode, svgIcon };
1155
+ async function _SnapController_fetchSnap(snapId, location) {
1156
+ try {
1157
+ const manifest = await location.manifest();
1158
+ const sourceCode = await location.fetch(manifest.result.source.location.npm.filePath);
1159
+ (0, snaps_utils_1.validateSnapShasum)(manifest.result, sourceCode.toString());
1160
+ const { iconPath } = manifest.result.source.location.npm;
1161
+ const files = [sourceCode];
1162
+ if (iconPath) {
1163
+ files.push(await location.fetch(iconPath));
1164
+ }
1165
+ return { manifest, files, location };
1166
+ }
1167
+ catch (error) {
1168
+ // TODO(ritave): Export `getErrorMessage()` from @metamask/utils and use it here
1169
+ // https://github.com/MetaMask/utils/blob/62d022ef83c91fa4d150e51913be4441508a0ab1/src/assert.ts
1170
+ const message = error instanceof Error ? error.message : error.toString();
1171
+ throw new Error(`Failed to fetch Snap "${snapId}": ${message}.`);
1172
+ }
1190
1173
  }, _SnapController_processSnapPermissions = function _SnapController_processSnapPermissions(initialPermissions) {
1191
1174
  return (0, snaps_utils_1.fromEntries)(Object.entries(initialPermissions).map(([initialPermission, value]) => {
1192
1175
  if ((0, utils_1.hasProperty)(rpc_methods_1.caveatMappers, initialPermission)) {
@@ -1198,8 +1181,11 @@ async function _SnapController_fetchLocalSnap(localhostUrl) {
1198
1181
  endowments_1.endowmentCaveatMappers[initialPermission](value),
1199
1182
  ];
1200
1183
  }
1201
- (0, utils_1.assert)(Object.keys(value).length === 0);
1202
- return [initialPermission, {}];
1184
+ // If we have no mapping, this may be a non-snap permission, return as-is
1185
+ return [
1186
+ initialPermission,
1187
+ value,
1188
+ ];
1203
1189
  }));
1204
1190
  }, _SnapController_getRpcRequestHandler = function _SnapController_getRpcRequestHandler(snapId) {
1205
1191
  const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
@@ -1297,6 +1283,72 @@ async function _SnapController_executeWithTimeout(snapId, promise, timer) {
1297
1283
  if (runtime.pendingInboundRequests.length === 0) {
1298
1284
  runtime.lastRequest = Date.now();
1299
1285
  }
1286
+ }, _SnapController_getRollbackSnapshot = function _SnapController_getRollbackSnapshot(snapId) {
1287
+ return __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").get(snapId);
1288
+ }, _SnapController_createRollbackSnapshot = function _SnapController_createRollbackSnapshot(snapId) {
1289
+ (0, utils_1.assert)(__classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").get(snapId) === undefined, new Error(`Snap "${snapId}" rollback snapshot already exists.`));
1290
+ __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").set(snapId, {
1291
+ statePatches: [],
1292
+ sourceCode: '',
1293
+ permissions: { revoked: null, granted: [], requestData: null },
1294
+ newVersion: '',
1295
+ });
1296
+ const newRollbackSnapshot = __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").get(snapId);
1297
+ (0, utils_1.assert)(newRollbackSnapshot !== undefined, new Error(`Snapshot creation failed for ${snapId}.`));
1298
+ return newRollbackSnapshot;
1299
+ }, _SnapController_rollbackSnap =
1300
+ /**
1301
+ * Rolls back a snap to its previous state, permissions
1302
+ * and source code based on the `RollbackSnapshot` that
1303
+ * is captured during the update process. After rolling back,
1304
+ * the function also emits an event indicating that the
1305
+ * snap has been rolled back and it clears the snapshot
1306
+ * for that snap.
1307
+ *
1308
+ * @param snapId - The snap id.
1309
+ * @throws {@link Error}. If a snapshot does not exist.
1310
+ */
1311
+ async function _SnapController_rollbackSnap(snapId) {
1312
+ var _a;
1313
+ const rollbackSnapshot = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRollbackSnapshot).call(this, snapId);
1314
+ if (!rollbackSnapshot) {
1315
+ throw new Error('A snapshot does not exist for this snap.');
1316
+ }
1317
+ await this.stopSnap(snapId, snaps_utils_1.SnapStatusEvents.Stop);
1318
+ const { statePatches, sourceCode, permissions } = rollbackSnapshot;
1319
+ if (statePatches === null || statePatches === void 0 ? void 0 : statePatches.length) {
1320
+ this.applyPatches(statePatches);
1321
+ }
1322
+ if (sourceCode) {
1323
+ const runtime = __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_getRuntimeExpect).call(this, snapId);
1324
+ runtime.sourceCode = sourceCode;
1325
+ }
1326
+ if (permissions.revoked && Object.keys(permissions.revoked).length) {
1327
+ this.messagingSystem.call('PermissionController:grantPermissions', {
1328
+ approvedPermissions: permissions.revoked,
1329
+ subject: { origin: snapId },
1330
+ requestData: permissions.requestData,
1331
+ });
1332
+ }
1333
+ if ((_a = permissions.granted) === null || _a === void 0 ? void 0 : _a.length) {
1334
+ this.messagingSystem.call('PermissionController:revokePermissions', {
1335
+ [snapId]: permissions.granted,
1336
+ });
1337
+ }
1338
+ const truncatedSnap = this.getTruncatedExpect(snapId);
1339
+ this.messagingSystem.publish('SnapController:snapRolledback', truncatedSnap, rollbackSnapshot.newVersion);
1340
+ __classPrivateFieldGet(this, _SnapController_rollbackSnapshots, "f").delete(snapId);
1341
+ }, _SnapController_rollbackSnaps =
1342
+ /**
1343
+ * Iterates through an array of snap ids
1344
+ * and calls `rollbackSnap` on them.
1345
+ *
1346
+ * @param snapIds - An array of snap ids.
1347
+ */
1348
+ async function _SnapController_rollbackSnaps(snapIds) {
1349
+ for (const snapId of snapIds) {
1350
+ await __classPrivateFieldGet(this, _SnapController_instances, "m", _SnapController_rollbackSnap).call(this, snapId);
1351
+ }
1300
1352
  }, _SnapController_getRuntime = function _SnapController_getRuntime(snapId) {
1301
1353
  return this.snapsRuntimeData.get(snapId);
1302
1354
  }, _SnapController_getRuntimeExpect = function _SnapController_getRuntimeExpect(snapId) {
@@ -1327,5 +1379,14 @@ async function _SnapController_executeWithTimeout(snapId, promise, timer) {
1327
1379
  // oldPermissions ∖ (oldPermissions ∖ desiredPermissionsSet) ⟺ oldPermissions ∩ desiredPermissionsSet
1328
1380
  const approvedPermissions = (0, utils_2.setDiff)(oldPermissions, unusedPermissions);
1329
1381
  return { newPermissions, unusedPermissions, approvedPermissions };
1382
+ }, _SnapController_isValidUpdate = function _SnapController_isValidUpdate(snapId, newVersionRange) {
1383
+ const existingSnap = this.getExpect(snapId);
1384
+ if ((0, snaps_utils_1.satisfiesVersionRange)(existingSnap.version, newVersionRange)) {
1385
+ return false;
1386
+ }
1387
+ if ((0, snaps_utils_1.gtRange)(existingSnap.version, newVersionRange)) {
1388
+ return false;
1389
+ }
1390
+ return true;
1330
1391
  };
1331
1392
  //# sourceMappingURL=SnapController.js.map