@metamask/snaps-controllers 3.4.0 → 3.5.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.
@@ -61,12 +61,12 @@ function _define_property(obj, key, value) {
61
61
  }
62
62
  return obj;
63
63
  }
64
- import { BaseControllerV2 as BaseController } from '@metamask/base-controller';
64
+ import { BaseController } from '@metamask/base-controller';
65
65
  import { SubjectType } from '@metamask/permission-controller';
66
66
  import { rpcErrors } from '@metamask/rpc-errors';
67
67
  import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/snaps-rpc-methods';
68
68
  import { AuxiliaryFileEncoding, getErrorMessage } from '@metamask/snaps-sdk';
69
- import { validateComponentLinks, assertIsSnapManifest, assertIsValidSnapId, DEFAULT_ENDOWMENTS, DEFAULT_REQUESTED_SNAP_VERSION, encodeAuxiliaryFile, HandlerType, isOriginAllowed, logError, normalizeRelative, OnTransactionResponseStruct, resolveVersionRange, SnapCaveatType, SnapStatus, SnapStatusEvents, validateFetchedSnap, unwrapError, OnHomePageResponseStruct, getValidatedLocalizationFiles } from '@metamask/snaps-utils';
69
+ import { validateComponentLinks, assertIsSnapManifest, assertIsValidSnapId, DEFAULT_ENDOWMENTS, DEFAULT_REQUESTED_SNAP_VERSION, encodeAuxiliaryFile, HandlerType, isOriginAllowed, logError, normalizeRelative, OnTransactionResponseStruct, resolveVersionRange, SnapCaveatType, SnapStatus, SnapStatusEvents, validateFetchedSnap, unwrapError, OnHomePageResponseStruct, getValidatedLocalizationFiles, encodeBase64 } from '@metamask/snaps-utils';
70
70
  import { assert, assertIsJsonRpcRequest, assertStruct, Duration, gtRange, gtVersion, hasProperty, inMilliseconds, isNonEmptyArray, isValidSemVerRange, satisfiesVersionRange, timeSince } from '@metamask/utils';
71
71
  import { createMachine, interpret } from '@xstate/fsm';
72
72
  import { nanoid } from 'nanoid';
@@ -425,7 +425,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
425
425
  * @param path - The path to the requested file.
426
426
  * @param encoding - An optional requested file encoding.
427
427
  * @returns The file requested in the chosen file encoding or null if the file is not found.
428
- */ getSnapFile(snapId, path, encoding = AuxiliaryFileEncoding.Base64) {
428
+ */ async getSnapFile(snapId, path, encoding = AuxiliaryFileEncoding.Base64) {
429
429
  const snap = this.getExpect(snapId);
430
430
  const normalizedPath = normalizeRelative(path);
431
431
  const value = snap.auxiliaryFiles?.find((file)=>file.path === normalizedPath)?.value;
@@ -1153,10 +1153,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
1153
1153
  });
1154
1154
  _class_private_method_get(this, _initializeStateMachine, initializeStateMachine).call(this);
1155
1155
  _class_private_method_get(this, _registerMessageHandlers, registerMessageHandlers).call(this);
1156
- Object.values(state?.snaps ?? {}).forEach((snap)=>_class_private_method_get(this, _setupRuntime, setupRuntime).call(this, snap.id, {
1157
- sourceCode: snap.sourceCode,
1158
- state: state?.snapStates?.[snap.id] ?? null
1159
- }));
1156
+ Object.values(state?.snaps ?? {}).forEach((snap)=>_class_private_method_get(this, _setupRuntime, setupRuntime).call(this, snap.id));
1160
1157
  }
1161
1158
  }
