@metamask/snaps-controllers 0.25.0 → 0.26.1

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