@metamask/snaps-controllers 0.37.2-flask.1 → 0.38.1-flask.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/dist/cjs/services/AbstractExecutionService.js +1 -19
  3. package/dist/cjs/services/AbstractExecutionService.js.map +1 -1
  4. package/dist/cjs/services/ExecutionService.js.map +1 -1
  5. package/dist/cjs/snaps/SnapController.js +66 -54
  6. package/dist/cjs/snaps/SnapController.js.map +1 -1
  7. package/dist/cjs/snaps/endowments/enum.js +1 -0
  8. package/dist/cjs/snaps/endowments/enum.js.map +1 -1
  9. package/dist/cjs/snaps/endowments/index.js +6 -2
  10. package/dist/cjs/snaps/endowments/index.js.map +1 -1
  11. package/dist/cjs/snaps/endowments/lifecycle-hooks.js +37 -0
  12. package/dist/cjs/snaps/endowments/lifecycle-hooks.js.map +1 -0
  13. package/dist/cjs/snaps/registry/json.js +33 -5
  14. package/dist/cjs/snaps/registry/json.js.map +1 -1
  15. package/dist/cjs/snaps/registry/registry.js.map +1 -1
  16. package/dist/esm/services/AbstractExecutionService.js +1 -19
  17. package/dist/esm/services/AbstractExecutionService.js.map +1 -1
  18. package/dist/esm/services/ExecutionService.js.map +1 -1
  19. package/dist/esm/snaps/SnapController.js +67 -55
  20. package/dist/esm/snaps/SnapController.js.map +1 -1
  21. package/dist/esm/snaps/endowments/enum.js +1 -0
  22. package/dist/esm/snaps/endowments/enum.js.map +1 -1
  23. package/dist/esm/snaps/endowments/index.js +6 -2
  24. package/dist/esm/snaps/endowments/index.js.map +1 -1
  25. package/dist/esm/snaps/endowments/lifecycle-hooks.js +27 -0
  26. package/dist/esm/snaps/endowments/lifecycle-hooks.js.map +1 -0
  27. package/dist/esm/snaps/registry/json.js +33 -5
  28. package/dist/esm/snaps/registry/json.js.map +1 -1
  29. package/dist/esm/snaps/registry/registry.js.map +1 -1
  30. package/dist/types/services/ExecutionService.d.ts +1 -1
  31. package/dist/types/snaps/SnapController.d.ts +5 -14
  32. package/dist/types/snaps/endowments/enum.d.ts +2 -1
  33. package/dist/types/snaps/endowments/index.d.ts +9 -0
  34. package/dist/types/snaps/endowments/lifecycle-hooks.d.ts +15 -0
  35. package/dist/types/snaps/registry/json.d.ts +5 -1
  36. package/dist/types/snaps/registry/registry.d.ts +1 -0
  37. package/package.json +12 -14
