@metamask/snaps-controllers 20.0.6 → 21.0.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.
Files changed (30) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/dist/snaps/SnapController.cjs +126 -19
  3. package/dist/snaps/SnapController.cjs.map +1 -1
  4. package/dist/snaps/SnapController.d.cts +24 -17
  5. package/dist/snaps/SnapController.d.cts.map +1 -1
  6. package/dist/snaps/SnapController.d.mts +24 -17
  7. package/dist/snaps/SnapController.d.mts.map +1 -1
  8. package/dist/snaps/SnapController.mjs +126 -19
  9. package/dist/snaps/SnapController.mjs.map +1 -1
  10. package/dist/snaps/registry/SnapRegistryController-method-action-types.cjs.map +1 -1
  11. package/dist/snaps/registry/SnapRegistryController-method-action-types.d.cts +12 -1
  12. package/dist/snaps/registry/SnapRegistryController-method-action-types.d.cts.map +1 -1
  13. package/dist/snaps/registry/SnapRegistryController-method-action-types.d.mts +12 -1
  14. package/dist/snaps/registry/SnapRegistryController-method-action-types.d.mts.map +1 -1
  15. package/dist/snaps/registry/SnapRegistryController-method-action-types.mjs.map +1 -1
  16. package/dist/snaps/registry/SnapRegistryController.cjs +28 -5
  17. package/dist/snaps/registry/SnapRegistryController.cjs.map +1 -1
  18. package/dist/snaps/registry/SnapRegistryController.d.cts +10 -1
  19. package/dist/snaps/registry/SnapRegistryController.d.cts.map +1 -1
  20. package/dist/snaps/registry/SnapRegistryController.d.mts +10 -1
  21. package/dist/snaps/registry/SnapRegistryController.d.mts.map +1 -1
  22. package/dist/snaps/registry/SnapRegistryController.mjs +28 -5
  23. package/dist/snaps/registry/SnapRegistryController.mjs.map +1 -1
  24. package/dist/snaps/registry/index.cjs.map +1 -1
  25. package/dist/snaps/registry/index.d.cts +1 -1
  26. package/dist/snaps/registry/index.d.cts.map +1 -1
  27. package/dist/snaps/registry/index.d.mts +1 -1
  28. package/dist/snaps/registry/index.d.mts.map +1 -1
  29. package/dist/snaps/registry/index.mjs.map +1 -1
  30. package/package.json +5 -4