1162
1159
  function initializeStateMachine() {
@@ -1231,7 +1228,7 @@ function registerMessageHandlers() {
1231
1228
  this.messagingSystem.registerActionHandler(`${controllerName}:getRegistryMetadata`, async (...args)=>this.getRegistryMetadata(...args));
1232
1229
  this.messagingSystem.registerActionHandler(`${controllerName}:disconnectOrigin`, (...args)=>this.removeSnapFromSubject(...args));
1233
1230
  this.messagingSystem.registerActionHandler(`${controllerName}:revokeDynamicPermissions`, (...args)=>this.revokeDynamicSnapPermissions(...args));
1234
- this.messagingSystem.registerActionHandler(`${controllerName}:getFile`, (...args)=>this.getSnapFile(...args));
1231
+ this.messagingSystem.registerActionHandler(`${controllerName}:getFile`, async (...args)=>this.getSnapFile(...args));
1235
1232
  }
1236
1233
  function pollForLastRequestStatus() {
1237
1234
  _class_private_field_set(this, _timeoutForLastRequestStatus, setTimeout(()=>{
@@ -1282,8 +1279,7 @@ async function stopSnapsLastRequestPastMax() {
1282
1279
  const entries = [
1283
1280
  ..._class_private_field_get(this, _snapsRuntimeData).entries()
1284
1281
  ];
1285
- return Promise.all(entries.filter(([_snapId, runtime])=>runtime.activeReferences === 0 && runtime.pendingInboundRequests.length === 0 && // lastRequest should always be set here but TypeScript wants this check
1286
- runtime.lastRequest && _class_private_field_get(this, _maxIdleTime) && timeSince(runtime.lastRequest) > _class_private_field_get(this, _maxIdleTime)).map(async ([snapId])=>this.stopSnap(snapId, SnapStatusEvents.Stop)));
1282
+ return Promise.all(entries.filter(([_snapId, runtime])=>runtime.activeReferences === 0 && runtime.pendingInboundRequests.length === 0 && runtime.lastRequest && _class_private_field_get(this, _maxIdleTime) && timeSince(runtime.lastRequest) > _class_private_field_get(this, _maxIdleTime)).map(async ([snapId])=>this.stopSnap(snapId, SnapStatusEvents.Stop)));
1287
1283
  }
1288
1284
  function transition(snapId, event) {
1289
1285
  const { interpreter } = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
@@ -1346,10 +1342,7 @@ async function resolveAllowlistVersion(snapId, versionRange) {
1346
1342
  }
1347
1343
  async function add(args) {
1348
1344
  const { id: snapId, location, versionRange } = args;
1349
- _class_private_method_get(this, _setupRuntime, setupRuntime).call(this, snapId, {
1350
- sourceCode: null,
1351
- state: null
1352
- });
1345
+ _class_private_method_get(this, _setupRuntime, setupRuntime).call(this, snapId);
1353
1346
  const runtime = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
1354
1347
  if (!runtime.installPromise) {
1355
1348
  log(`Adding snap: ${snapId}`);
@@ -1387,11 +1380,14 @@ async function startSnap(snapData) {
1387
1380
  throw new Error(`Snap "${snapId}" is already started.`);
1388
1381
  }
1389
1382
  try {
1383
+ const runtime = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
1390
1384
  const result = await _class_private_method_get(this, _executeWithTimeout, executeWithTimeout).call(this, this.messagingSystem.call('ExecutionService:executeSnap', {
1391
1385
  ...snapData,
1392
1386
  endowments: await _class_private_method_get(this, _getEndowments, getEndowments).call(this, snapId)
1393
1387
  }));
1394
1388
  _class_private_method_get(this, _transition, transition).call(this, snapId, SnapStatusEvents.Start);
1389
+ // We treat the initialization of the snap as the first request, for idle timing purposes.
1390
+ runtime.lastRequest = Date.now();
1395
1391
  return result;
1396
1392
  } catch (error) {
1397
1393
  await _class_private_method_get(this, _terminateSnap, terminateSnap).call(this, snapId);
@@ -1433,10 +1429,13 @@ function set(args) {
1433
1429
  const { version } = manifest.result;
1434
1430
  const sourceCode = sourceCodeFile.toString();
1435
1431
  assert(typeof sourceCode === 'string' && sourceCode.length > 0, `Invalid source code for snap "${snapId}".`);
1436
- const auxiliaryFiles = rawAuxiliaryFiles.map((file)=>({
1432
+ const auxiliaryFiles = rawAuxiliaryFiles.map((file)=>{
1433
+ assert(typeof file.data.base64 === 'string');
1434
+ return {
1437
1435
  path: file.path,
1438
- value: file.toString('base64')
1439
- }));
1436
+ value: file.data.base64
1437
+ };
1438
+ });
1440
1439
  const snapsState = this.state.snaps;
1441
1440
  const existingSnap = snapsState[snapId];
1442
1441
  const previousVersionHistory = existingSnap?.versionHistory ?? [];
@@ -1492,6 +1491,11 @@ async function fetchSnap(snapId, location) {
1492
1491
  const { iconPath } = manifest.result.source.location.npm;
1493
1492
  const svgIcon = iconPath ? await location.fetch(iconPath) : undefined;
1494
1493
  const auxiliaryFiles = await getSnapFiles(location, manifest.result.source.files);
1494
+ await Promise.all(auxiliaryFiles.map(async (file)=>{
1495
+ // This should still be safe
1496
+ // eslint-disable-next-line require-atomic-updates
1497
+ file.data.base64 = await encodeBase64(file);
1498
+ }));
1495
1499
  const localizationFiles = await getSnapFiles(location, manifest.result.source.locales);
1496
1500
  const validatedLocalizationFiles = getValidatedLocalizationFiles(localizationFiles);
1497
1501
  const files = {
@@ -1703,7 +1707,7 @@ function getRuntimeExpect(snapId) {
1703
1707
  assert(runtime !== undefined, new Error(`Snap "${snapId}" runtime data not found`));
1704
1708
  return runtime;
1705
1709
  }
1706
- function setupRuntime(snapId, data) {
1710
+ function setupRuntime(snapId) {
1707
1711
  if (_class_private_field_get(this, _snapsRuntimeData).has(snapId)) {
1708
1712
  return;
1709
1713
  }
@@ -1723,8 +1727,7 @@ function setupRuntime(snapId, data) {
1723
1727
  activeReferences: 0,
1724
1728
  pendingInboundRequests: [],
1725
1729
  pendingOutboundRequests: 0,
1726
- interpreter,
1727
- ...data
1730
+ interpreter
1728
1731
  });
1729
1732
  }
1730
1733
  function calculatePermissionsChange(snapId, desiredPermissionsSet) {