@@ -64,7 +64,7 @@ function _define_property(obj, key, value) {
64
64
  import { BaseControllerV2 as BaseController } from '@metamask/base-controller';
65
65
  import { SubjectType } from '@metamask/permission-controller';
66
66
  import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/rpc-methods';
67
- import { assertIsSnapManifest, DEFAULT_ENDOWMENTS, DEFAULT_REQUESTED_SNAP_VERSION, normalizeRelative, resolveVersionRange, SnapCaveatType, SnapStatus, SnapStatusEvents, assertIsValidSnapId, logError, logWarning, validateFetchedSnap } from '@metamask/snaps-utils';
67
+ import { assertIsSnapManifest, assertIsValidSnapId, DEFAULT_ENDOWMENTS, DEFAULT_REQUESTED_SNAP_VERSION, getErrorMessage, HandlerType, logError, logWarning, normalizeRelative, resolveVersionRange, SnapCaveatType, SnapStatus, SnapStatusEvents, validateFetchedSnap } from '@metamask/snaps-utils';
68
68
  import { assert, assertIsJsonRpcRequest, Duration, gtRange, gtVersion, hasProperty, inMilliseconds, isNonEmptyArray, isValidSemVerRange, satisfiesVersionRange, timeSince } from '@metamask/utils';
69
69
  import { createMachine, interpret } from '@xstate/fsm';
70
70
  import { ethErrors } from 'eth-rpc-errors';
@@ -112,7 +112,7 @@ const defaultState = {
112
112
  return truncatedSnap;
113
113
  }
114
114
  const name = 'SnapController';
115
- var _closeAllConnections = /*#__PURE__*/ new WeakMap(), _dynamicPermissions = /*#__PURE__*/ new WeakMap(), _environmentEndowmentPermissions = /*#__PURE__*/ new WeakMap(), _excludedPermissions = /*#__PURE__*/ new WeakMap(), _featureFlags = /*#__PURE__*/ new WeakMap(), _fetchFunction = /*#__PURE__*/ new WeakMap(), _idleTimeCheckInterval = /*#__PURE__*/ new WeakMap(), _maxIdleTime = /*#__PURE__*/ new WeakMap(), _detectSnapLocation = /*#__PURE__*/ new WeakMap(), _rollbackSnapshots = /*#__PURE__*/ new WeakMap(), _timeoutForLastRequestStatus = /*#__PURE__*/ new WeakMap(), _statusMachine = /*#__PURE__*/ new WeakMap(), /**
115
+ var _closeAllConnections = /*#__PURE__*/ new WeakMap(), _dynamicPermissions = /*#__PURE__*/ new WeakMap(), _environmentEndowmentPermissions = /*#__PURE__*/ new WeakMap(), _excludedPermissions = /*#__PURE__*/ new WeakMap(), _featureFlags = /*#__PURE__*/ new WeakMap(), _fetchFunction = /*#__PURE__*/ new WeakMap(), _idleTimeCheckInterval = /*#__PURE__*/ new WeakMap(), _maxIdleTime = /*#__PURE__*/ new WeakMap(), _detectSnapLocation = /*#__PURE__*/ new WeakMap(), _snapsRuntimeData = /*#__PURE__*/ new WeakMap(), _rollbackSnapshots = /*#__PURE__*/ new WeakMap(), _timeoutForLastRequestStatus = /*#__PURE__*/ new WeakMap(), _statusMachine = /*#__PURE__*/ new WeakMap(), /**
116
116
  * We track status of a Snap using a finite-state-machine.
117
117
  * It keeps track of whether the snap is started / stopped / etc.
118
118
  *
@@ -192,7 +192,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
192
192
  * @param snapId - The snap id.
193
193
  * @param newVersionRange - The new version range being requsted.
194
194
  * @returns `true` if validation checks pass and `false` if they do not.
195
- */ _isValidUpdate = /*#__PURE__*/ new WeakSet();
195
+ */ _isValidUpdate = /*#__PURE__*/ new WeakSet(), _callLifecycleHook = /*#__PURE__*/ new WeakSet();
196
196
  /*
197
197
  * A snap is initialized in three phases:
198
198
  * - Add: Loads the snap from a remote source and parses it.
@@ -204,6 +204,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
204
204
  * blocks/unblocks snaps as appropriate. See {@link SnapController.blockSnap}
205
205
  * for more information.
206
206
  */ async updateBlockedSnaps() {
207
+ await this.messagingSystem.call('SnapsRegistry:update');
207
208
  const blockedSnaps = await this.messagingSystem.call('SnapsRegistry:get', Object.values(this.state.snaps).reduce((blockListArg, snap)=>{
208
209
  blockListArg[snap.id] = {
209
210
  version: snap.version,
@@ -244,14 +245,13 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
244
245
  *
245
246
  * @param snapId - The id of the Snap to start.
246
247
  */ async startSnap(snapId) {
247
- const runtime = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
248
- if (this.state.snaps[snapId].enabled === false) {
248
+ const snap = this.state.snaps[snapId];
249
+ if (snap.enabled === false) {
249
250
  throw new Error(`Snap "${snapId}" is disabled.`);
250
251
  }
251
- assert(runtime.sourceCode);
252
252
  await _class_private_method_get(this, _startSnap, startSnap).call(this, {
253
253
  snapId,
254
- sourceCode: runtime.sourceCode
254
+ sourceCode: snap.sourceCode
255
255
  });
256
256
  }
257
257
  /**
@@ -380,8 +380,9 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
380
380
  * @param snapId - The id of the Snap whose state should be updated.
381
381
  * @param newSnapState - The new state of the snap.
382
382
  */ async updateSnapState(snapId, newSnapState) {
383
- const runtime = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
384
- runtime.state = newSnapState;
383
+ this.update((state)=>{
384
+ state.snapStates[snapId] = newSnapState;
385
+ });
385
386
  }
386
387
  /**
387
388
  * Clears the state of the snap with the given id.
@@ -389,8 +390,9 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
389
390
  *
390
391
  * @param snapId - The id of the Snap whose state should be cleared.
391
392
  */ clearSnapState(snapId) {
392
- const runtime = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
393
- runtime.state = null;
393
+ this.update((state)=>{
394
+ state.snapStates[snapId] = null;
395
+ });
394
396
  }
395
397
  /**
396
398
  * Adds error from a snap to the SnapController state.
@@ -429,7 +431,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
429
431
  * @returns A promise that resolves with the decrypted snap state or null if no state exists.
430
432
  * @throws If the snap state decryption fails.
431
433
  */ async getSnapState(snapId) {
432
- const { state } = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
434
+ const state = this.state.snapStates[snapId];
433
435
  return state ?? null;
434
436
  }
435
437
  /**
@@ -475,7 +477,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
475
477
  await this.disableSnap(snapId);
476
478
  _class_private_method_get(this, _revokeAllSnapPermissions, revokeAllSnapPermissions).call(this, snapId);
477
479
  _class_private_method_get(this, _removeSnapFromSubjects, removeSnapFromSubjects).call(this, snapId);
478
- this.snapsRuntimeData.delete(snapId);
480
+ _class_private_field_get(this, _snapsRuntimeData).delete(snapId);
479
481
  this.update((state)=>{
480
482
  delete state.snaps[snapId];
481
483
  delete state.snapStates[snapId];
@@ -597,9 +599,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
597
599
  pendingUpdates.push(snapId);
598
600
  let rollbackSnapshot = _class_private_method_get(this, _getRollbackSnapshot, getRollbackSnapshot).call(this, snapId);
599
601
  if (rollbackSnapshot === undefined) {
600
- const prevSourceCode = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId).sourceCode;
601
602
  rollbackSnapshot = _class_private_method_get(this, _createRollbackSnapshot, createRollbackSnapshot).call(this, snapId);
602
- rollbackSnapshot.sourceCode = prevSourceCode;
603
603
  rollbackSnapshot.newVersion = version;
604
604
  } else {
605
605
  throw new Error('This snap is already being updated.');
@@ -915,12 +915,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
915
915
  anonymous: false
916
916
  },
917
917
  snapStates: {
918
- persist: ()=>{
919
- return Object.keys(this.state.snaps).reduce((acc, cur)=>{
920
- acc[cur] = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, cur).state;
921
- return acc;
922
- }, {});
923
- },
918
+ persist: true,
924
919
  anonymous: false
925
920
  },
926
921
  snaps: {
@@ -928,7 +923,6 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
928
923
  return Object.values(snaps).map((snap)=>{
929
924
  return {
930
925
  ...snap,
931
- sourceCode: _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snap.id).sourceCode,
932
926
  // At the time state is rehydrated, no snap will be running.
933
927
  status: SnapStatus.Stopped
934
928
  };
@@ -943,18 +937,7 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
943
937
  name,
944
938
  state: {
945
939
  ...defaultState,
946
- ...{
947
- ...state,
948
- snaps: Object.values(state?.snaps ?? {}).reduce((memo, snap)=>{
949
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
950
- // sourceCode is stripped out to prevent piping to MetaMask UI,
951
- // it is stored in the runtime while we're running a snap and then
952
- // persisted to state when needed.
953
- const { sourceCode, ...rest } = snap;
954
- memo[snap.id] = rest;
955
- return memo;
956
- }, {})
957
- }
940
+ ...state
958
941
  }
959
942
  });
960
943
  _class_private_method_init(this, _initializeStateMachine);
@@ -1049,6 +1032,16 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
1049
1032
  _class_private_method_init(this, _setupRuntime);
1050
1033
  _class_private_method_init(this, _calculatePermissionsChange);
1051
1034
  _class_private_method_init(this, _isValidUpdate);
1035
+ /**
1036
+ * Call a lifecycle hook on a snap, if the snap has the
1037
+ * `endowment:lifecycle-hooks` permission. If the snap does not have the
1038
+ * permission, nothing happens.
1039
+ *
1040
+ * @param snapId - The snap ID.
1041
+ * @param handler - The lifecycle hook to call. This should be one of the
1042
+ * supported lifecycle hooks.
1043
+ * @private
1044
+ */ _class_private_method_init(this, _callLifecycleHook);
1052
1045
  _class_private_field_init(this, _closeAllConnections, {
1053
1046
  writable: true,
1054
1047
  value: void 0
@@ -1087,8 +1080,10 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
1087
1080
  writable: true,
1088
1081
  value: void 0
1089
1082
  });
1090
- // This property cannot be hash private yet because of tests.
1091
- _define_property(this, "snapsRuntimeData", void 0);
1083
+ _class_private_field_init(this, _snapsRuntimeData, {
1084
+ writable: true,
1085
+ value: void 0
1086
+ });
1092
1087
  _class_private_field_init(this, _rollbackSnapshots, {
1093
1088
  writable: true,
1094
1089
  value: void 0
@@ -1115,12 +1110,22 @@ _initializeStateMachine = /*#__PURE__*/ new WeakSet(), /**
1115
1110
  this._onOutboundRequest = this._onOutboundRequest.bind(this);
1116
1111
  this._onOutboundResponse = this._onOutboundResponse.bind(this);
1117
1112
  _class_private_field_set(this, _rollbackSnapshots, new Map());
1118
- this.snapsRuntimeData = new Map();
1113
+ _class_private_field_set(this, _snapsRuntimeData, new Map());
1119
1114
  _class_private_method_get(this, _pollForLastRequestStatus, pollForLastRequestStatus).call(this);
1120
1115
  /* eslint-disable @typescript-eslint/unbound-method */ this.messagingSystem.subscribe('ExecutionService:unhandledError', this._onUnhandledSnapError);
1121
1116
  this.messagingSystem.subscribe('ExecutionService:outboundRequest', this._onOutboundRequest);
1122
1117
  this.messagingSystem.subscribe('ExecutionService:outboundResponse', this._onOutboundResponse);
1123
- /* eslint-enable @typescript-eslint/unbound-method */ _class_private_method_get(this, _initializeStateMachine, initializeStateMachine).call(this);
1118
+ /* eslint-enable @typescript-eslint/unbound-method */ this.messagingSystem.subscribe('SnapController:snapInstalled', ({ id })=>{
1119
+ _class_private_method_get(this, _callLifecycleHook, callLifecycleHook).call(this, id, HandlerType.OnInstall).catch((error)=>{
1120
+ logError(`Error when calling \`onInstall\` lifecycle hook for snap "${id}": ${getErrorMessage(error)}`);
1121
+ });
1122
+ });
1123
+ this.messagingSystem.subscribe('SnapController:snapUpdated', ({ id })=>{
1124
+ _class_private_method_get(this, _callLifecycleHook, callLifecycleHook).call(this, id, HandlerType.OnUpdate).catch((error)=>{
1125
+ logError(`Error when calling \`onUpdate\` lifecycle hook for snap "${id}": ${getErrorMessage(error)}`);
1126
+ });
1127
+ });
1128
+ _class_private_method_get(this, _initializeStateMachine, initializeStateMachine).call(this);
1124
1129
  _class_private_method_get(this, _registerMessageHandlers, registerMessageHandlers).call(this);
1125
1130
  Object.values(state?.snaps ?? {}).forEach((snap)=>_class_private_method_get(this, _setupRuntime, setupRuntime).call(this, snap.id, {
1126
1131
  sourceCode: snap.sourceCode,
@@ -1249,7 +1254,7 @@ async function assertIsInstallAllowed(snapId, snapInfo) {
1249
1254
  }
1250
1255
  async function stopSnapsLastRequestPastMax() {
1251
1256
  const entries = [
1252
- ...this.snapsRuntimeData.entries()
1257
+ ..._class_private_field_get(this, _snapsRuntimeData).entries()
1253
1258
  ];
1254
1259
  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
1255
1260
  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)));
@@ -1424,6 +1429,7 @@ function set(args) {
1424
1429
  initialPermissions: manifest.result.initialPermissions,
1425
1430
  manifest: manifest.result,
1426
1431
  status: _class_private_field_get(this, _statusMachine).config.initial,
1432
+ sourceCode,
1427
1433
  version,
1428
1434
  versionHistory
1429
1435
  };
@@ -1441,8 +1447,6 @@ function set(args) {
1441
1447
  rollbackSnapshot.statePatches = inversePatches;
1442
1448
  }
1443
1449
  }
1444
- const runtime = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
1445
- runtime.sourceCode = sourceCode;
1446
1450
  this.messagingSystem.publish(`SnapController:snapAdded`, snap, svgIcon?.toString());
1447
1451
  return {
1448
1452
  ...snap,
@@ -1472,15 +1476,12 @@ async function fetchSnap(snapId, location) {
1472
1476
  location
1473
1477
  };
1474
1478
  } catch (error) {
1475
- // TODO(ritave): Export `getErrorMessage()` from @metamask/utils and use it here
1476
- // https://github.com/MetaMask/utils/blob/62d022ef83c91fa4d150e51913be4441508a0ab1/src/assert.ts
1477
- const message = error instanceof Error ? error.message : error.toString();
1478
- throw new Error(`Failed to fetch Snap "${snapId}": ${message}.`);
1479
+ throw new Error(`Failed to fetch snap "${snapId}": ${getErrorMessage(error)}.`);
1479
1480
  }
1480
1481
  }
1481
1482
  function validateSnapPermissions(processedPermissions) {
1482
1483
  const permissionKeys = Object.keys(processedPermissions);
1483
- const handlerPermissions = Object.values(handlerEndowments);
1484
+ const handlerPermissions = Array.from(new Set(Object.values(handlerEndowments)));
1484
1485
  assert(permissionKeys.some((key)=>handlerPermissions.includes(key)), `A snap must request at least one of the following permissions: ${handlerPermissions.join(', ')}.`);
1485
1486
  const excludedPermissionErrors = permissionKeys.reduce((errors, permission)=>{
1486
1487
  if (hasProperty(_class_private_field_get(this, _excludedPermissions), permission)) {
@@ -1581,7 +1582,6 @@ function createRollbackSnapshot(snapId) {
1581
1582
  assert(_class_private_field_get(this, _rollbackSnapshots).get(snapId) === undefined, new Error(`Snap "${snapId}" rollback snapshot already exists.`));
1582
1583
  _class_private_field_get(this, _rollbackSnapshots).set(snapId, {
1583
1584
  statePatches: [],
1584
- sourceCode: '',
1585
1585
  permissions: {
1586
1586
  revoked: null,
1587
1587
  granted: [],
@@ -1603,7 +1603,7 @@ async function rollbackSnap(snapId) {
1603
1603
  if (this.get(snapId)?.status !== SnapStatus.Stopped) {
1604
1604
  _class_private_method_get(this, _transition, transition).call(this, snapId, SnapStatusEvents.Stop);
1605
1605
  }
1606
- const { statePatches, sourceCode, permissions } = rollbackSnapshot;
1606
+ const { statePatches, permissions } = rollbackSnapshot;
1607
1607
  if (statePatches?.length) {
1608
1608
  this.applyPatches(statePatches);
1609
1609
  }
@@ -1614,10 +1614,6 @@ async function rollbackSnap(snapId) {
1614
1614
  state.snaps[snapId].status = SnapStatus.Stopped;
1615
1615
  });
1616
1616
  }
1617
- if (sourceCode) {
1618
- const runtime = _class_private_method_get(this, _getRuntimeExpect, getRuntimeExpect).call(this, snapId);
1619
- runtime.sourceCode = sourceCode;
1620
- }
1621
1617
  if (permissions.revoked && Object.keys(permissions.revoked).length) {
1622
1618
  this.messagingSystem.call('PermissionController:grantPermissions', {
1623
1619
  approvedPermissions: permissions.revoked,
@@ -1642,7 +1638,7 @@ async function rollbackSnaps(snapIds) {
1642
1638
  }
1643
1639
  }
1644
1640
  function getRuntime(snapId) {
1645
- return this.snapsRuntimeData.get(snapId);
1641
+ return _class_private_field_get(this, _snapsRuntimeData).get(snapId);
1646
1642
  }
1647
1643
  function getRuntimeExpect(snapId) {
1648
1644
  const runtime = _class_private_method_get(this, _getRuntime, getRuntime).call(this, snapId);
@@ -1650,7 +1646,7 @@ function getRuntimeExpect(snapId) {
1650
1646
  return runtime;
1651
1647
  }
1652
1648
  function setupRuntime(snapId, data) {
1653
- if (this.snapsRuntimeData.has(snapId)) {
1649
+ if (_class_private_field_get(this, _snapsRuntimeData).has(snapId)) {
1654
1650
  return;
1655
1651
  }
1656
1652
  const snap = this.get(snapId);
@@ -1662,7 +1658,7 @@ function setupRuntime(snapId, data) {
1662
1658
  value: snap?.status ?? _class_private_field_get(this, _statusMachine).config.initial
1663
1659
  });
1664
1660
  forceStrict(interpreter);
1665
- this.snapsRuntimeData.set(snapId, {
1661
+ _class_private_field_get(this, _snapsRuntimeData).set(snapId, {
1666
1662
  lastRequest: null,
1667
1663
  rpcHandler: null,
1668
1664
  installPromise: null,
@@ -1698,5 +1694,21 @@ function isValidUpdate(snapId, newVersionRange) {
1698
1694
  }
1699
1695
  return true;
1700
1696
  }
1697
+ async function callLifecycleHook(snapId, handler) {
1698
+ const permissionName = handlerEndowments[handler];
1699
+ const hasPermission = this.messagingSystem.call('PermissionController:hasPermission', snapId, permissionName);
1700
+ if (!hasPermission) {
1701
+ return;
1702
+ }
1703
+ await this.handleRequest({
1704
+ snapId,
1705
+ handler,
1706
+ origin: '',
1707
+ request: {
1708
+ jsonrpc: '2.0',
1709
+ method: handler
1710
+ }
1711
+ });
1712
+ }
1701
1713
 
1702
1714
  //# sourceMappingURL=SnapController.js.map