package/CHANGELOG.md CHANGED
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [21.0.0]
11
+
12
+ ### Added
13
+
14
+ - **BREAKING:** Track Snap lifecycle analytics events in `SnapController` ([#4048](https://github.com/MetaMask/snaps/pull/4048))
15
+ - This logic was previously in the clients, but can now be removed there.
16
+ - The `SnapController` messenger now requires the following actions and events:
17
+ - `AnalyticsController:trackEvent`
18
+ - `SnapController:snapInstallStarted`
19
+ - `SnapController:snapInstallFailed`
20
+ - `SnapController:snapInstalled`
21
+ - `SnapController:snapUpdated`
22
+ - `SnapController:snapUninstalled`
23
+ - **BREAKING:** Track analytics for OTA Snap updates ([#4049](https://github.com/MetaMask/snaps/pull/4049))
24
+ - The `SnapController` constructor now requires a `clientConfig` parameter.
25
+ - Add `requestPeriodicUpdate` method to `SnapRegistryController` ([#4043](https://github.com/MetaMask/snaps/pull/4043))
26
+
27
+ ### Changed
28
+
29
+ - Bump `@metamask/storage-service` from `1.0.1` to `1.0.2` ([#4035](https://github.com/MetaMask/snaps/pull/4035))
30
+ - Bump `@metamask/approval-controller` from `9.0.1` to `9.0.2` ([#4026](https://github.com/MetaMask/snaps/pull/4026))
31
+
10
32
  ## [20.0.6]
11
33
 
12
34
  ### Changed
@@ -1234,7 +1256,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1234
1256
  - The version of the package no longer needs to match the version of all other
1235
1257
  MetaMask Snaps packages.
1236
1258
 
1237
- [Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@20.0.6...HEAD
1259
+ [Unreleased]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@21.0.0...HEAD
1260
+ [21.0.0]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@20.0.6...@metamask/snaps-controllers@21.0.0
1238
1261
  [20.0.6]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@20.0.5...@metamask/snaps-controllers@20.0.6
1239
1262
  [20.0.5]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@20.0.4...@metamask/snaps-controllers@20.0.5
1240
1263
  [20.0.4]: https://github.com/MetaMask/snaps/compare/@metamask/snaps-controllers@20.0.3...@metamask/snaps-controllers@20.0.4
@@ -103,13 +103,13 @@ class SnapController extends base_controller_1.BaseController {
103
103
  #getMnemonicSeed;
104
104
  #getFeatureFlags;
105
105
  #clientCryptography;
106
+ #clientConfig;
106
107
  #detectSnapLocation;
107
108
  #snapsRuntimeData;
108
109
  #rollbackSnapshots;
109
110
  #timeoutForLastRequestStatus;
110
111
  #statusMachine;
111
112
  #preinstalledSnaps;
112
- #trackEvent;
113
113
  #trackSnapExport;
114
114
  #ensureOnboardingComplete;
115
115
  // A promise that resolves when the controller has finished setting up.
@@ -117,7 +117,7 @@ class SnapController extends base_controller_1.BaseController {
117
117
  // It is resolved when the controller has finished setting up and the platform is ready.
118
118
  // It is rejected if there is an error during setup.
119
119
  #controllerSetup = (0, utils_1.createDeferredPromise)();
120
- constructor({ messenger, state, dynamicPermissions = ['endowment:caip25', 'wallet_snap'], environmentEndowmentPermissions = [], excludedPermissions = {}, idleTimeCheckInterval = (0, utils_1.inMilliseconds)(5, utils_1.Duration.Second), maxIdleTime = (0, utils_1.inMilliseconds)(30, utils_1.Duration.Second), maxRequestTime = (0, utils_1.inMilliseconds)(60, utils_1.Duration.Second), fetchFunction = globalThis.fetch.bind(undefined), featureFlags = {}, detectSnapLocation: detectSnapLocationFunction = location_1.detectSnapLocation, preinstalledSnaps = null, encryptor, getMnemonicSeed, getFeatureFlags = () => ({}), clientCryptography, trackEvent, ensureOnboardingComplete, }) {
120
+ constructor({ messenger, state, dynamicPermissions = ['endowment:caip25', 'wallet_snap'], environmentEndowmentPermissions = [], excludedPermissions = {}, idleTimeCheckInterval = (0, utils_1.inMilliseconds)(5, utils_1.Duration.Second), maxIdleTime = (0, utils_1.inMilliseconds)(30, utils_1.Duration.Second), maxRequestTime = (0, utils_1.inMilliseconds)(60, utils_1.Duration.Second), fetchFunction = globalThis.fetch.bind(undefined), featureFlags = {}, detectSnapLocation: detectSnapLocationFunction = location_1.detectSnapLocation, preinstalledSnaps = null, encryptor, getMnemonicSeed, getFeatureFlags = () => ({}), clientCryptography, ensureOnboardingComplete, clientConfig, }) {
121
121
  super({
122
122
  messenger,
123
123
  metadata: {
@@ -191,13 +191,13 @@ class SnapController extends base_controller_1.BaseController {
191
191
  this.#getMnemonicSeed = getMnemonicSeed;
192
192
  this.#getFeatureFlags = getFeatureFlags;
193
193
  this.#clientCryptography = clientCryptography;
194
+ this.#clientConfig = clientConfig;
194
195
  this.#preinstalledSnaps = preinstalledSnaps;
195
196
  this._onUnhandledSnapError = this._onUnhandledSnapError.bind(this);
196
197
  this._onOutboundRequest = this._onOutboundRequest.bind(this);
197
198
  this._onOutboundResponse = this._onOutboundResponse.bind(this);
198
199
  this.#rollbackSnapshots = new Map();
199
200
  this.#snapsRuntimeData = new Map();
200
- this.#trackEvent = trackEvent;
201
201
  this.#ensureOnboardingComplete = ensureOnboardingComplete;
202
202
  this.#pollForLastRequestStatus();
203
203
  /* eslint-disable @typescript-eslint/unbound-method */
@@ -205,14 +205,117 @@ class SnapController extends base_controller_1.BaseController {
205
205
  this.messenger.subscribe('ExecutionService:outboundRequest', this._onOutboundRequest);
206
206
  this.messenger.subscribe('ExecutionService:outboundResponse', this._onOutboundResponse);
207
207
  /* eslint-enable @typescript-eslint/unbound-method */
208
- this.messenger.subscribe('SnapController:snapInstalled', ({ id }, origin) => {
209
- this.#callLifecycleHook(origin, id, snaps_utils_1.HandlerType.OnInstall).catch((error) => {
210
- (0, snaps_utils_1.logError)(`Error when calling \`onInstall\` lifecycle hook for snap "${id}": ${(0, snaps_sdk_1.getErrorMessage)(error)}`);
208
+ this.messenger.subscribe('SnapController:snapInstalled', (snap, origin, preinstalled) => {
209
+ this.#callLifecycleHook(origin, snap.id, snaps_utils_1.HandlerType.OnInstall).catch((error) => {
210
+ (0, snaps_utils_1.logError)(`Error when calling \`onInstall\` lifecycle hook for snap "${snap.id}": ${(0, snaps_sdk_1.getErrorMessage)(error)}`);
211
+ });
212
+ if (preinstalled) {
213
+ return;
214
+ }
215
+ const snapMetadata = this.messenger.call('SnapRegistryController:getMetadata', snap.id);
216
+ this.messenger.call('AnalyticsController:trackEvent', {
217
+ name: 'Snap Installed',
218
+ properties: {
219
+ // eslint-disable-next-line @typescript-eslint/naming-convention
220
+ snap_id: snap.id,
221
+ version: snap.version,
222
+ origin,
223
+ // eslint-disable-next-line @typescript-eslint/naming-convention
224
+ snap_category: snapMetadata?.category ?? null,
225
+ },
226
+ sensitiveProperties: {},
227
+ saveDataRecording: false,
228
+ hasProperties: true,
229
+ });
230
+ });
231
+ this.messenger.subscribe('SnapController:snapUpdated', (snap, oldVersion, origin, preinstalled, ota) => {
232
+ this.#callLifecycleHook(origin, snap.id, snaps_utils_1.HandlerType.OnUpdate).catch((error) => {
233
+ (0, snaps_utils_1.logError)(`Error when calling \`onUpdate\` lifecycle hook for snap "${snap.id}": ${(0, snaps_sdk_1.getErrorMessage)(error)}`);
234
+ });
235
+ if (preinstalled && !ota) {
236
+ return;
237
+ }
238
+ const snapMetadata = this.messenger.call('SnapRegistryController:getMetadata', snap.id);
239
+ this.messenger.call('AnalyticsController:trackEvent', {
240
+ name: 'Snap Updated',
241
+ /* eslint-disable @typescript-eslint/naming-convention */
242
+ properties: {
243
+ snap_id: snap.id,
244
+ old_version: oldVersion,
245
+ new_version: snap.version,
246
+ origin,
247
+ snap_category: snapMetadata?.category ?? null,
248
+ ota,
249
+ client_version: this.#clientConfig.version,
250
+ client_type: this.#clientConfig.type,
251
+ },
252
+ /* eslint-enable @typescript-eslint/naming-convention */
253
+ sensitiveProperties: {},
254
+ saveDataRecording: false,
255
+ hasProperties: true,
256
+ });
257
+ });
258
+ this.messenger.subscribe('SnapController:snapInstallStarted', (snapId, origin, isUpdate, preinstalled) => {
259
+ if (preinstalled) {
260
+ return;
261
+ }
262
+ const snapMetadata = this.messenger.call('SnapRegistryController:getMetadata', snapId);
263
+ this.messenger.call('AnalyticsController:trackEvent', {
264
+ name: isUpdate ? 'Snap Update Started' : 'Snap Install Started',
265
+ properties: {
266
+ // eslint-disable-next-line @typescript-eslint/naming-convention
267
+ snap_id: snapId,
268
+ origin,
269
+ // eslint-disable-next-line @typescript-eslint/naming-convention
270
+ snap_category: snapMetadata?.category ?? null,
271
+ },
272
+ sensitiveProperties: {},
273
+ saveDataRecording: false,
274
+ hasProperties: true,
275
+ });
276
+ });
277
+ this.messenger.subscribe('SnapController:snapInstallFailed', (snapId, origin, isUpdate, error, preinstalled) => {
278
+ if (preinstalled) {
279
+ return;
280
+ }
281
+ const isRejected = error.includes('User rejected the request.');
282
+ const snapMetadata = this.messenger.call('SnapRegistryController:getMetadata', snapId);
283
+ // eslint-disable-next-line no-nested-ternary
284
+ const name = isUpdate
285
+ ? isRejected
286
+ ? 'Snap Update Rejected'
287
+ : 'Snap Update Failed'
288
+ : isRejected
289
+ ? 'Snap Install Rejected'
290
+ : 'Snap Install Failed';
291
+ this.messenger.call('AnalyticsController:trackEvent', {
292
+ name,
293
+ properties: {
294
+ // eslint-disable-next-line @typescript-eslint/naming-convention
295
+ snap_id: snapId,
296
+ origin,
297
+ // eslint-disable-next-line @typescript-eslint/naming-convention
298
+ snap_category: snapMetadata?.category ?? null,
299
+ },
300
+ sensitiveProperties: {},
301
+ saveDataRecording: false,
302
+ hasProperties: true,
211
303
  });
212
304
  });
213
- this.messenger.subscribe('SnapController:snapUpdated', ({ id }, _oldVersion, origin) => {
214
- this.#callLifecycleHook(origin, id, snaps_utils_1.HandlerType.OnUpdate).catch((error) => {
215
- (0, snaps_utils_1.logError)(`Error when calling \`onUpdate\` lifecycle hook for snap "${id}": ${(0, snaps_sdk_1.getErrorMessage)(error)}`);
305
+ this.messenger.subscribe('SnapController:snapUninstalled', (snap) => {
306
+ const snapMetadata = this.messenger.call('SnapRegistryController:getMetadata', snap.id);
307
+ this.messenger.call('AnalyticsController:trackEvent', {
308
+ name: 'Snap Uninstalled',
309
+ properties: {
310
+ // eslint-disable-next-line @typescript-eslint/naming-convention
311
+ snap_id: snap.id,
312
+ version: snap.version,
313
+ // eslint-disable-next-line @typescript-eslint/naming-convention
314
+ snap_category: snapMetadata?.category ?? null,
315
+ },
316
+ sensitiveProperties: {},
317
+ saveDataRecording: false,
318
+ hasProperties: true,
216
319
  });
217
320
  });
218
321
  this.messenger.subscribe('KeyringController:lock', this.#handleLock.bind(this));
@@ -226,18 +329,20 @@ class SnapController extends base_controller_1.BaseController {
226
329
  Object.values(this.state?.snaps ?? {}).forEach((snap) => this.#setupRuntime(snap.id));
227
330
  this.#trackSnapExport = (0, utils_2.throttleTracking)((snapId, handler, success, origin) => {
228
331
  const snapMetadata = this.messenger.call('SnapRegistryController:getMetadata', snapId);
229
- this.#trackEvent({
230
- event: 'Snap Export Used',
231
- category: 'Snaps',
332
+ this.messenger.call('AnalyticsController:trackEvent', {
333
+ name: 'Snap Export Used',
232
334
  properties: {
233
335
  // eslint-disable-next-line @typescript-eslint/naming-convention
234
336
  snap_id: snapId,
235
337
  export: handler,
236
338
  // eslint-disable-next-line @typescript-eslint/naming-convention
237
- snap_category: snapMetadata?.category,
339
+ snap_category: snapMetadata?.category ?? null,
238
340
  success,
239
341
  origin,
240
342
  },
343
+ sensitiveProperties: {},
344
+ saveDataRecording: false,
345
+ hasProperties: true,
241
346
  });
242
347
  });
243
348
  }
@@ -424,7 +529,7 @@ class SnapController extends base_controller_1.BaseController {
424
529
  this.#setupRuntime(snapId);
425
530
  // Emit events
426
531
  if (isUpdate) {
427
- this.messenger.publish('SnapController:snapUpdated', this.#getTruncatedSnapExpect(snapId), existingSnap.version, constants_1.METAMASK_ORIGIN, true);
532
+ this.messenger.publish('SnapController:snapUpdated', this.#getTruncatedSnapExpect(snapId), existingSnap.version, constants_1.METAMASK_ORIGIN, true, false);
428
533
  }
429
534
  else if (!isMissingSource) {
430
535
  this.messenger.publish('SnapController:snapInstalled', this.#getTruncatedSnapExpect(snapId), constants_1.METAMASK_ORIGIN, true);
@@ -516,6 +621,7 @@ class SnapController extends base_controller_1.BaseController {
516
621
  const resolvedVersion = await this.#resolveAllowlistVersion(snap.id, preinstalledVersionRange, true);
517
622
  if (resolvedVersion !== preinstalledVersionRange &&
518
623
  (0, utils_1.gtVersion)(resolvedVersion, snap.version)) {
624
+ const oldVersion = snap.version;
519
625
  const location = this.#detectSnapLocation(snap.id, {
520
626
  versionRange: resolvedVersion,
521
627
  fetch: this.#fetchFunction,
@@ -529,6 +635,7 @@ class SnapController extends base_controller_1.BaseController {
529
635
  versionRange: resolvedVersion,
530
636
  automaticUpdate: true,
531
637
  });
638
+ this.messenger.publish('SnapController:snapUpdated', this.#getTruncatedSnapExpect(snap.id), oldVersion, approval_controller_1.ORIGIN_METAMASK, true, true);
532
639
  }
533
640
  }));
534
641
  }
@@ -1373,7 +1480,7 @@ class SnapController extends base_controller_1.BaseController {
1373
1480
  }
1374
1481
  // Once we finish all installs / updates, emit events.
1375
1482
  pendingInstalls.forEach((snapId) => this.messenger.publish(`SnapController:snapInstalled`, this.#getTruncatedSnapExpect(snapId), origin, false));
1376
- pendingUpdates.forEach(({ snapId, oldVersion }) => this.messenger.publish(`SnapController:snapUpdated`, this.#getTruncatedSnapExpect(snapId), oldVersion, origin, false));
1483
+ pendingUpdates.forEach(({ snapId, oldVersion }) => this.messenger.publish(`SnapController:snapUpdated`, this.#getTruncatedSnapExpect(snapId), oldVersion, origin, false, false));
1377
1484
  snapIds.forEach((snapId) => this.#rollbackSnapshots.delete(snapId));
1378
1485
  }
1379
1486
  catch (error) {
@@ -1418,7 +1525,7 @@ class SnapController extends base_controller_1.BaseController {
1418
1525
  snapId,
1419
1526
  type: exports.SNAP_APPROVAL_INSTALL,
1420
1527
  });
1421
- this.messenger.publish('SnapController:snapInstallStarted', snapId, origin, false);
1528
+ this.messenger.publish('SnapController:snapInstallStarted', snapId, origin, false, false);
1422
1529
  // Existing snaps must be stopped before overwriting
1423
1530
  if (existingSnap && this.isSnapRunning(snapId)) {
1424
1531
  await this.stopSnap(snapId, snaps_utils_1.SnapStatusEvents.Stop);
@@ -1459,7 +1566,7 @@ class SnapController extends base_controller_1.BaseController {
1459
1566
  type: exports.SNAP_APPROVAL_INSTALL,
1460
1567
  error: errorString,
1461
1568
  });
1462
- this.messenger.publish('SnapController:snapInstallFailed', snapId, origin, false, errorString);
1569
+ this.messenger.publish('SnapController:snapInstallFailed', snapId, origin, false, errorString, false);
1463
1570
  throw error;
1464
1571
  }
1465
1572
  }
@@ -1530,7 +1637,7 @@ class SnapController extends base_controller_1.BaseController {
1530
1637
  type: exports.SNAP_APPROVAL_UPDATE,
1531
1638
  });
1532
1639
  try {
1533
- this.messenger.publish('SnapController:snapInstallStarted', snapId, origin, true);
1640
+ this.messenger.publish('SnapController:snapInstallStarted', snapId, origin, true, preinstalled ?? false);
1534
1641
  const oldManifest = snap.manifest;
1535
1642
  const newSnap = await (0, utils_2.fetchSnap)(snapId, location);
1536
1643
  const { sourceCode: sourceCodeFile, manifest: manifestFile } = newSnap;
@@ -1638,7 +1745,7 @@ class SnapController extends base_controller_1.BaseController {
1638
1745
  type: exports.SNAP_APPROVAL_UPDATE,
1639
1746
  });
1640
1747
  }
1641
- this.messenger.publish('SnapController:snapInstallFailed', snapId, origin, true, errorString);
1748
+ this.messenger.publish('SnapController:snapInstallFailed', snapId, origin, true, errorString, preinstalled ?? false);
1642
1749
  throw error;
1643
1750
  }
1644
1751
  }