@metamask-previews/profile-metrics-controller 2.0.0-preview-1137ade3 → 2.0.0-preview-fa81dffb

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.
package/CHANGELOG.md CHANGED
@@ -7,10 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ - `ProfileMetricsController` contructor now accepts an optional `initialDelayDuration` parameter ([#7624](https://github.com/MetaMask/core/pull/7624))
13
+ - The parameter can be used to override the default time-based delay for the first data collection after opt-in
14
+ - Add `skipInitialDelay()` method to `ProfileMetricsController` ([#7624](https://github.com/MetaMask/core/pull/7624))
15
+ - The method can be also called through the `ProfileMetricsController:skipInitialDelay` action via messenger
16
+
10
17
  ### Changed
11
18
 
19
+ - **BREAKING:** `ProileMetricsControllerMessenger` now requires the `TransactionController:transactionSubmitted` action to be allowed ([#7624](https://github.com/MetaMask/core/pull/7624))
20
+ - Set time-based delay for first `ProfileMetricsController` data collection after opt-in ([#7624](https://github.com/MetaMask/core/pull/7624))
12
21
  - Upgrade `@metamask/utils` from `^11.8.1` to `^11.9.0` ([#7511](https://github.com/MetaMask/core/pull/7511))
13
22
  - Bump `@metamask/controller-utils` from `^11.16.0` to `^11.18.0` ([#7534](https://github.com/MetaMask/core/pull/7534), [#7583](https://github.com/MetaMask/core/pull/7583))
23
+ - Bump `@metamask/accounts-controller` from `^35.0.0` to `^35.0.2` ([#7604](https://github.com/MetaMask/core/pull/7604), [#7642](https://github.com/MetaMask/core/pull/7642))
24
+ - Bump `@metamask/polling-controller` from `^16.0.0` to `^16.0.2` ([#7604](https://github.com/MetaMask/core/pull/7604), [#7642](https://github.com/MetaMask/core/pull/7642))
14
25
 
15
26
  ## [2.0.0]
16
27
 
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * This file is auto generated by `scripts/generate-method-action-types.ts`.
4
+ * Do not edit manually.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ //# sourceMappingURL=ProfileMetricsController-method-action-types.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProfileMetricsController-method-action-types.cjs","sourceRoot":"","sources":["../src/ProfileMetricsController-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated by `scripts/generate-method-action-types.ts`.\n * Do not edit manually.\n */\n\nimport type { ProfileMetricsController } from './ProfileMetricsController';\n\n/**\n * Skip the initial delay period by setting the end timestamp to the current time.\n * Metrics will be sent on the next poll.\n */\nexport type ProfileMetricsControllerSkipInitialDelayAction = {\n type: `ProfileMetricsController:skipInitialDelay`;\n handler: ProfileMetricsController['skipInitialDelay'];\n};\n\n/**\n * Union of all ProfileMetricsController action types.\n */\nexport type ProfileMetricsControllerMethodActions =\n ProfileMetricsControllerSkipInitialDelayAction;\n"]}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * This file is auto generated by `scripts/generate-method-action-types.ts`.
3
+ * Do not edit manually.
4
+ */
5
+ import type { ProfileMetricsController } from "./ProfileMetricsController.cjs";
6
+ /**
7
+ * Skip the initial delay period by setting the end timestamp to the current time.
8
+ * Metrics will be sent on the next poll.
9
+ */
10
+ export type ProfileMetricsControllerSkipInitialDelayAction = {
11
+ type: `ProfileMetricsController:skipInitialDelay`;
12
+ handler: ProfileMetricsController['skipInitialDelay'];
13
+ };
14
+ /**
15
+ * Union of all ProfileMetricsController action types.
16
+ */
17
+ export type ProfileMetricsControllerMethodActions = ProfileMetricsControllerSkipInitialDelayAction;
18
+ //# sourceMappingURL=ProfileMetricsController-method-action-types.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProfileMetricsController-method-action-types.d.cts","sourceRoot":"","sources":["../src/ProfileMetricsController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,wBAAwB,EAAE,uCAAmC;AAE3E;;;GAGG;AACH,MAAM,MAAM,8CAA8C,GAAG;IAC3D,IAAI,EAAE,2CAA2C,CAAC;IAClD,OAAO,EAAE,wBAAwB,CAAC,kBAAkB,CAAC,CAAC;CACvD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,qCAAqC,GAC/C,8CAA8C,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * This file is auto generated by `scripts/generate-method-action-types.ts`.
3
+ * Do not edit manually.
4
+ */
5
+ import type { ProfileMetricsController } from "./ProfileMetricsController.mjs";
6
+ /**
7
+ * Skip the initial delay period by setting the end timestamp to the current time.
8
+ * Metrics will be sent on the next poll.
9
+ */
10
+ export type ProfileMetricsControllerSkipInitialDelayAction = {
11
+ type: `ProfileMetricsController:skipInitialDelay`;
12
+ handler: ProfileMetricsController['skipInitialDelay'];
13
+ };
14
+ /**
15
+ * Union of all ProfileMetricsController action types.
16
+ */
17
+ export type ProfileMetricsControllerMethodActions = ProfileMetricsControllerSkipInitialDelayAction;
18
+ //# sourceMappingURL=ProfileMetricsController-method-action-types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProfileMetricsController-method-action-types.d.mts","sourceRoot":"","sources":["../src/ProfileMetricsController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,wBAAwB,EAAE,uCAAmC;AAE3E;;;GAGG;AACH,MAAM,MAAM,8CAA8C,GAAG;IAC3D,IAAI,EAAE,2CAA2C,CAAC;IAClD,OAAO,EAAE,wBAAwB,CAAC,kBAAkB,CAAC,CAAC;CACvD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,qCAAqC,GAC/C,8CAA8C,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * This file is auto generated by `scripts/generate-method-action-types.ts`.
3
+ * Do not edit manually.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=ProfileMetricsController-method-action-types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProfileMetricsController-method-action-types.mjs","sourceRoot":"","sources":["../src/ProfileMetricsController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated by `scripts/generate-method-action-types.ts`.\n * Do not edit manually.\n */\n\nimport type { ProfileMetricsController } from './ProfileMetricsController';\n\n/**\n * Skip the initial delay period by setting the end timestamp to the current time.\n * Metrics will be sent on the next poll.\n */\nexport type ProfileMetricsControllerSkipInitialDelayAction = {\n type: `ProfileMetricsController:skipInitialDelay`;\n handler: ProfileMetricsController['skipInitialDelay'];\n};\n\n/**\n * Union of all ProfileMetricsController action types.\n */\nexport type ProfileMetricsControllerMethodActions =\n ProfileMetricsControllerSkipInitialDelayAction;\n"]}
@@ -10,10 +10,11 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _ProfileMetricsController_instances, _ProfileMetricsController_mutex, _ProfileMetricsController_assertUserOptedIn, _ProfileMetricsController_getMetaMetricsId, _ProfileMetricsController_queueFirstSyncIfNeeded, _ProfileMetricsController_addAccountToQueue, _ProfileMetricsController_removeAccountFromQueue;
13
+ var _ProfileMetricsController_instances, _ProfileMetricsController_mutex, _ProfileMetricsController_assertUserOptedIn, _ProfileMetricsController_getMetaMetricsId, _ProfileMetricsController_initialDelayDuration, _ProfileMetricsController_queueFirstSyncIfNeeded, _ProfileMetricsController_setInitialDelayEndTimestampIfNull, _ProfileMetricsController_isInitialDelayComplete, _ProfileMetricsController_addAccountToQueue, _ProfileMetricsController_removeAccountFromQueue;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.ProfileMetricsController = exports.getDefaultProfileMetricsControllerState = exports.controllerName = void 0;
15
+ exports.ProfileMetricsController = exports.getDefaultProfileMetricsControllerState = exports.DEFAULT_INITIAL_DELAY_DURATION = exports.controllerName = void 0;
16
16
  const polling_controller_1 = require("@metamask/polling-controller");
17
+ const utils_1 = require("@metamask/utils");
17
18
  const async_mutex_1 = require("async-mutex");
18
19
  /**
19
20
  * The name of the {@link ProfileMetricsController}, used to namespace the
@@ -21,6 +22,10 @@ const async_mutex_1 = require("async-mutex");
21
22
  * when composed with other controllers.
22
23
  */
23
24
  exports.controllerName = 'ProfileMetricsController';
25
+ /**
26
+ * The default delay duration before data is sent for the first time.
27
+ */
28
+ exports.DEFAULT_INITIAL_DELAY_DURATION = (0, utils_1.inMilliseconds)(10, utils_1.Duration.Minute);
24
29
  /**
25
30
  * The metadata for each property in {@link ProfileMetricsControllerState}.
26
31
  */
@@ -37,6 +42,12 @@ const profileMetricsControllerMetadata = {
37
42
  includeInStateLogs: true,
38
43
  usedInUi: false,
39
44
  },
45
+ initialDelayEndTimestamp: {
46
+ persist: true,
47
+ includeInDebugSnapshot: true,
48
+ includeInStateLogs: true,
49
+ usedInUi: false,
50
+ },
40
51
  };
41
52
  /**
42
53
  * Constructs the default {@link ProfileMetricsController} state. This allows
@@ -53,7 +64,7 @@ function getDefaultProfileMetricsControllerState() {
53
64
  };
54
65
  }
55
66
  exports.getDefaultProfileMetricsControllerState = getDefaultProfileMetricsControllerState;
56
- const MESSENGER_EXPOSED_METHODS = [];
67
+ const MESSENGER_EXPOSED_METHODS = ['skipInitialDelay'];
57
68
  class ProfileMetricsController extends (0, polling_controller_1.StaticIntervalPollingController)() {
58
69
  /**
59
70
  * Constructs a new {@link ProfileMetricsController}.
@@ -69,8 +80,10 @@ class ProfileMetricsController extends (0, polling_controller_1.StaticIntervalPo
69
80
  * of the user.
70
81
  * @param args.interval - The interval, in milliseconds, at which the controller will
71
82
  * attempt to send user profile data. Defaults to 10 seconds.
83
+ * @param args.initialDelayDuration - The delay duration before data is sent
84
+ * for the first time, in milliseconds. Defaults to 10 minutes.
72
85
  */
73
- constructor({ messenger, state, assertUserOptedIn, getMetaMetricsId, interval = 10 * 1000, }) {
86
+ constructor({ messenger, state, assertUserOptedIn, getMetaMetricsId, interval = 10 * 1000, initialDelayDuration = exports.DEFAULT_INITIAL_DELAY_DURATION, }) {
74
87
  super({
75
88
  messenger,
76
89
  metadata: profileMetricsControllerMetadata,
@@ -84,18 +97,22 @@ class ProfileMetricsController extends (0, polling_controller_1.StaticIntervalPo
84
97
  _ProfileMetricsController_mutex.set(this, new async_mutex_1.Mutex());
85
98
  _ProfileMetricsController_assertUserOptedIn.set(this, void 0);
86
99
  _ProfileMetricsController_getMetaMetricsId.set(this, void 0);
100
+ _ProfileMetricsController_initialDelayDuration.set(this, void 0);
87
101
  __classPrivateFieldSet(this, _ProfileMetricsController_assertUserOptedIn, assertUserOptedIn, "f");
88
102
  __classPrivateFieldSet(this, _ProfileMetricsController_getMetaMetricsId, getMetaMetricsId, "f");
103
+ __classPrivateFieldSet(this, _ProfileMetricsController_initialDelayDuration, initialDelayDuration, "f");
89
104
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
90
105
  this.messenger.subscribe('KeyringController:unlock', () => {
91
- __classPrivateFieldGet(this, _ProfileMetricsController_instances, "m", _ProfileMetricsController_queueFirstSyncIfNeeded).call(this).catch(console.error);
92
106
  if (__classPrivateFieldGet(this, _ProfileMetricsController_assertUserOptedIn, "f").call(this)) {
93
- this.startPolling(null);
107
+ // If the user has already opted in at the start of the session,
108
+ // it must have opted in during onboarding, or during a previous session.
109
+ this.skipInitialDelay();
94
110
  }
111
+ __classPrivateFieldGet(this, _ProfileMetricsController_instances, "m", _ProfileMetricsController_queueFirstSyncIfNeeded).call(this).catch(this.messenger.captureException ?? console.error);
112
+ this.startPolling(null);
95
113
  });
96
- this.messenger.subscribe('KeyringController:lock', () => {
97
- this.stopAllPolling();
98
- });
114
+ this.messenger.subscribe('KeyringController:lock', () => this.stopAllPolling());
115
+ this.messenger.subscribe('TransactionController:transactionSubmitted', () => this.skipInitialDelay());
99
116
  this.messenger.subscribe('AccountsController:accountAdded', (account) => {
100
117
  __classPrivateFieldGet(this, _ProfileMetricsController_instances, "m", _ProfileMetricsController_addAccountToQueue).call(this, account).catch(console.error);
101
118
  });
@@ -104,6 +121,15 @@ class ProfileMetricsController extends (0, polling_controller_1.StaticIntervalPo
104
121
  });
105
122
  this.setIntervalLength(interval);
106
123
  }
124
+ /**
125
+ * Skip the initial delay period by setting the end timestamp to the current time.
126
+ * Metrics will be sent on the next poll.
127
+ */
128
+ skipInitialDelay() {
129
+ this.update((state) => {
130
+ state.initialDelayEndTimestamp = Date.now();
131
+ });
132
+ }
107
133
  /**
108
134
  * Execute a single poll to sync user profile data.
109
135
  *
@@ -118,6 +144,10 @@ class ProfileMetricsController extends (0, polling_controller_1.StaticIntervalPo
118
144
  if (!__classPrivateFieldGet(this, _ProfileMetricsController_assertUserOptedIn, "f").call(this)) {
119
145
  return;
120
146
  }
147
+ __classPrivateFieldGet(this, _ProfileMetricsController_instances, "m", _ProfileMetricsController_setInitialDelayEndTimestampIfNull).call(this);
148
+ if (!__classPrivateFieldGet(this, _ProfileMetricsController_instances, "m", _ProfileMetricsController_isInitialDelayComplete).call(this)) {
149
+ return;
150
+ }
121
151
  for (const [entropySourceId, accounts] of Object.entries(this.state.syncQueue)) {
122
152
  try {
123
153
  await this.messenger.call('ProfileMetricsService:submitMetrics', {
@@ -138,7 +168,7 @@ class ProfileMetricsController extends (0, polling_controller_1.StaticIntervalPo
138
168
  }
139
169
  }
140
170
  exports.ProfileMetricsController = ProfileMetricsController;
141
- _ProfileMetricsController_mutex = new WeakMap(), _ProfileMetricsController_assertUserOptedIn = new WeakMap(), _ProfileMetricsController_getMetaMetricsId = new WeakMap(), _ProfileMetricsController_instances = new WeakSet(), _ProfileMetricsController_queueFirstSyncIfNeeded =
171
+ _ProfileMetricsController_mutex = new WeakMap(), _ProfileMetricsController_assertUserOptedIn = new WeakMap(), _ProfileMetricsController_getMetaMetricsId = new WeakMap(), _ProfileMetricsController_initialDelayDuration = new WeakMap(), _ProfileMetricsController_instances = new WeakSet(), _ProfileMetricsController_queueFirstSyncIfNeeded =
142
172
  /**
143
173
  * Add existing accounts to the sync queue if it has not been done yet.
144
174
  *
@@ -162,6 +192,13 @@ async function _ProfileMetricsController_queueFirstSyncIfNeeded() {
162
192
  state.initialEnqueueCompleted = true;
163
193
  });
164
194
  });
195
+ }, _ProfileMetricsController_setInitialDelayEndTimestampIfNull = function _ProfileMetricsController_setInitialDelayEndTimestampIfNull() {
196
+ this.update((state) => {
197
+ state.initialDelayEndTimestamp ?? (state.initialDelayEndTimestamp = Date.now() + __classPrivateFieldGet(this, _ProfileMetricsController_initialDelayDuration, "f"));
198
+ });
199
+ }, _ProfileMetricsController_isInitialDelayComplete = function _ProfileMetricsController_isInitialDelayComplete() {
200
+ return (this.state.initialDelayEndTimestamp !== undefined &&
201
+ Date.now() >= this.state.initialDelayEndTimestamp);
165
202
  }, _ProfileMetricsController_addAccountToQueue =
166
203
  /**
167
204
  * Queue the given account to be synced at the next poll.
@@ -1 +1 @@
1
- {"version":3,"file":"ProfileMetricsController.cjs","sourceRoot":"","sources":["../src/ProfileMetricsController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountsControllerAccountAddedEvent,\n AccountsControllerAccountRemovedEvent,\n AccountsControllerGetStateAction,\n} from '@metamask/accounts-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport type {\n KeyringControllerLockEvent,\n KeyringControllerUnlockEvent,\n} from '@metamask/keyring-controller';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport { Mutex } from 'async-mutex';\n\nimport type { ProfileMetricsServiceMethodActions } from '.';\nimport type { AccountWithScopes } from './ProfileMetricsService';\n\n/**\n * The name of the {@link ProfileMetricsController}, used to namespace the\n * controller's actions and events and to namespace the controller's state data\n * when composed with other controllers.\n */\nexport const controllerName = 'ProfileMetricsController';\n\n/**\n * Describes the shape of the state object for {@link ProfileMetricsController}.\n */\nexport type ProfileMetricsControllerState = {\n /**\n * Whether existing accounts have been added\n * to the queue.\n */\n initialEnqueueCompleted: boolean;\n /**\n * The queue of accounts to be synced.\n * Each key is an entropy source ID, and each value is an array of account\n * addresses associated with that entropy source. Accounts with no entropy\n * source ID are grouped under the key \"null\".\n */\n syncQueue: Record<string, AccountWithScopes[]>;\n};\n\n/**\n * The metadata for each property in {@link ProfileMetricsControllerState}.\n */\nconst profileMetricsControllerMetadata = {\n initialEnqueueCompleted: {\n persist: true,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: false,\n },\n syncQueue: {\n persist: true,\n includeInDebugSnapshot: false,\n includeInStateLogs: true,\n usedInUi: false,\n },\n} satisfies StateMetadata<ProfileMetricsControllerState>;\n\n/**\n * Constructs the default {@link ProfileMetricsController} state. This allows\n * consumers to provide a partial state object when initializing the controller\n * and also helps in constructing complete state objects for this controller in\n * tests.\n *\n * @returns The default {@link ProfileMetricsController} state.\n */\nexport function getDefaultProfileMetricsControllerState(): ProfileMetricsControllerState {\n return {\n initialEnqueueCompleted: false,\n syncQueue: {},\n };\n}\n\nconst MESSENGER_EXPOSED_METHODS = [] as const;\n\n/**\n * Retrieves the state of the {@link ProfileMetricsController}.\n */\nexport type ProfileMetricsControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n ProfileMetricsControllerState\n>;\n\n/**\n * Actions that {@link ProfileMetricsControllerMessenger} exposes to other consumers.\n */\nexport type ProfileMetricsControllerActions =\n | ProfileMetricsControllerGetStateAction\n | ProfileMetricsServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link ProfileMetricsControllerMessenger} calls.\n */\ntype AllowedActions =\n | ProfileMetricsServiceMethodActions\n | AccountsControllerGetStateAction;\n\n/**\n * Published when the state of {@link ProfileMetricsController} changes.\n */\nexport type ProfileMetricsControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n ProfileMetricsControllerState\n >;\n\n/**\n * Events that {@link ProfileMetricsControllerMessenger} exposes to other consumers.\n */\nexport type ProfileMetricsControllerEvents =\n ProfileMetricsControllerStateChangeEvent;\n\n/**\n * Events from other messengers that {@link ProfileMetricsControllerMessenger} subscribes\n * to.\n */\ntype AllowedEvents =\n | KeyringControllerUnlockEvent\n | KeyringControllerLockEvent\n | AccountsControllerAccountAddedEvent\n | AccountsControllerAccountRemovedEvent;\n\n/**\n * The messenger restricted to actions and events accessed by\n * {@link ProfileMetricsController}.\n */\nexport type ProfileMetricsControllerMessenger = Messenger<\n typeof controllerName,\n ProfileMetricsControllerActions | AllowedActions,\n ProfileMetricsControllerEvents | AllowedEvents\n>;\n\nexport class ProfileMetricsController extends StaticIntervalPollingController()<\n typeof controllerName,\n ProfileMetricsControllerState,\n ProfileMetricsControllerMessenger\n> {\n readonly #mutex = new Mutex();\n\n readonly #assertUserOptedIn: () => boolean;\n\n readonly #getMetaMetricsId: () => string;\n\n /**\n * Constructs a new {@link ProfileMetricsController}.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this controller.\n * @param args.state - The desired state with which to initialize this\n * controller. Missing properties will be filled in with defaults.\n * @param args.assertUserOptedIn - A function that asserts whether the user has\n * opted in to user profile features. If the user has not opted in, sync\n * operations will be no-ops.\n * @param args.getMetaMetricsId - A function that returns the MetaMetrics ID\n * of the user.\n * @param args.interval - The interval, in milliseconds, at which the controller will\n * attempt to send user profile data. Defaults to 10 seconds.\n */\n constructor({\n messenger,\n state,\n assertUserOptedIn,\n getMetaMetricsId,\n interval = 10 * 1000,\n }: {\n messenger: ProfileMetricsControllerMessenger;\n state?: Partial<ProfileMetricsControllerState>;\n interval?: number;\n assertUserOptedIn: () => boolean;\n getMetaMetricsId: () => string;\n }) {\n super({\n messenger,\n metadata: profileMetricsControllerMetadata,\n name: controllerName,\n state: {\n ...getDefaultProfileMetricsControllerState(),\n ...state,\n },\n });\n\n this.#assertUserOptedIn = assertUserOptedIn;\n this.#getMetaMetricsId = getMetaMetricsId;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.messenger.subscribe('KeyringController:unlock', () => {\n this.#queueFirstSyncIfNeeded().catch(console.error);\n if (this.#assertUserOptedIn()) {\n this.startPolling(null);\n }\n });\n\n this.messenger.subscribe('KeyringController:lock', () => {\n this.stopAllPolling();\n });\n\n this.messenger.subscribe('AccountsController:accountAdded', (account) => {\n this.#addAccountToQueue(account).catch(console.error);\n });\n\n this.messenger.subscribe('AccountsController:accountRemoved', (account) => {\n this.#removeAccountFromQueue(account).catch(console.error);\n });\n\n this.setIntervalLength(interval);\n }\n\n /**\n * Execute a single poll to sync user profile data.\n *\n * The queued accounts are sent to the ProfileMetricsService, and the sync\n * queue is cleared. This operation is mutexed to prevent concurrent\n * executions.\n *\n * @returns A promise that resolves when the poll is complete.\n */\n async _executePoll(): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n if (!this.#assertUserOptedIn()) {\n return;\n }\n for (const [entropySourceId, accounts] of Object.entries(\n this.state.syncQueue,\n )) {\n try {\n await this.messenger.call('ProfileMetricsService:submitMetrics', {\n metametricsId: this.#getMetaMetricsId(),\n entropySourceId:\n entropySourceId === 'null' ? null : entropySourceId,\n accounts,\n });\n this.update((state) => {\n delete state.syncQueue[entropySourceId];\n });\n } catch (error) {\n // We want to log the error but continue processing other batches.\n console.error(\n `Failed to submit profile metrics for entropy source ID ${entropySourceId}:`,\n error,\n );\n }\n }\n });\n }\n\n /**\n * Add existing accounts to the sync queue if it has not been done yet.\n *\n * This method ensures that the first sync is only executed once,\n * and only if the user has opted in to user profile features.\n */\n async #queueFirstSyncIfNeeded(): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n if (this.state.initialEnqueueCompleted) {\n return;\n }\n const newGroupedAccounts = groupAccountsByEntropySourceId(\n Object.values(\n this.messenger.call('AccountsController:getState').internalAccounts\n .accounts,\n ),\n );\n this.update((state) => {\n for (const key of Object.keys(newGroupedAccounts)) {\n if (!state.syncQueue[key]) {\n state.syncQueue[key] = [];\n }\n state.syncQueue[key].push(...newGroupedAccounts[key]);\n }\n state.initialEnqueueCompleted = true;\n });\n });\n }\n\n /**\n * Queue the given account to be synced at the next poll.\n *\n * @param account - The account to sync.\n */\n async #addAccountToQueue(account: InternalAccount): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n this.update((state) => {\n const entropySourceId = getAccountEntropySourceId(account) ?? 'null';\n if (!state.syncQueue[entropySourceId]) {\n state.syncQueue[entropySourceId] = [];\n }\n state.syncQueue[entropySourceId].push({\n address: account.address,\n scopes: account.scopes,\n });\n });\n });\n }\n\n /**\n * Remove the given account from the sync queue.\n *\n * @param account - The account address to remove.\n */\n async #removeAccountFromQueue(account: string): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n this.update((state) => {\n for (const [entropySourceId, groupedAddresses] of Object.entries(\n state.syncQueue,\n )) {\n const index = groupedAddresses.findIndex(\n ({ address }) => address === account,\n );\n if (index === -1) {\n continue;\n }\n groupedAddresses.splice(index, 1);\n if (groupedAddresses.length === 0) {\n delete state.syncQueue[entropySourceId];\n }\n break;\n }\n });\n });\n }\n}\n\n/**\n * Retrieves the entropy source ID from the given account, if it exists.\n *\n * @param account - The account from which to retrieve the entropy source ID.\n * @returns The entropy source ID, or null if it does not exist.\n */\nfunction getAccountEntropySourceId(account: InternalAccount): string | null {\n if (account.options.entropy?.type === 'mnemonic') {\n return account.options.entropy.id;\n }\n return null;\n}\n\n/**\n * Groups accounts by their entropy source ID.\n *\n * @param accounts - The accounts to group.\n * @returns An object where each key is an entropy source ID and each value is\n * an array of account addresses associated with that entropy source ID.\n */\nfunction groupAccountsByEntropySourceId(\n accounts: InternalAccount[],\n): Record<string, AccountWithScopes[]> {\n return accounts.reduce(\n (result: Record<string, AccountWithScopes[]>, account) => {\n const entropySourceId = getAccountEntropySourceId(account);\n const key = entropySourceId ?? 'null';\n if (!result[key]) {\n result[key] = [];\n }\n result[key].push({ address: account.address, scopes: account.scopes });\n return result;\n },\n {},\n );\n}\n"]}
1
+ {"version":3,"file":"ProfileMetricsController.cjs","sourceRoot":"","sources":["../src/ProfileMetricsController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountsControllerAccountAddedEvent,\n AccountsControllerAccountRemovedEvent,\n AccountsControllerGetStateAction,\n} from '@metamask/accounts-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport type {\n KeyringControllerLockEvent,\n KeyringControllerUnlockEvent,\n} from '@metamask/keyring-controller';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport { TransactionControllerTransactionSubmittedEvent } from '@metamask/transaction-controller';\nimport { Duration, inMilliseconds } from '@metamask/utils';\nimport { Mutex } from 'async-mutex';\n\nimport type { ProfileMetricsServiceMethodActions } from '.';\nimport type { ProfileMetricsControllerMethodActions } from '.';\nimport type { AccountWithScopes } from './ProfileMetricsService';\n\n/**\n * The name of the {@link ProfileMetricsController}, used to namespace the\n * controller's actions and events and to namespace the controller's state data\n * when composed with other controllers.\n */\nexport const controllerName = 'ProfileMetricsController';\n\n/**\n * The default delay duration before data is sent for the first time.\n */\nexport const DEFAULT_INITIAL_DELAY_DURATION = inMilliseconds(\n 10,\n Duration.Minute,\n);\n\n/**\n * Describes the shape of the state object for {@link ProfileMetricsController}.\n */\nexport type ProfileMetricsControllerState = {\n /**\n * Whether existing accounts have been added\n * to the queue.\n */\n initialEnqueueCompleted: boolean;\n /**\n * The queue of accounts to be synced.\n * Each key is an entropy source ID, and each value is an array of account\n * addresses associated with that entropy source. Accounts with no entropy\n * source ID are grouped under the key \"null\".\n */\n syncQueue: Record<string, AccountWithScopes[]>;\n /**\n * The timestamp when the first data sending can be attempted.\n */\n initialDelayEndTimestamp?: number;\n};\n\n/**\n * The metadata for each property in {@link ProfileMetricsControllerState}.\n */\nconst profileMetricsControllerMetadata = {\n initialEnqueueCompleted: {\n persist: true,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: false,\n },\n syncQueue: {\n persist: true,\n includeInDebugSnapshot: false,\n includeInStateLogs: true,\n usedInUi: false,\n },\n initialDelayEndTimestamp: {\n persist: true,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: false,\n },\n} satisfies StateMetadata<ProfileMetricsControllerState>;\n\n/**\n * Constructs the default {@link ProfileMetricsController} state. This allows\n * consumers to provide a partial state object when initializing the controller\n * and also helps in constructing complete state objects for this controller in\n * tests.\n *\n * @returns The default {@link ProfileMetricsController} state.\n */\nexport function getDefaultProfileMetricsControllerState(): ProfileMetricsControllerState {\n return {\n initialEnqueueCompleted: false,\n syncQueue: {},\n };\n}\n\nconst MESSENGER_EXPOSED_METHODS = ['skipInitialDelay'] as const;\n\n/**\n * Retrieves the state of the {@link ProfileMetricsController}.\n */\nexport type ProfileMetricsControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n ProfileMetricsControllerState\n>;\n\n/**\n * Actions that {@link ProfileMetricsControllerMessenger} exposes to other consumers.\n */\nexport type ProfileMetricsControllerActions =\n | ProfileMetricsControllerGetStateAction\n | ProfileMetricsControllerMethodActions;\n\n/**\n * Actions from other messengers that {@link ProfileMetricsControllerMessenger} calls.\n */\ntype AllowedActions =\n | ProfileMetricsServiceMethodActions\n | AccountsControllerGetStateAction;\n\n/**\n * Published when the state of {@link ProfileMetricsController} changes.\n */\nexport type ProfileMetricsControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n ProfileMetricsControllerState\n >;\n\n/**\n * Events that {@link ProfileMetricsControllerMessenger} exposes to other consumers.\n */\nexport type ProfileMetricsControllerEvents =\n ProfileMetricsControllerStateChangeEvent;\n\n/**\n * Events from other messengers that {@link ProfileMetricsControllerMessenger} subscribes\n * to.\n */\ntype AllowedEvents =\n | KeyringControllerUnlockEvent\n | KeyringControllerLockEvent\n | AccountsControllerAccountAddedEvent\n | AccountsControllerAccountRemovedEvent\n | TransactionControllerTransactionSubmittedEvent;\n\n/**\n * The messenger restricted to actions and events accessed by\n * {@link ProfileMetricsController}.\n */\nexport type ProfileMetricsControllerMessenger = Messenger<\n typeof controllerName,\n ProfileMetricsControllerActions | AllowedActions,\n ProfileMetricsControllerEvents | AllowedEvents\n>;\n\nexport class ProfileMetricsController extends StaticIntervalPollingController()<\n typeof controllerName,\n ProfileMetricsControllerState,\n ProfileMetricsControllerMessenger\n> {\n readonly #mutex = new Mutex();\n\n readonly #assertUserOptedIn: () => boolean;\n\n readonly #getMetaMetricsId: () => string;\n\n readonly #initialDelayDuration: number;\n\n /**\n * Constructs a new {@link ProfileMetricsController}.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this controller.\n * @param args.state - The desired state with which to initialize this\n * controller. Missing properties will be filled in with defaults.\n * @param args.assertUserOptedIn - A function that asserts whether the user has\n * opted in to user profile features. If the user has not opted in, sync\n * operations will be no-ops.\n * @param args.getMetaMetricsId - A function that returns the MetaMetrics ID\n * of the user.\n * @param args.interval - The interval, in milliseconds, at which the controller will\n * attempt to send user profile data. Defaults to 10 seconds.\n * @param args.initialDelayDuration - The delay duration before data is sent\n * for the first time, in milliseconds. Defaults to 10 minutes.\n */\n constructor({\n messenger,\n state,\n assertUserOptedIn,\n getMetaMetricsId,\n interval = 10 * 1000,\n initialDelayDuration = DEFAULT_INITIAL_DELAY_DURATION,\n }: {\n messenger: ProfileMetricsControllerMessenger;\n state?: Partial<ProfileMetricsControllerState>;\n interval?: number;\n assertUserOptedIn: () => boolean;\n getMetaMetricsId: () => string;\n initialDelayDuration?: number;\n }) {\n super({\n messenger,\n metadata: profileMetricsControllerMetadata,\n name: controllerName,\n state: {\n ...getDefaultProfileMetricsControllerState(),\n ...state,\n },\n });\n\n this.#assertUserOptedIn = assertUserOptedIn;\n this.#getMetaMetricsId = getMetaMetricsId;\n this.#initialDelayDuration = initialDelayDuration;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.messenger.subscribe('KeyringController:unlock', () => {\n if (this.#assertUserOptedIn()) {\n // If the user has already opted in at the start of the session,\n // it must have opted in during onboarding, or during a previous session.\n this.skipInitialDelay();\n }\n this.#queueFirstSyncIfNeeded().catch(\n this.messenger.captureException ?? console.error,\n );\n this.startPolling(null);\n });\n\n this.messenger.subscribe('KeyringController:lock', () =>\n this.stopAllPolling(),\n );\n\n this.messenger.subscribe('TransactionController:transactionSubmitted', () =>\n this.skipInitialDelay(),\n );\n\n this.messenger.subscribe('AccountsController:accountAdded', (account) => {\n this.#addAccountToQueue(account).catch(console.error);\n });\n\n this.messenger.subscribe('AccountsController:accountRemoved', (account) => {\n this.#removeAccountFromQueue(account).catch(console.error);\n });\n\n this.setIntervalLength(interval);\n }\n\n /**\n * Skip the initial delay period by setting the end timestamp to the current time.\n * Metrics will be sent on the next poll.\n */\n skipInitialDelay(): void {\n this.update((state) => {\n state.initialDelayEndTimestamp = Date.now();\n });\n }\n\n /**\n * Execute a single poll to sync user profile data.\n *\n * The queued accounts are sent to the ProfileMetricsService, and the sync\n * queue is cleared. This operation is mutexed to prevent concurrent\n * executions.\n *\n * @returns A promise that resolves when the poll is complete.\n */\n async _executePoll(): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n if (!this.#assertUserOptedIn()) {\n return;\n }\n this.#setInitialDelayEndTimestampIfNull();\n if (!this.#isInitialDelayComplete()) {\n return;\n }\n for (const [entropySourceId, accounts] of Object.entries(\n this.state.syncQueue,\n )) {\n try {\n await this.messenger.call('ProfileMetricsService:submitMetrics', {\n metametricsId: this.#getMetaMetricsId(),\n entropySourceId:\n entropySourceId === 'null' ? null : entropySourceId,\n accounts,\n });\n this.update((state) => {\n delete state.syncQueue[entropySourceId];\n });\n } catch (error) {\n // We want to log the error but continue processing other batches.\n console.error(\n `Failed to submit profile metrics for entropy source ID ${entropySourceId}:`,\n error,\n );\n }\n }\n });\n }\n\n /**\n * Add existing accounts to the sync queue if it has not been done yet.\n *\n * This method ensures that the first sync is only executed once,\n * and only if the user has opted in to user profile features.\n */\n async #queueFirstSyncIfNeeded(): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n if (this.state.initialEnqueueCompleted) {\n return;\n }\n const newGroupedAccounts = groupAccountsByEntropySourceId(\n Object.values(\n this.messenger.call('AccountsController:getState').internalAccounts\n .accounts,\n ),\n );\n this.update((state) => {\n for (const key of Object.keys(newGroupedAccounts)) {\n if (!state.syncQueue[key]) {\n state.syncQueue[key] = [];\n }\n state.syncQueue[key].push(...newGroupedAccounts[key]);\n }\n state.initialEnqueueCompleted = true;\n });\n });\n }\n\n /**\n * Set the initial delay end timestamp if it is not already set.\n */\n #setInitialDelayEndTimestampIfNull(): void {\n this.update((state) => {\n state.initialDelayEndTimestamp ??=\n Date.now() + this.#initialDelayDuration;\n });\n }\n\n /**\n * Check if the initial delay end timestamp is in the past.\n *\n * @returns True if the initial delay period has completed, false otherwise.\n */\n #isInitialDelayComplete(): boolean {\n return (\n this.state.initialDelayEndTimestamp !== undefined &&\n Date.now() >= this.state.initialDelayEndTimestamp\n );\n }\n\n /**\n * Queue the given account to be synced at the next poll.\n *\n * @param account - The account to sync.\n */\n async #addAccountToQueue(account: InternalAccount): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n this.update((state) => {\n const entropySourceId = getAccountEntropySourceId(account) ?? 'null';\n if (!state.syncQueue[entropySourceId]) {\n state.syncQueue[entropySourceId] = [];\n }\n state.syncQueue[entropySourceId].push({\n address: account.address,\n scopes: account.scopes,\n });\n });\n });\n }\n\n /**\n * Remove the given account from the sync queue.\n *\n * @param account - The account address to remove.\n */\n async #removeAccountFromQueue(account: string): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n this.update((state) => {\n for (const [entropySourceId, groupedAddresses] of Object.entries(\n state.syncQueue,\n )) {\n const index = groupedAddresses.findIndex(\n ({ address }) => address === account,\n );\n if (index === -1) {\n continue;\n }\n groupedAddresses.splice(index, 1);\n if (groupedAddresses.length === 0) {\n delete state.syncQueue[entropySourceId];\n }\n break;\n }\n });\n });\n }\n}\n\n/**\n * Retrieves the entropy source ID from the given account, if it exists.\n *\n * @param account - The account from which to retrieve the entropy source ID.\n * @returns The entropy source ID, or null if it does not exist.\n */\nfunction getAccountEntropySourceId(account: InternalAccount): string | null {\n if (account.options.entropy?.type === 'mnemonic') {\n return account.options.entropy.id;\n }\n return null;\n}\n\n/**\n * Groups accounts by their entropy source ID.\n *\n * @param accounts - The accounts to group.\n * @returns An object where each key is an entropy source ID and each value is\n * an array of account addresses associated with that entropy source ID.\n */\nfunction groupAccountsByEntropySourceId(\n accounts: InternalAccount[],\n): Record<string, AccountWithScopes[]> {\n return accounts.reduce(\n (result: Record<string, AccountWithScopes[]>, account) => {\n const entropySourceId = getAccountEntropySourceId(account);\n const key = entropySourceId ?? 'null';\n if (!result[key]) {\n result[key] = [];\n }\n result[key].push({ address: account.address, scopes: account.scopes });\n return result;\n },\n {},\n );\n}\n"]}
@@ -3,7 +3,9 @@ import type { AccountsControllerAccountAddedEvent, AccountsControllerAccountRemo
3
3
  import type { ControllerGetStateAction, ControllerStateChangeEvent } from "@metamask/base-controller";
4
4
  import type { KeyringControllerLockEvent, KeyringControllerUnlockEvent } from "@metamask/keyring-controller";
5
5
  import type { Messenger } from "@metamask/messenger";
6
+ import { TransactionControllerTransactionSubmittedEvent } from "@metamask/transaction-controller";
6
7
  import type { ProfileMetricsServiceMethodActions } from "./index.cjs";
8
+ import type { ProfileMetricsControllerMethodActions } from "./index.cjs";
7
9
  import type { AccountWithScopes } from "./ProfileMetricsService.cjs";
8
10
  /**
9
11
  * The name of the {@link ProfileMetricsController}, used to namespace the
@@ -11,6 +13,10 @@ import type { AccountWithScopes } from "./ProfileMetricsService.cjs";
11
13
  * when composed with other controllers.
12
14
  */
13
15
  export declare const controllerName = "ProfileMetricsController";
16
+ /**
17
+ * The default delay duration before data is sent for the first time.
18
+ */
19
+ export declare const DEFAULT_INITIAL_DELAY_DURATION: number;
14
20
  /**
15
21
  * Describes the shape of the state object for {@link ProfileMetricsController}.
16
22
  */
@@ -27,6 +33,10 @@ export type ProfileMetricsControllerState = {
27
33
  * source ID are grouped under the key "null".
28
34
  */
29
35
  syncQueue: Record<string, AccountWithScopes[]>;
36
+ /**
37
+ * The timestamp when the first data sending can be attempted.
38
+ */
39
+ initialDelayEndTimestamp?: number;
30
40
  };
31
41
  /**
32
42
  * Constructs the default {@link ProfileMetricsController} state. This allows
@@ -44,7 +54,7 @@ export type ProfileMetricsControllerGetStateAction = ControllerGetStateAction<ty
44
54
  /**
45
55
  * Actions that {@link ProfileMetricsControllerMessenger} exposes to other consumers.
46
56
  */
47
- export type ProfileMetricsControllerActions = ProfileMetricsControllerGetStateAction | ProfileMetricsServiceMethodActions;
57
+ export type ProfileMetricsControllerActions = ProfileMetricsControllerGetStateAction | ProfileMetricsControllerMethodActions;
48
58
  /**
49
59
  * Actions from other messengers that {@link ProfileMetricsControllerMessenger} calls.
50
60
  */
@@ -61,7 +71,7 @@ export type ProfileMetricsControllerEvents = ProfileMetricsControllerStateChange
61
71
  * Events from other messengers that {@link ProfileMetricsControllerMessenger} subscribes
62
72
  * to.
63
73
  */
64
- type AllowedEvents = KeyringControllerUnlockEvent | KeyringControllerLockEvent | AccountsControllerAccountAddedEvent | AccountsControllerAccountRemovedEvent;
74
+ type AllowedEvents = KeyringControllerUnlockEvent | KeyringControllerLockEvent | AccountsControllerAccountAddedEvent | AccountsControllerAccountRemovedEvent | TransactionControllerTransactionSubmittedEvent;
65
75
  /**
66
76
  * The messenger restricted to actions and events accessed by
67
77
  * {@link ProfileMetricsController}.
@@ -69,12 +79,6 @@ type AllowedEvents = KeyringControllerUnlockEvent | KeyringControllerLockEvent |
69
79
  export type ProfileMetricsControllerMessenger = Messenger<typeof controllerName, ProfileMetricsControllerActions | AllowedActions, ProfileMetricsControllerEvents | AllowedEvents>;
70
80
  declare const ProfileMetricsController_base: (abstract new (...args: any[]) => {
71
81
  readonly "__#14@#intervalIds": Record<string, NodeJS.Timeout>;
72
- /**
73
- * The queue of accounts to be synced.
74
- * Each key is an entropy source ID, and each value is an array of account
75
- * addresses associated with that entropy source. Accounts with no entropy
76
- * source ID are grouped under the key "null".
77
- */
78
82
  "__#14@#intervalLength": number | undefined;
79
83
  setIntervalLength(intervalLength: number): void;
80
84
  getIntervalLength(): number | undefined;
@@ -104,14 +108,22 @@ export declare class ProfileMetricsController extends ProfileMetricsController_b
104
108
  * of the user.
105
109
  * @param args.interval - The interval, in milliseconds, at which the controller will
106
110
  * attempt to send user profile data. Defaults to 10 seconds.
111
+ * @param args.initialDelayDuration - The delay duration before data is sent
112
+ * for the first time, in milliseconds. Defaults to 10 minutes.
107
113
  */
108
- constructor({ messenger, state, assertUserOptedIn, getMetaMetricsId, interval, }: {
114
+ constructor({ messenger, state, assertUserOptedIn, getMetaMetricsId, interval, initialDelayDuration, }: {
109
115
  messenger: ProfileMetricsControllerMessenger;
110
116
  state?: Partial<ProfileMetricsControllerState>;
111
117
  interval?: number;
112
118
  assertUserOptedIn: () => boolean;
113
119
  getMetaMetricsId: () => string;
120
+ initialDelayDuration?: number;
114
121
  });
122
+ /**
123
+ * Skip the initial delay period by setting the end timestamp to the current time.
124
+ * Metrics will be sent on the next poll.
125
+ */
126
+ skipInitialDelay(): void;
115
127
  /**
116
128
  * Execute a single poll to sync user profile data.
117
129
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ProfileMetricsController.d.cts","sourceRoot":"","sources":["../src/ProfileMetricsController.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EACV,mCAAmC,EACnC,qCAAqC,EACrC,gCAAgC,EACjC,sCAAsC;AACvC,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAE3B,kCAAkC;AACnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,4BAA4B,EAC7B,qCAAqC;AAEtC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAIrD,OAAO,KAAK,EAAE,kCAAkC,EAAE,oBAAU;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,oCAAgC;AAEjE;;;;GAIG;AACH,eAAO,MAAM,cAAc,6BAA6B,CAAC;AAEzD;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C;;;OAGG;IACH,uBAAuB,EAAE,OAAO,CAAC;IACjC;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;CAChD,CAAC;AAoBF;;;;;;;GAOG;AACH,wBAAgB,uCAAuC,IAAI,6BAA6B,CAKvF;AAID;;GAEG;AACH,MAAM,MAAM,sCAAsC,GAAG,wBAAwB,CAC3E,OAAO,cAAc,EACrB,6BAA6B,CAC9B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,+BAA+B,GACvC,sCAAsC,GACtC,kCAAkC,CAAC;AAEvC;;GAEG;AACH,KAAK,cAAc,GACf,kCAAkC,GAClC,gCAAgC,CAAC;AAErC;;GAEG;AACH,MAAM,MAAM,wCAAwC,GAClD,0BAA0B,CACxB,OAAO,cAAc,EACrB,6BAA6B,CAC9B,CAAC;AAEJ;;GAEG;AACH,MAAM,MAAM,8BAA8B,GACxC,wCAAwC,CAAC;AAE3C;;;GAGG;AACH,KAAK,aAAa,GACd,4BAA4B,GAC5B,0BAA0B,GAC1B,mCAAmC,GACnC,qCAAqC,CAAC;AAE1C;;;GAGG;AACH,MAAM,MAAM,iCAAiC,GAAG,SAAS,CACvD,OAAO,cAAc,EACrB,+BAA+B,GAAG,cAAc,EAChD,8BAA8B,GAAG,aAAa,CAC/C,CAAC;;;IAnGA;;;;;OAKG;;;;;;;;;;;;;;AAgGL,qBAAa,wBAAyB,SAAQ,8BAC5C,OAAO,cAAc,EACrB,6BAA6B,EAC7B,iCAAiC,CAClC;;IAOC;;;;;;;;;;;;;;OAcG;gBACS,EACV,SAAS,EACT,KAAK,EACL,iBAAiB,EACjB,gBAAgB,EAChB,QAAoB,GACrB,EAAE;QACD,SAAS,EAAE,iCAAiC,CAAC;QAC7C,KAAK,CAAC,EAAE,OAAO,CAAC,6BAA6B,CAAC,CAAC;QAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,iBAAiB,EAAE,MAAM,OAAO,CAAC;QACjC,gBAAgB,EAAE,MAAM,MAAM,CAAC;KAChC;IAyCD;;;;;;;;OAQG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;CAwGpC"}
1
+ {"version":3,"file":"ProfileMetricsController.d.cts","sourceRoot":"","sources":["../src/ProfileMetricsController.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EACV,mCAAmC,EACnC,qCAAqC,EACrC,gCAAgC,EACjC,sCAAsC;AACvC,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAE3B,kCAAkC;AACnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,4BAA4B,EAC7B,qCAAqC;AAEtC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAErD,OAAO,EAAE,8CAA8C,EAAE,yCAAyC;AAIlG,OAAO,KAAK,EAAE,kCAAkC,EAAE,oBAAU;AAC5D,OAAO,KAAK,EAAE,qCAAqC,EAAE,oBAAU;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,oCAAgC;AAEjE;;;;GAIG;AACH,eAAO,MAAM,cAAc,6BAA6B,CAAC;AAEzD;;GAEG;AACH,eAAO,MAAM,8BAA8B,QAG1C,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C;;;OAGG;IACH,uBAAuB,EAAE,OAAO,CAAC;IACjC;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAC/C;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC,CAAC;AA0BF;;;;;;;GAOG;AACH,wBAAgB,uCAAuC,IAAI,6BAA6B,CAKvF;AAID;;GAEG;AACH,MAAM,MAAM,sCAAsC,GAAG,wBAAwB,CAC3E,OAAO,cAAc,EACrB,6BAA6B,CAC9B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,+BAA+B,GACvC,sCAAsC,GACtC,qCAAqC,CAAC;AAE1C;;GAEG;AACH,KAAK,cAAc,GACf,kCAAkC,GAClC,gCAAgC,CAAC;AAErC;;GAEG;AACH,MAAM,MAAM,wCAAwC,GAClD,0BAA0B,CACxB,OAAO,cAAc,EACrB,6BAA6B,CAC9B,CAAC;AAEJ;;GAEG;AACH,MAAM,MAAM,8BAA8B,GACxC,wCAAwC,CAAC;AAE3C;;;GAGG;AACH,KAAK,aAAa,GACd,4BAA4B,GAC5B,0BAA0B,GAC1B,mCAAmC,GACnC,qCAAqC,GACrC,8CAA8C,CAAC;AAEnD;;;GAGG;AACH,MAAM,MAAM,iCAAiC,GAAG,SAAS,CACvD,OAAO,cAAc,EACrB,+BAA+B,GAAG,cAAc,EAChD,8BAA8B,GAAG,aAAa,CAC/C,CAAC;;;;;;;;;;;;;;;;AAEF,qBAAa,wBAAyB,SAAQ,8BAC5C,OAAO,cAAc,EACrB,6BAA6B,EAC7B,iCAAiC,CAClC;;IASC;;;;;;;;;;;;;;;;OAgBG;gBACS,EACV,SAAS,EACT,KAAK,EACL,iBAAiB,EACjB,gBAAgB,EAChB,QAAoB,EACpB,oBAAqD,GACtD,EAAE;QACD,SAAS,EAAE,iCAAiC,CAAC;QAC7C,KAAK,CAAC,EAAE,OAAO,CAAC,6BAA6B,CAAC,CAAC;QAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,iBAAiB,EAAE,MAAM,OAAO,CAAC;QACjC,gBAAgB,EAAE,MAAM,MAAM,CAAC;QAC/B,oBAAoB,CAAC,EAAE,MAAM,CAAC;KAC/B;IAmDD;;;OAGG;IACH,gBAAgB,IAAI,IAAI;IAMxB;;;;;;;;OAQG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;CAkIpC"}
@@ -3,7 +3,9 @@ import type { AccountsControllerAccountAddedEvent, AccountsControllerAccountRemo
3
3
  import type { ControllerGetStateAction, ControllerStateChangeEvent } from "@metamask/base-controller";
4
4
  import type { KeyringControllerLockEvent, KeyringControllerUnlockEvent } from "@metamask/keyring-controller";
5
5
  import type { Messenger } from "@metamask/messenger";
6
+ import { TransactionControllerTransactionSubmittedEvent } from "@metamask/transaction-controller";
6
7
  import type { ProfileMetricsServiceMethodActions } from "./index.mjs";
8
+ import type { ProfileMetricsControllerMethodActions } from "./index.mjs";
7
9
  import type { AccountWithScopes } from "./ProfileMetricsService.mjs";
8
10
  /**
9
11
  * The name of the {@link ProfileMetricsController}, used to namespace the
@@ -11,6 +13,10 @@ import type { AccountWithScopes } from "./ProfileMetricsService.mjs";
11
13
  * when composed with other controllers.
12
14
  */
13
15
  export declare const controllerName = "ProfileMetricsController";
16
+ /**
17
+ * The default delay duration before data is sent for the first time.
18
+ */
19
+ export declare const DEFAULT_INITIAL_DELAY_DURATION: number;
14
20
  /**
15
21
  * Describes the shape of the state object for {@link ProfileMetricsController}.
16
22
  */
@@ -27,6 +33,10 @@ export type ProfileMetricsControllerState = {
27
33
  * source ID are grouped under the key "null".
28
34
  */
29
35
  syncQueue: Record<string, AccountWithScopes[]>;
36
+ /**
37
+ * The timestamp when the first data sending can be attempted.
38
+ */
39
+ initialDelayEndTimestamp?: number;
30
40
  };
31
41
  /**
32
42
  * Constructs the default {@link ProfileMetricsController} state. This allows
@@ -44,7 +54,7 @@ export type ProfileMetricsControllerGetStateAction = ControllerGetStateAction<ty
44
54
  /**
45
55
  * Actions that {@link ProfileMetricsControllerMessenger} exposes to other consumers.
46
56
  */
47
- export type ProfileMetricsControllerActions = ProfileMetricsControllerGetStateAction | ProfileMetricsServiceMethodActions;
57
+ export type ProfileMetricsControllerActions = ProfileMetricsControllerGetStateAction | ProfileMetricsControllerMethodActions;
48
58
  /**
49
59
  * Actions from other messengers that {@link ProfileMetricsControllerMessenger} calls.
50
60
  */
@@ -61,7 +71,7 @@ export type ProfileMetricsControllerEvents = ProfileMetricsControllerStateChange
61
71
  * Events from other messengers that {@link ProfileMetricsControllerMessenger} subscribes
62
72
  * to.
63
73
  */
64
- type AllowedEvents = KeyringControllerUnlockEvent | KeyringControllerLockEvent | AccountsControllerAccountAddedEvent | AccountsControllerAccountRemovedEvent;
74
+ type AllowedEvents = KeyringControllerUnlockEvent | KeyringControllerLockEvent | AccountsControllerAccountAddedEvent | AccountsControllerAccountRemovedEvent | TransactionControllerTransactionSubmittedEvent;
65
75
  /**
66
76
  * The messenger restricted to actions and events accessed by
67
77
  * {@link ProfileMetricsController}.
@@ -69,12 +79,6 @@ type AllowedEvents = KeyringControllerUnlockEvent | KeyringControllerLockEvent |
69
79
  export type ProfileMetricsControllerMessenger = Messenger<typeof controllerName, ProfileMetricsControllerActions | AllowedActions, ProfileMetricsControllerEvents | AllowedEvents>;
70
80
  declare const ProfileMetricsController_base: (abstract new (...args: any[]) => {
71
81
  readonly "__#14@#intervalIds": Record<string, NodeJS.Timeout>;
72
- /**
73
- * The queue of accounts to be synced.
74
- * Each key is an entropy source ID, and each value is an array of account
75
- * addresses associated with that entropy source. Accounts with no entropy
76
- * source ID are grouped under the key "null".
77
- */
78
82
  "__#14@#intervalLength": number | undefined;
79
83
  setIntervalLength(intervalLength: number): void;
80
84
  getIntervalLength(): number | undefined;
@@ -104,14 +108,22 @@ export declare class ProfileMetricsController extends ProfileMetricsController_b
104
108
  * of the user.
105
109
  * @param args.interval - The interval, in milliseconds, at which the controller will
106
110
  * attempt to send user profile data. Defaults to 10 seconds.
111
+ * @param args.initialDelayDuration - The delay duration before data is sent
112
+ * for the first time, in milliseconds. Defaults to 10 minutes.
107
113
  */
108
- constructor({ messenger, state, assertUserOptedIn, getMetaMetricsId, interval, }: {
114
+ constructor({ messenger, state, assertUserOptedIn, getMetaMetricsId, interval, initialDelayDuration, }: {
109
115
  messenger: ProfileMetricsControllerMessenger;
110
116
  state?: Partial<ProfileMetricsControllerState>;
111
117
  interval?: number;
112
118
  assertUserOptedIn: () => boolean;
113
119
  getMetaMetricsId: () => string;
120
+ initialDelayDuration?: number;
114
121
  });
122
+ /**
123
+ * Skip the initial delay period by setting the end timestamp to the current time.
124
+ * Metrics will be sent on the next poll.
125
+ */
126
+ skipInitialDelay(): void;
115
127
  /**
116
128
  * Execute a single poll to sync user profile data.
117
129
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ProfileMetricsController.d.mts","sourceRoot":"","sources":["../src/ProfileMetricsController.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EACV,mCAAmC,EACnC,qCAAqC,EACrC,gCAAgC,EACjC,sCAAsC;AACvC,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAE3B,kCAAkC;AACnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,4BAA4B,EAC7B,qCAAqC;AAEtC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAIrD,OAAO,KAAK,EAAE,kCAAkC,EAAE,oBAAU;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,oCAAgC;AAEjE;;;;GAIG;AACH,eAAO,MAAM,cAAc,6BAA6B,CAAC;AAEzD;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C;;;OAGG;IACH,uBAAuB,EAAE,OAAO,CAAC;IACjC;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;CAChD,CAAC;AAoBF;;;;;;;GAOG;AACH,wBAAgB,uCAAuC,IAAI,6BAA6B,CAKvF;AAID;;GAEG;AACH,MAAM,MAAM,sCAAsC,GAAG,wBAAwB,CAC3E,OAAO,cAAc,EACrB,6BAA6B,CAC9B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,+BAA+B,GACvC,sCAAsC,GACtC,kCAAkC,CAAC;AAEvC;;GAEG;AACH,KAAK,cAAc,GACf,kCAAkC,GAClC,gCAAgC,CAAC;AAErC;;GAEG;AACH,MAAM,MAAM,wCAAwC,GAClD,0BAA0B,CACxB,OAAO,cAAc,EACrB,6BAA6B,CAC9B,CAAC;AAEJ;;GAEG;AACH,MAAM,MAAM,8BAA8B,GACxC,wCAAwC,CAAC;AAE3C;;;GAGG;AACH,KAAK,aAAa,GACd,4BAA4B,GAC5B,0BAA0B,GAC1B,mCAAmC,GACnC,qCAAqC,CAAC;AAE1C;;;GAGG;AACH,MAAM,MAAM,iCAAiC,GAAG,SAAS,CACvD,OAAO,cAAc,EACrB,+BAA+B,GAAG,cAAc,EAChD,8BAA8B,GAAG,aAAa,CAC/C,CAAC;;;IAnGA;;;;;OAKG;;;;;;;;;;;;;;AAgGL,qBAAa,wBAAyB,SAAQ,8BAC5C,OAAO,cAAc,EACrB,6BAA6B,EAC7B,iCAAiC,CAClC;;IAOC;;;;;;;;;;;;;;OAcG;gBACS,EACV,SAAS,EACT,KAAK,EACL,iBAAiB,EACjB,gBAAgB,EAChB,QAAoB,GACrB,EAAE;QACD,SAAS,EAAE,iCAAiC,CAAC;QAC7C,KAAK,CAAC,EAAE,OAAO,CAAC,6BAA6B,CAAC,CAAC;QAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,iBAAiB,EAAE,MAAM,OAAO,CAAC;QACjC,gBAAgB,EAAE,MAAM,MAAM,CAAC;KAChC;IAyCD;;;;;;;;OAQG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;CAwGpC"}
1
+ {"version":3,"file":"ProfileMetricsController.d.mts","sourceRoot":"","sources":["../src/ProfileMetricsController.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EACV,mCAAmC,EACnC,qCAAqC,EACrC,gCAAgC,EACjC,sCAAsC;AACvC,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAE3B,kCAAkC;AACnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,4BAA4B,EAC7B,qCAAqC;AAEtC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAErD,OAAO,EAAE,8CAA8C,EAAE,yCAAyC;AAIlG,OAAO,KAAK,EAAE,kCAAkC,EAAE,oBAAU;AAC5D,OAAO,KAAK,EAAE,qCAAqC,EAAE,oBAAU;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,oCAAgC;AAEjE;;;;GAIG;AACH,eAAO,MAAM,cAAc,6BAA6B,CAAC;AAEzD;;GAEG;AACH,eAAO,MAAM,8BAA8B,QAG1C,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C;;;OAGG;IACH,uBAAuB,EAAE,OAAO,CAAC;IACjC;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAC/C;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC,CAAC;AA0BF;;;;;;;GAOG;AACH,wBAAgB,uCAAuC,IAAI,6BAA6B,CAKvF;AAID;;GAEG;AACH,MAAM,MAAM,sCAAsC,GAAG,wBAAwB,CAC3E,OAAO,cAAc,EACrB,6BAA6B,CAC9B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,+BAA+B,GACvC,sCAAsC,GACtC,qCAAqC,CAAC;AAE1C;;GAEG;AACH,KAAK,cAAc,GACf,kCAAkC,GAClC,gCAAgC,CAAC;AAErC;;GAEG;AACH,MAAM,MAAM,wCAAwC,GAClD,0BAA0B,CACxB,OAAO,cAAc,EACrB,6BAA6B,CAC9B,CAAC;AAEJ;;GAEG;AACH,MAAM,MAAM,8BAA8B,GACxC,wCAAwC,CAAC;AAE3C;;;GAGG;AACH,KAAK,aAAa,GACd,4BAA4B,GAC5B,0BAA0B,GAC1B,mCAAmC,GACnC,qCAAqC,GACrC,8CAA8C,CAAC;AAEnD;;;GAGG;AACH,MAAM,MAAM,iCAAiC,GAAG,SAAS,CACvD,OAAO,cAAc,EACrB,+BAA+B,GAAG,cAAc,EAChD,8BAA8B,GAAG,aAAa,CAC/C,CAAC;;;;;;;;;;;;;;;;AAEF,qBAAa,wBAAyB,SAAQ,8BAC5C,OAAO,cAAc,EACrB,6BAA6B,EAC7B,iCAAiC,CAClC;;IASC;;;;;;;;;;;;;;;;OAgBG;gBACS,EACV,SAAS,EACT,KAAK,EACL,iBAAiB,EACjB,gBAAgB,EAChB,QAAoB,EACpB,oBAAqD,GACtD,EAAE;QACD,SAAS,EAAE,iCAAiC,CAAC;QAC7C,KAAK,CAAC,EAAE,OAAO,CAAC,6BAA6B,CAAC,CAAC;QAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,iBAAiB,EAAE,MAAM,OAAO,CAAC;QACjC,gBAAgB,EAAE,MAAM,MAAM,CAAC;QAC/B,oBAAoB,CAAC,EAAE,MAAM,CAAC;KAC/B;IAmDD;;;OAGG;IACH,gBAAgB,IAAI,IAAI;IAMxB;;;;;;;;OAQG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;CAkIpC"}
@@ -9,8 +9,9 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _ProfileMetricsController_instances, _ProfileMetricsController_mutex, _ProfileMetricsController_assertUserOptedIn, _ProfileMetricsController_getMetaMetricsId, _ProfileMetricsController_queueFirstSyncIfNeeded, _ProfileMetricsController_addAccountToQueue, _ProfileMetricsController_removeAccountFromQueue;
12
+ var _ProfileMetricsController_instances, _ProfileMetricsController_mutex, _ProfileMetricsController_assertUserOptedIn, _ProfileMetricsController_getMetaMetricsId, _ProfileMetricsController_initialDelayDuration, _ProfileMetricsController_queueFirstSyncIfNeeded, _ProfileMetricsController_setInitialDelayEndTimestampIfNull, _ProfileMetricsController_isInitialDelayComplete, _ProfileMetricsController_addAccountToQueue, _ProfileMetricsController_removeAccountFromQueue;
13
13
  import { StaticIntervalPollingController } from "@metamask/polling-controller";
14
+ import { Duration, inMilliseconds } from "@metamask/utils";
14
15
  import { Mutex } from "async-mutex";
15
16
  /**
16
17
  * The name of the {@link ProfileMetricsController}, used to namespace the
@@ -18,6 +19,10 @@ import { Mutex } from "async-mutex";
18
19
  * when composed with other controllers.
19
20
  */
20
21
  export const controllerName = 'ProfileMetricsController';
22
+ /**
23
+ * The default delay duration before data is sent for the first time.
24
+ */
25
+ export const DEFAULT_INITIAL_DELAY_DURATION = inMilliseconds(10, Duration.Minute);
21
26
  /**
22
27
  * The metadata for each property in {@link ProfileMetricsControllerState}.
23
28
  */
@@ -34,6 +39,12 @@ const profileMetricsControllerMetadata = {
34
39
  includeInStateLogs: true,
35
40
  usedInUi: false,
36
41
  },
42
+ initialDelayEndTimestamp: {
43
+ persist: true,
44
+ includeInDebugSnapshot: true,
45
+ includeInStateLogs: true,
46
+ usedInUi: false,
47
+ },
37
48
  };
38
49
  /**
39
50
  * Constructs the default {@link ProfileMetricsController} state. This allows
@@ -49,7 +60,7 @@ export function getDefaultProfileMetricsControllerState() {
49
60
  syncQueue: {},
50
61
  };
51
62
  }
52
- const MESSENGER_EXPOSED_METHODS = [];
63
+ const MESSENGER_EXPOSED_METHODS = ['skipInitialDelay'];
53
64
  export class ProfileMetricsController extends StaticIntervalPollingController() {
54
65
  /**
55
66
  * Constructs a new {@link ProfileMetricsController}.
@@ -65,8 +76,10 @@ export class ProfileMetricsController extends StaticIntervalPollingController()
65
76
  * of the user.
66
77
  * @param args.interval - The interval, in milliseconds, at which the controller will
67
78
  * attempt to send user profile data. Defaults to 10 seconds.
79
+ * @param args.initialDelayDuration - The delay duration before data is sent
80
+ * for the first time, in milliseconds. Defaults to 10 minutes.
68
81
  */
69
- constructor({ messenger, state, assertUserOptedIn, getMetaMetricsId, interval = 10 * 1000, }) {
82
+ constructor({ messenger, state, assertUserOptedIn, getMetaMetricsId, interval = 10 * 1000, initialDelayDuration = DEFAULT_INITIAL_DELAY_DURATION, }) {
70
83
  super({
71
84
  messenger,
72
85
  metadata: profileMetricsControllerMetadata,
@@ -80,18 +93,22 @@ export class ProfileMetricsController extends StaticIntervalPollingController()
80
93
  _ProfileMetricsController_mutex.set(this, new Mutex());
81
94
  _ProfileMetricsController_assertUserOptedIn.set(this, void 0);
82
95
  _ProfileMetricsController_getMetaMetricsId.set(this, void 0);
96
+ _ProfileMetricsController_initialDelayDuration.set(this, void 0);
83
97
  __classPrivateFieldSet(this, _ProfileMetricsController_assertUserOptedIn, assertUserOptedIn, "f");
84
98
  __classPrivateFieldSet(this, _ProfileMetricsController_getMetaMetricsId, getMetaMetricsId, "f");
99
+ __classPrivateFieldSet(this, _ProfileMetricsController_initialDelayDuration, initialDelayDuration, "f");
85
100
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
86
101
  this.messenger.subscribe('KeyringController:unlock', () => {
87
- __classPrivateFieldGet(this, _ProfileMetricsController_instances, "m", _ProfileMetricsController_queueFirstSyncIfNeeded).call(this).catch(console.error);
88
102
  if (__classPrivateFieldGet(this, _ProfileMetricsController_assertUserOptedIn, "f").call(this)) {
89
- this.startPolling(null);
103
+ // If the user has already opted in at the start of the session,
104
+ // it must have opted in during onboarding, or during a previous session.
105
+ this.skipInitialDelay();
90
106
  }
107
+ __classPrivateFieldGet(this, _ProfileMetricsController_instances, "m", _ProfileMetricsController_queueFirstSyncIfNeeded).call(this).catch(this.messenger.captureException ?? console.error);
108
+ this.startPolling(null);
91
109
  });
92
- this.messenger.subscribe('KeyringController:lock', () => {
93
- this.stopAllPolling();
94
- });
110
+ this.messenger.subscribe('KeyringController:lock', () => this.stopAllPolling());
111
+ this.messenger.subscribe('TransactionController:transactionSubmitted', () => this.skipInitialDelay());
95
112
  this.messenger.subscribe('AccountsController:accountAdded', (account) => {
96
113
  __classPrivateFieldGet(this, _ProfileMetricsController_instances, "m", _ProfileMetricsController_addAccountToQueue).call(this, account).catch(console.error);
97
114
  });
@@ -100,6 +117,15 @@ export class ProfileMetricsController extends StaticIntervalPollingController()
100
117
  });
101
118
  this.setIntervalLength(interval);
102
119
  }
120
+ /**
121
+ * Skip the initial delay period by setting the end timestamp to the current time.
122
+ * Metrics will be sent on the next poll.
123
+ */
124
+ skipInitialDelay() {
125
+ this.update((state) => {
126
+ state.initialDelayEndTimestamp = Date.now();
127
+ });
128
+ }
103
129
  /**
104
130
  * Execute a single poll to sync user profile data.
105
131
  *
@@ -114,6 +140,10 @@ export class ProfileMetricsController extends StaticIntervalPollingController()
114
140
  if (!__classPrivateFieldGet(this, _ProfileMetricsController_assertUserOptedIn, "f").call(this)) {
115
141
  return;
116
142
  }
143
+ __classPrivateFieldGet(this, _ProfileMetricsController_instances, "m", _ProfileMetricsController_setInitialDelayEndTimestampIfNull).call(this);
144
+ if (!__classPrivateFieldGet(this, _ProfileMetricsController_instances, "m", _ProfileMetricsController_isInitialDelayComplete).call(this)) {
145
+ return;
146
+ }
117
147
  for (const [entropySourceId, accounts] of Object.entries(this.state.syncQueue)) {
118
148
  try {
119
149
  await this.messenger.call('ProfileMetricsService:submitMetrics', {
@@ -133,7 +163,7 @@ export class ProfileMetricsController extends StaticIntervalPollingController()
133
163
  });
134
164
  }
135
165
  }
136
- _ProfileMetricsController_mutex = new WeakMap(), _ProfileMetricsController_assertUserOptedIn = new WeakMap(), _ProfileMetricsController_getMetaMetricsId = new WeakMap(), _ProfileMetricsController_instances = new WeakSet(), _ProfileMetricsController_queueFirstSyncIfNeeded =
166
+ _ProfileMetricsController_mutex = new WeakMap(), _ProfileMetricsController_assertUserOptedIn = new WeakMap(), _ProfileMetricsController_getMetaMetricsId = new WeakMap(), _ProfileMetricsController_initialDelayDuration = new WeakMap(), _ProfileMetricsController_instances = new WeakSet(), _ProfileMetricsController_queueFirstSyncIfNeeded =
137
167
  /**
138
168
  * Add existing accounts to the sync queue if it has not been done yet.
139
169
  *
@@ -157,6 +187,13 @@ async function _ProfileMetricsController_queueFirstSyncIfNeeded() {
157
187
  state.initialEnqueueCompleted = true;
158
188
  });
159
189
  });
190
+ }, _ProfileMetricsController_setInitialDelayEndTimestampIfNull = function _ProfileMetricsController_setInitialDelayEndTimestampIfNull() {
191
+ this.update((state) => {
192
+ state.initialDelayEndTimestamp ?? (state.initialDelayEndTimestamp = Date.now() + __classPrivateFieldGet(this, _ProfileMetricsController_initialDelayDuration, "f"));
193
+ });
194
+ }, _ProfileMetricsController_isInitialDelayComplete = function _ProfileMetricsController_isInitialDelayComplete() {
195
+ return (this.state.initialDelayEndTimestamp !== undefined &&
196
+ Date.now() >= this.state.initialDelayEndTimestamp);
160
197
  }, _ProfileMetricsController_addAccountToQueue =
161
198
  /**
162
199
  * Queue the given account to be synced at the next poll.
@@ -1 +1 @@
1
- {"version":3,"file":"ProfileMetricsController.mjs","sourceRoot":"","sources":["../src/ProfileMetricsController.ts"],"names":[],"mappings":";;;;;;;;;;;;AAgBA,OAAO,EAAE,+BAA+B,EAAE,qCAAqC;AAC/E,OAAO,EAAE,KAAK,EAAE,oBAAoB;AAKpC;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,0BAA0B,CAAC;AAoBzD;;GAEG;AACH,MAAM,gCAAgC,GAAG;IACvC,uBAAuB,EAAE;QACvB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,IAAI;QAC5B,kBAAkB,EAAE,IAAI;QACxB,QAAQ,EAAE,KAAK;KAChB;IACD,SAAS,EAAE;QACT,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,KAAK;QAC7B,kBAAkB,EAAE,IAAI;QACxB,QAAQ,EAAE,KAAK;KAChB;CACqD,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,UAAU,uCAAuC;IACrD,OAAO;QACL,uBAAuB,EAAE,KAAK;QAC9B,SAAS,EAAE,EAAE;KACd,CAAC;AACJ,CAAC;AAED,MAAM,yBAAyB,GAAG,EAAW,CAAC;AA2D9C,MAAM,OAAO,wBAAyB,SAAQ,+BAA+B,EAI5E;IAOC;;;;;;;;;;;;;;OAcG;IACH,YAAY,EACV,SAAS,EACT,KAAK,EACL,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,GAAG,EAAE,GAAG,IAAI,GAOrB;QACC,KAAK,CAAC;YACJ,SAAS;YACT,QAAQ,EAAE,gCAAgC;YAC1C,IAAI,EAAE,cAAc;YACpB,KAAK,EAAE;gBACL,GAAG,uCAAuC,EAAE;gBAC5C,GAAG,KAAK;aACT;SACF,CAAC,CAAC;;QA1CI,0CAAS,IAAI,KAAK,EAAE,EAAC;QAErB,8DAAkC;QAElC,6DAAgC;QAwCvC,uBAAA,IAAI,+CAAsB,iBAAiB,MAAA,CAAC;QAC5C,uBAAA,IAAI,8CAAqB,gBAAgB,MAAA,CAAC;QAE1C,IAAI,CAAC,SAAS,CAAC,4BAA4B,CACzC,IAAI,EACJ,yBAAyB,CAC1B,CAAC;QAEF,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,0BAA0B,EAAE,GAAG,EAAE;YACxD,uBAAA,IAAI,6FAAwB,MAA5B,IAAI,CAA0B,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACpD,IAAI,uBAAA,IAAI,mDAAmB,MAAvB,IAAI,CAAqB,EAAE,CAAC;gBAC9B,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,wBAAwB,EAAE,GAAG,EAAE;YACtD,IAAI,CAAC,cAAc,EAAE,CAAC;QACxB,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,iCAAiC,EAAE,CAAC,OAAO,EAAE,EAAE;YACtE,uBAAA,IAAI,wFAAmB,MAAvB,IAAI,EAAoB,OAAO,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,mCAAmC,EAAE,CAAC,OAAO,EAAE,EAAE;YACxE,uBAAA,IAAI,6FAAwB,MAA5B,IAAI,EAAyB,OAAO,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,YAAY;QAChB,MAAM,uBAAA,IAAI,uCAAO,CAAC,YAAY,CAAC,KAAK,IAAI,EAAE;YACxC,IAAI,CAAC,uBAAA,IAAI,mDAAmB,MAAvB,IAAI,CAAqB,EAAE,CAAC;gBAC/B,OAAO;YACT,CAAC;YACD,KAAK,MAAM,CAAC,eAAe,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CACtD,IAAI,CAAC,KAAK,CAAC,SAAS,CACrB,EAAE,CAAC;gBACF,IAAI,CAAC;oBACH,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,qCAAqC,EAAE;wBAC/D,aAAa,EAAE,uBAAA,IAAI,kDAAkB,MAAtB,IAAI,CAAoB;wBACvC,eAAe,EACb,eAAe,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe;wBACrD,QAAQ;qBACT,CAAC,CAAC;oBACH,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;wBACpB,OAAO,KAAK,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;oBAC1C,CAAC,CAAC,CAAC;gBACL,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,kEAAkE;oBAClE,OAAO,CAAC,KAAK,CACX,0DAA0D,eAAe,GAAG,EAC5E,KAAK,CACN,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CA6EF;;AA3EC;;;;;GAKG;AACH,KAAK;IACH,MAAM,uBAAA,IAAI,uCAAO,CAAC,YAAY,CAAC,KAAK,IAAI,EAAE;QACxC,IAAI,IAAI,CAAC,KAAK,CAAC,uBAAuB,EAAE,CAAC;YACvC,OAAO;QACT,CAAC;QACD,MAAM,kBAAkB,GAAG,8BAA8B,CACvD,MAAM,CAAC,MAAM,CACX,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC,gBAAgB;aAChE,QAAQ,CACZ,CACF,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,EAAE,CAAC;gBAClD,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC1B,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;gBAC5B,CAAC;gBACD,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC;YACxD,CAAC;YACD,KAAK,CAAC,uBAAuB,GAAG,IAAI,CAAC;QACvC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,KAAK,sDAAoB,OAAwB;IAC/C,MAAM,uBAAA,IAAI,uCAAO,CAAC,YAAY,CAAC,KAAK,IAAI,EAAE;QACxC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,MAAM,eAAe,GAAG,yBAAyB,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC;YACrE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,eAAe,CAAC,EAAE,CAAC;gBACtC,KAAK,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,EAAE,CAAC;YACxC,CAAC;YACD,KAAK,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC;gBACpC,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,KAAK,2DAAyB,OAAe;IAC3C,MAAM,uBAAA,IAAI,uCAAO,CAAC,YAAY,CAAC,KAAK,IAAI,EAAE;QACxC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,KAAK,MAAM,CAAC,eAAe,EAAE,gBAAgB,CAAC,IAAI,MAAM,CAAC,OAAO,CAC9D,KAAK,CAAC,SAAS,CAChB,EAAE,CAAC;gBACF,MAAM,KAAK,GAAG,gBAAgB,CAAC,SAAS,CACtC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,KAAK,OAAO,CACrC,CAAC;gBACF,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;oBACjB,SAAS;gBACX,CAAC;gBACD,gBAAgB,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;gBAClC,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAClC,OAAO,KAAK,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;gBAC1C,CAAC;gBACD,MAAM;YACR,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAGH;;;;;GAKG;AACH,SAAS,yBAAyB,CAAC,OAAwB;IACzD,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,KAAK,UAAU,EAAE,CAAC;QACjD,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;IACpC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,SAAS,8BAA8B,CACrC,QAA2B;IAE3B,OAAO,QAAQ,CAAC,MAAM,CACpB,CAAC,MAA2C,EAAE,OAAO,EAAE,EAAE;QACvD,MAAM,eAAe,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;QAC3D,MAAM,GAAG,GAAG,eAAe,IAAI,MAAM,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YACjB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;QACnB,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACvE,OAAO,MAAM,CAAC;IAChB,CAAC,EACD,EAAE,CACH,CAAC;AACJ,CAAC","sourcesContent":["import type {\n AccountsControllerAccountAddedEvent,\n AccountsControllerAccountRemovedEvent,\n AccountsControllerGetStateAction,\n} from '@metamask/accounts-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport type {\n KeyringControllerLockEvent,\n KeyringControllerUnlockEvent,\n} from '@metamask/keyring-controller';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport { Mutex } from 'async-mutex';\n\nimport type { ProfileMetricsServiceMethodActions } from '.';\nimport type { AccountWithScopes } from './ProfileMetricsService';\n\n/**\n * The name of the {@link ProfileMetricsController}, used to namespace the\n * controller's actions and events and to namespace the controller's state data\n * when composed with other controllers.\n */\nexport const controllerName = 'ProfileMetricsController';\n\n/**\n * Describes the shape of the state object for {@link ProfileMetricsController}.\n */\nexport type ProfileMetricsControllerState = {\n /**\n * Whether existing accounts have been added\n * to the queue.\n */\n initialEnqueueCompleted: boolean;\n /**\n * The queue of accounts to be synced.\n * Each key is an entropy source ID, and each value is an array of account\n * addresses associated with that entropy source. Accounts with no entropy\n * source ID are grouped under the key \"null\".\n */\n syncQueue: Record<string, AccountWithScopes[]>;\n};\n\n/**\n * The metadata for each property in {@link ProfileMetricsControllerState}.\n */\nconst profileMetricsControllerMetadata = {\n initialEnqueueCompleted: {\n persist: true,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: false,\n },\n syncQueue: {\n persist: true,\n includeInDebugSnapshot: false,\n includeInStateLogs: true,\n usedInUi: false,\n },\n} satisfies StateMetadata<ProfileMetricsControllerState>;\n\n/**\n * Constructs the default {@link ProfileMetricsController} state. This allows\n * consumers to provide a partial state object when initializing the controller\n * and also helps in constructing complete state objects for this controller in\n * tests.\n *\n * @returns The default {@link ProfileMetricsController} state.\n */\nexport function getDefaultProfileMetricsControllerState(): ProfileMetricsControllerState {\n return {\n initialEnqueueCompleted: false,\n syncQueue: {},\n };\n}\n\nconst MESSENGER_EXPOSED_METHODS = [] as const;\n\n/**\n * Retrieves the state of the {@link ProfileMetricsController}.\n */\nexport type ProfileMetricsControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n ProfileMetricsControllerState\n>;\n\n/**\n * Actions that {@link ProfileMetricsControllerMessenger} exposes to other consumers.\n */\nexport type ProfileMetricsControllerActions =\n | ProfileMetricsControllerGetStateAction\n | ProfileMetricsServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link ProfileMetricsControllerMessenger} calls.\n */\ntype AllowedActions =\n | ProfileMetricsServiceMethodActions\n | AccountsControllerGetStateAction;\n\n/**\n * Published when the state of {@link ProfileMetricsController} changes.\n */\nexport type ProfileMetricsControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n ProfileMetricsControllerState\n >;\n\n/**\n * Events that {@link ProfileMetricsControllerMessenger} exposes to other consumers.\n */\nexport type ProfileMetricsControllerEvents =\n ProfileMetricsControllerStateChangeEvent;\n\n/**\n * Events from other messengers that {@link ProfileMetricsControllerMessenger} subscribes\n * to.\n */\ntype AllowedEvents =\n | KeyringControllerUnlockEvent\n | KeyringControllerLockEvent\n | AccountsControllerAccountAddedEvent\n | AccountsControllerAccountRemovedEvent;\n\n/**\n * The messenger restricted to actions and events accessed by\n * {@link ProfileMetricsController}.\n */\nexport type ProfileMetricsControllerMessenger = Messenger<\n typeof controllerName,\n ProfileMetricsControllerActions | AllowedActions,\n ProfileMetricsControllerEvents | AllowedEvents\n>;\n\nexport class ProfileMetricsController extends StaticIntervalPollingController()<\n typeof controllerName,\n ProfileMetricsControllerState,\n ProfileMetricsControllerMessenger\n> {\n readonly #mutex = new Mutex();\n\n readonly #assertUserOptedIn: () => boolean;\n\n readonly #getMetaMetricsId: () => string;\n\n /**\n * Constructs a new {@link ProfileMetricsController}.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this controller.\n * @param args.state - The desired state with which to initialize this\n * controller. Missing properties will be filled in with defaults.\n * @param args.assertUserOptedIn - A function that asserts whether the user has\n * opted in to user profile features. If the user has not opted in, sync\n * operations will be no-ops.\n * @param args.getMetaMetricsId - A function that returns the MetaMetrics ID\n * of the user.\n * @param args.interval - The interval, in milliseconds, at which the controller will\n * attempt to send user profile data. Defaults to 10 seconds.\n */\n constructor({\n messenger,\n state,\n assertUserOptedIn,\n getMetaMetricsId,\n interval = 10 * 1000,\n }: {\n messenger: ProfileMetricsControllerMessenger;\n state?: Partial<ProfileMetricsControllerState>;\n interval?: number;\n assertUserOptedIn: () => boolean;\n getMetaMetricsId: () => string;\n }) {\n super({\n messenger,\n metadata: profileMetricsControllerMetadata,\n name: controllerName,\n state: {\n ...getDefaultProfileMetricsControllerState(),\n ...state,\n },\n });\n\n this.#assertUserOptedIn = assertUserOptedIn;\n this.#getMetaMetricsId = getMetaMetricsId;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.messenger.subscribe('KeyringController:unlock', () => {\n this.#queueFirstSyncIfNeeded().catch(console.error);\n if (this.#assertUserOptedIn()) {\n this.startPolling(null);\n }\n });\n\n this.messenger.subscribe('KeyringController:lock', () => {\n this.stopAllPolling();\n });\n\n this.messenger.subscribe('AccountsController:accountAdded', (account) => {\n this.#addAccountToQueue(account).catch(console.error);\n });\n\n this.messenger.subscribe('AccountsController:accountRemoved', (account) => {\n this.#removeAccountFromQueue(account).catch(console.error);\n });\n\n this.setIntervalLength(interval);\n }\n\n /**\n * Execute a single poll to sync user profile data.\n *\n * The queued accounts are sent to the ProfileMetricsService, and the sync\n * queue is cleared. This operation is mutexed to prevent concurrent\n * executions.\n *\n * @returns A promise that resolves when the poll is complete.\n */\n async _executePoll(): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n if (!this.#assertUserOptedIn()) {\n return;\n }\n for (const [entropySourceId, accounts] of Object.entries(\n this.state.syncQueue,\n )) {\n try {\n await this.messenger.call('ProfileMetricsService:submitMetrics', {\n metametricsId: this.#getMetaMetricsId(),\n entropySourceId:\n entropySourceId === 'null' ? null : entropySourceId,\n accounts,\n });\n this.update((state) => {\n delete state.syncQueue[entropySourceId];\n });\n } catch (error) {\n // We want to log the error but continue processing other batches.\n console.error(\n `Failed to submit profile metrics for entropy source ID ${entropySourceId}:`,\n error,\n );\n }\n }\n });\n }\n\n /**\n * Add existing accounts to the sync queue if it has not been done yet.\n *\n * This method ensures that the first sync is only executed once,\n * and only if the user has opted in to user profile features.\n */\n async #queueFirstSyncIfNeeded(): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n if (this.state.initialEnqueueCompleted) {\n return;\n }\n const newGroupedAccounts = groupAccountsByEntropySourceId(\n Object.values(\n this.messenger.call('AccountsController:getState').internalAccounts\n .accounts,\n ),\n );\n this.update((state) => {\n for (const key of Object.keys(newGroupedAccounts)) {\n if (!state.syncQueue[key]) {\n state.syncQueue[key] = [];\n }\n state.syncQueue[key].push(...newGroupedAccounts[key]);\n }\n state.initialEnqueueCompleted = true;\n });\n });\n }\n\n /**\n * Queue the given account to be synced at the next poll.\n *\n * @param account - The account to sync.\n */\n async #addAccountToQueue(account: InternalAccount): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n this.update((state) => {\n const entropySourceId = getAccountEntropySourceId(account) ?? 'null';\n if (!state.syncQueue[entropySourceId]) {\n state.syncQueue[entropySourceId] = [];\n }\n state.syncQueue[entropySourceId].push({\n address: account.address,\n scopes: account.scopes,\n });\n });\n });\n }\n\n /**\n * Remove the given account from the sync queue.\n *\n * @param account - The account address to remove.\n */\n async #removeAccountFromQueue(account: string): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n this.update((state) => {\n for (const [entropySourceId, groupedAddresses] of Object.entries(\n state.syncQueue,\n )) {\n const index = groupedAddresses.findIndex(\n ({ address }) => address === account,\n );\n if (index === -1) {\n continue;\n }\n groupedAddresses.splice(index, 1);\n if (groupedAddresses.length === 0) {\n delete state.syncQueue[entropySourceId];\n }\n break;\n }\n });\n });\n }\n}\n\n/**\n * Retrieves the entropy source ID from the given account, if it exists.\n *\n * @param account - The account from which to retrieve the entropy source ID.\n * @returns The entropy source ID, or null if it does not exist.\n */\nfunction getAccountEntropySourceId(account: InternalAccount): string | null {\n if (account.options.entropy?.type === 'mnemonic') {\n return account.options.entropy.id;\n }\n return null;\n}\n\n/**\n * Groups accounts by their entropy source ID.\n *\n * @param accounts - The accounts to group.\n * @returns An object where each key is an entropy source ID and each value is\n * an array of account addresses associated with that entropy source ID.\n */\nfunction groupAccountsByEntropySourceId(\n accounts: InternalAccount[],\n): Record<string, AccountWithScopes[]> {\n return accounts.reduce(\n (result: Record<string, AccountWithScopes[]>, account) => {\n const entropySourceId = getAccountEntropySourceId(account);\n const key = entropySourceId ?? 'null';\n if (!result[key]) {\n result[key] = [];\n }\n result[key].push({ address: account.address, scopes: account.scopes });\n return result;\n },\n {},\n );\n}\n"]}
1
+ {"version":3,"file":"ProfileMetricsController.mjs","sourceRoot":"","sources":["../src/ProfileMetricsController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountsControllerAccountAddedEvent,\n AccountsControllerAccountRemovedEvent,\n AccountsControllerGetStateAction,\n} from '@metamask/accounts-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport type {\n KeyringControllerLockEvent,\n KeyringControllerUnlockEvent,\n} from '@metamask/keyring-controller';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport { TransactionControllerTransactionSubmittedEvent } from '@metamask/transaction-controller';\nimport { Duration, inMilliseconds } from '@metamask/utils';\nimport { Mutex } from 'async-mutex';\n\nimport type { ProfileMetricsServiceMethodActions } from '.';\nimport type { ProfileMetricsControllerMethodActions } from '.';\nimport type { AccountWithScopes } from './ProfileMetricsService';\n\n/**\n * The name of the {@link ProfileMetricsController}, used to namespace the\n * controller's actions and events and to namespace the controller's state data\n * when composed with other controllers.\n */\nexport const controllerName = 'ProfileMetricsController';\n\n/**\n * The default delay duration before data is sent for the first time.\n */\nexport const DEFAULT_INITIAL_DELAY_DURATION = inMilliseconds(\n 10,\n Duration.Minute,\n);\n\n/**\n * Describes the shape of the state object for {@link ProfileMetricsController}.\n */\nexport type ProfileMetricsControllerState = {\n /**\n * Whether existing accounts have been added\n * to the queue.\n */\n initialEnqueueCompleted: boolean;\n /**\n * The queue of accounts to be synced.\n * Each key is an entropy source ID, and each value is an array of account\n * addresses associated with that entropy source. Accounts with no entropy\n * source ID are grouped under the key \"null\".\n */\n syncQueue: Record<string, AccountWithScopes[]>;\n /**\n * The timestamp when the first data sending can be attempted.\n */\n initialDelayEndTimestamp?: number;\n};\n\n/**\n * The metadata for each property in {@link ProfileMetricsControllerState}.\n */\nconst profileMetricsControllerMetadata = {\n initialEnqueueCompleted: {\n persist: true,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: false,\n },\n syncQueue: {\n persist: true,\n includeInDebugSnapshot: false,\n includeInStateLogs: true,\n usedInUi: false,\n },\n initialDelayEndTimestamp: {\n persist: true,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: false,\n },\n} satisfies StateMetadata<ProfileMetricsControllerState>;\n\n/**\n * Constructs the default {@link ProfileMetricsController} state. This allows\n * consumers to provide a partial state object when initializing the controller\n * and also helps in constructing complete state objects for this controller in\n * tests.\n *\n * @returns The default {@link ProfileMetricsController} state.\n */\nexport function getDefaultProfileMetricsControllerState(): ProfileMetricsControllerState {\n return {\n initialEnqueueCompleted: false,\n syncQueue: {},\n };\n}\n\nconst MESSENGER_EXPOSED_METHODS = ['skipInitialDelay'] as const;\n\n/**\n * Retrieves the state of the {@link ProfileMetricsController}.\n */\nexport type ProfileMetricsControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n ProfileMetricsControllerState\n>;\n\n/**\n * Actions that {@link ProfileMetricsControllerMessenger} exposes to other consumers.\n */\nexport type ProfileMetricsControllerActions =\n | ProfileMetricsControllerGetStateAction\n | ProfileMetricsControllerMethodActions;\n\n/**\n * Actions from other messengers that {@link ProfileMetricsControllerMessenger} calls.\n */\ntype AllowedActions =\n | ProfileMetricsServiceMethodActions\n | AccountsControllerGetStateAction;\n\n/**\n * Published when the state of {@link ProfileMetricsController} changes.\n */\nexport type ProfileMetricsControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n ProfileMetricsControllerState\n >;\n\n/**\n * Events that {@link ProfileMetricsControllerMessenger} exposes to other consumers.\n */\nexport type ProfileMetricsControllerEvents =\n ProfileMetricsControllerStateChangeEvent;\n\n/**\n * Events from other messengers that {@link ProfileMetricsControllerMessenger} subscribes\n * to.\n */\ntype AllowedEvents =\n | KeyringControllerUnlockEvent\n | KeyringControllerLockEvent\n | AccountsControllerAccountAddedEvent\n | AccountsControllerAccountRemovedEvent\n | TransactionControllerTransactionSubmittedEvent;\n\n/**\n * The messenger restricted to actions and events accessed by\n * {@link ProfileMetricsController}.\n */\nexport type ProfileMetricsControllerMessenger = Messenger<\n typeof controllerName,\n ProfileMetricsControllerActions | AllowedActions,\n ProfileMetricsControllerEvents | AllowedEvents\n>;\n\nexport class ProfileMetricsController extends StaticIntervalPollingController()<\n typeof controllerName,\n ProfileMetricsControllerState,\n ProfileMetricsControllerMessenger\n> {\n readonly #mutex = new Mutex();\n\n readonly #assertUserOptedIn: () => boolean;\n\n readonly #getMetaMetricsId: () => string;\n\n readonly #initialDelayDuration: number;\n\n /**\n * Constructs a new {@link ProfileMetricsController}.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this controller.\n * @param args.state - The desired state with which to initialize this\n * controller. Missing properties will be filled in with defaults.\n * @param args.assertUserOptedIn - A function that asserts whether the user has\n * opted in to user profile features. If the user has not opted in, sync\n * operations will be no-ops.\n * @param args.getMetaMetricsId - A function that returns the MetaMetrics ID\n * of the user.\n * @param args.interval - The interval, in milliseconds, at which the controller will\n * attempt to send user profile data. Defaults to 10 seconds.\n * @param args.initialDelayDuration - The delay duration before data is sent\n * for the first time, in milliseconds. Defaults to 10 minutes.\n */\n constructor({\n messenger,\n state,\n assertUserOptedIn,\n getMetaMetricsId,\n interval = 10 * 1000,\n initialDelayDuration = DEFAULT_INITIAL_DELAY_DURATION,\n }: {\n messenger: ProfileMetricsControllerMessenger;\n state?: Partial<ProfileMetricsControllerState>;\n interval?: number;\n assertUserOptedIn: () => boolean;\n getMetaMetricsId: () => string;\n initialDelayDuration?: number;\n }) {\n super({\n messenger,\n metadata: profileMetricsControllerMetadata,\n name: controllerName,\n state: {\n ...getDefaultProfileMetricsControllerState(),\n ...state,\n },\n });\n\n this.#assertUserOptedIn = assertUserOptedIn;\n this.#getMetaMetricsId = getMetaMetricsId;\n this.#initialDelayDuration = initialDelayDuration;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.messenger.subscribe('KeyringController:unlock', () => {\n if (this.#assertUserOptedIn()) {\n // If the user has already opted in at the start of the session,\n // it must have opted in during onboarding, or during a previous session.\n this.skipInitialDelay();\n }\n this.#queueFirstSyncIfNeeded().catch(\n this.messenger.captureException ?? console.error,\n );\n this.startPolling(null);\n });\n\n this.messenger.subscribe('KeyringController:lock', () =>\n this.stopAllPolling(),\n );\n\n this.messenger.subscribe('TransactionController:transactionSubmitted', () =>\n this.skipInitialDelay(),\n );\n\n this.messenger.subscribe('AccountsController:accountAdded', (account) => {\n this.#addAccountToQueue(account).catch(console.error);\n });\n\n this.messenger.subscribe('AccountsController:accountRemoved', (account) => {\n this.#removeAccountFromQueue(account).catch(console.error);\n });\n\n this.setIntervalLength(interval);\n }\n\n /**\n * Skip the initial delay period by setting the end timestamp to the current time.\n * Metrics will be sent on the next poll.\n */\n skipInitialDelay(): void {\n this.update((state) => {\n state.initialDelayEndTimestamp = Date.now();\n });\n }\n\n /**\n * Execute a single poll to sync user profile data.\n *\n * The queued accounts are sent to the ProfileMetricsService, and the sync\n * queue is cleared. This operation is mutexed to prevent concurrent\n * executions.\n *\n * @returns A promise that resolves when the poll is complete.\n */\n async _executePoll(): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n if (!this.#assertUserOptedIn()) {\n return;\n }\n this.#setInitialDelayEndTimestampIfNull();\n if (!this.#isInitialDelayComplete()) {\n return;\n }\n for (const [entropySourceId, accounts] of Object.entries(\n this.state.syncQueue,\n )) {\n try {\n await this.messenger.call('ProfileMetricsService:submitMetrics', {\n metametricsId: this.#getMetaMetricsId(),\n entropySourceId:\n entropySourceId === 'null' ? null : entropySourceId,\n accounts,\n });\n this.update((state) => {\n delete state.syncQueue[entropySourceId];\n });\n } catch (error) {\n // We want to log the error but continue processing other batches.\n console.error(\n `Failed to submit profile metrics for entropy source ID ${entropySourceId}:`,\n error,\n );\n }\n }\n });\n }\n\n /**\n * Add existing accounts to the sync queue if it has not been done yet.\n *\n * This method ensures that the first sync is only executed once,\n * and only if the user has opted in to user profile features.\n */\n async #queueFirstSyncIfNeeded(): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n if (this.state.initialEnqueueCompleted) {\n return;\n }\n const newGroupedAccounts = groupAccountsByEntropySourceId(\n Object.values(\n this.messenger.call('AccountsController:getState').internalAccounts\n .accounts,\n ),\n );\n this.update((state) => {\n for (const key of Object.keys(newGroupedAccounts)) {\n if (!state.syncQueue[key]) {\n state.syncQueue[key] = [];\n }\n state.syncQueue[key].push(...newGroupedAccounts[key]);\n }\n state.initialEnqueueCompleted = true;\n });\n });\n }\n\n /**\n * Set the initial delay end timestamp if it is not already set.\n */\n #setInitialDelayEndTimestampIfNull(): void {\n this.update((state) => {\n state.initialDelayEndTimestamp ??=\n Date.now() + this.#initialDelayDuration;\n });\n }\n\n /**\n * Check if the initial delay end timestamp is in the past.\n *\n * @returns True if the initial delay period has completed, false otherwise.\n */\n #isInitialDelayComplete(): boolean {\n return (\n this.state.initialDelayEndTimestamp !== undefined &&\n Date.now() >= this.state.initialDelayEndTimestamp\n );\n }\n\n /**\n * Queue the given account to be synced at the next poll.\n *\n * @param account - The account to sync.\n */\n async #addAccountToQueue(account: InternalAccount): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n this.update((state) => {\n const entropySourceId = getAccountEntropySourceId(account) ?? 'null';\n if (!state.syncQueue[entropySourceId]) {\n state.syncQueue[entropySourceId] = [];\n }\n state.syncQueue[entropySourceId].push({\n address: account.address,\n scopes: account.scopes,\n });\n });\n });\n }\n\n /**\n * Remove the given account from the sync queue.\n *\n * @param account - The account address to remove.\n */\n async #removeAccountFromQueue(account: string): Promise<void> {\n await this.#mutex.runExclusive(async () => {\n this.update((state) => {\n for (const [entropySourceId, groupedAddresses] of Object.entries(\n state.syncQueue,\n )) {\n const index = groupedAddresses.findIndex(\n ({ address }) => address === account,\n );\n if (index === -1) {\n continue;\n }\n groupedAddresses.splice(index, 1);\n if (groupedAddresses.length === 0) {\n delete state.syncQueue[entropySourceId];\n }\n break;\n }\n });\n });\n }\n}\n\n/**\n * Retrieves the entropy source ID from the given account, if it exists.\n *\n * @param account - The account from which to retrieve the entropy source ID.\n * @returns The entropy source ID, or null if it does not exist.\n */\nfunction getAccountEntropySourceId(account: InternalAccount): string | null {\n if (account.options.entropy?.type === 'mnemonic') {\n return account.options.entropy.id;\n }\n return null;\n}\n\n/**\n * Groups accounts by their entropy source ID.\n *\n * @param accounts - The accounts to group.\n * @returns An object where each key is an entropy source ID and each value is\n * an array of account addresses associated with that entropy source ID.\n */\nfunction groupAccountsByEntropySourceId(\n accounts: InternalAccount[],\n): Record<string, AccountWithScopes[]> {\n return accounts.reduce(\n (result: Record<string, AccountWithScopes[]>, account) => {\n const entropySourceId = getAccountEntropySourceId(account);\n const key = entropySourceId ?? 'null';\n if (!result[key]) {\n result[key] = [];\n }\n result[key].push({ address: account.address, scopes: account.scopes });\n return result;\n },\n {},\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAQA,2EAGoC;AAFlC,oIAAA,wBAAwB,OAAA;AACxB,mJAAA,uCAAuC,OAAA;AAQzC,qEAA6E;AAApE,8HAAA,qBAAqB,OAAA;AAAE,oHAAA,WAAW,OAAA","sourcesContent":["export type {\n ProfileMetricsControllerActions,\n ProfileMetricsControllerEvents,\n ProfileMetricsControllerGetStateAction,\n ProfileMetricsControllerMessenger,\n ProfileMetricsControllerState,\n ProfileMetricsControllerStateChangeEvent,\n} from './ProfileMetricsController';\nexport {\n ProfileMetricsController,\n getDefaultProfileMetricsControllerState,\n} from './ProfileMetricsController';\nexport type {\n ProfileMetricsServiceActions,\n ProfileMetricsServiceEvents,\n ProfileMetricsServiceMessenger,\n ProfileMetricsSubmitMetricsRequest,\n} from './ProfileMetricsService';\nexport { ProfileMetricsService, serviceName } from './ProfileMetricsService';\nexport type { ProfileMetricsServiceMethodActions } from './ProfileMetricsService-method-action-types';\n"]}
1
+ {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAQA,2EAGoC;AAFlC,oIAAA,wBAAwB,OAAA;AACxB,mJAAA,uCAAuC,OAAA;AAQzC,qEAA6E;AAApE,8HAAA,qBAAqB,OAAA;AAAE,oHAAA,WAAW,OAAA","sourcesContent":["export type {\n ProfileMetricsControllerActions,\n ProfileMetricsControllerEvents,\n ProfileMetricsControllerGetStateAction,\n ProfileMetricsControllerMessenger,\n ProfileMetricsControllerState,\n ProfileMetricsControllerStateChangeEvent,\n} from './ProfileMetricsController';\nexport {\n ProfileMetricsController,\n getDefaultProfileMetricsControllerState,\n} from './ProfileMetricsController';\nexport type {\n ProfileMetricsServiceActions,\n ProfileMetricsServiceEvents,\n ProfileMetricsServiceMessenger,\n ProfileMetricsSubmitMetricsRequest,\n} from './ProfileMetricsService';\nexport { ProfileMetricsService, serviceName } from './ProfileMetricsService';\nexport type { ProfileMetricsServiceMethodActions } from './ProfileMetricsService-method-action-types';\nexport type {\n ProfileMetricsControllerMethodActions,\n ProfileMetricsControllerSkipInitialDelayAction,\n} from './ProfileMetricsController-method-action-types';\n"]}
package/dist/index.d.cts CHANGED
@@ -3,4 +3,5 @@ export { ProfileMetricsController, getDefaultProfileMetricsControllerState, } fr
3
3
  export type { ProfileMetricsServiceActions, ProfileMetricsServiceEvents, ProfileMetricsServiceMessenger, ProfileMetricsSubmitMetricsRequest, } from "./ProfileMetricsService.cjs";
4
4
  export { ProfileMetricsService, serviceName } from "./ProfileMetricsService.cjs";
5
5
  export type { ProfileMetricsServiceMethodActions } from "./ProfileMetricsService-method-action-types.cjs";
6
+ export type { ProfileMetricsControllerMethodActions, ProfileMetricsControllerSkipInitialDelayAction, } from "./ProfileMetricsController-method-action-types.cjs";
6
7
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,+BAA+B,EAC/B,8BAA8B,EAC9B,sCAAsC,EACtC,iCAAiC,EACjC,6BAA6B,EAC7B,wCAAwC,GACzC,uCAAmC;AACpC,OAAO,EACL,wBAAwB,EACxB,uCAAuC,GACxC,uCAAmC;AACpC,YAAY,EACV,4BAA4B,EAC5B,2BAA2B,EAC3B,8BAA8B,EAC9B,kCAAkC,GACnC,oCAAgC;AACjC,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,oCAAgC;AAC7E,YAAY,EAAE,kCAAkC,EAAE,wDAAoD"}
1
+ {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,+BAA+B,EAC/B,8BAA8B,EAC9B,sCAAsC,EACtC,iCAAiC,EACjC,6BAA6B,EAC7B,wCAAwC,GACzC,uCAAmC;AACpC,OAAO,EACL,wBAAwB,EACxB,uCAAuC,GACxC,uCAAmC;AACpC,YAAY,EACV,4BAA4B,EAC5B,2BAA2B,EAC3B,8BAA8B,EAC9B,kCAAkC,GACnC,oCAAgC;AACjC,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,oCAAgC;AAC7E,YAAY,EAAE,kCAAkC,EAAE,wDAAoD;AACtG,YAAY,EACV,qCAAqC,EACrC,8CAA8C,GAC/C,2DAAuD"}
package/dist/index.d.mts CHANGED
@@ -3,4 +3,5 @@ export { ProfileMetricsController, getDefaultProfileMetricsControllerState, } fr
3
3
  export type { ProfileMetricsServiceActions, ProfileMetricsServiceEvents, ProfileMetricsServiceMessenger, ProfileMetricsSubmitMetricsRequest, } from "./ProfileMetricsService.mjs";
4
4
  export { ProfileMetricsService, serviceName } from "./ProfileMetricsService.mjs";
5
5
  export type { ProfileMetricsServiceMethodActions } from "./ProfileMetricsService-method-action-types.mjs";
6
+ export type { ProfileMetricsControllerMethodActions, ProfileMetricsControllerSkipInitialDelayAction, } from "./ProfileMetricsController-method-action-types.mjs";
6
7
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,+BAA+B,EAC/B,8BAA8B,EAC9B,sCAAsC,EACtC,iCAAiC,EACjC,6BAA6B,EAC7B,wCAAwC,GACzC,uCAAmC;AACpC,OAAO,EACL,wBAAwB,EACxB,uCAAuC,GACxC,uCAAmC;AACpC,YAAY,EACV,4BAA4B,EAC5B,2BAA2B,EAC3B,8BAA8B,EAC9B,kCAAkC,GACnC,oCAAgC;AACjC,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,oCAAgC;AAC7E,YAAY,EAAE,kCAAkC,EAAE,wDAAoD"}
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,+BAA+B,EAC/B,8BAA8B,EAC9B,sCAAsC,EACtC,iCAAiC,EACjC,6BAA6B,EAC7B,wCAAwC,GACzC,uCAAmC;AACpC,OAAO,EACL,wBAAwB,EACxB,uCAAuC,GACxC,uCAAmC;AACpC,YAAY,EACV,4BAA4B,EAC5B,2BAA2B,EAC3B,8BAA8B,EAC9B,kCAAkC,GACnC,oCAAgC;AACjC,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,oCAAgC;AAC7E,YAAY,EAAE,kCAAkC,EAAE,wDAAoD;AACtG,YAAY,EACV,qCAAqC,EACrC,8CAA8C,GAC/C,2DAAuD"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,wBAAwB,EACxB,uCAAuC,EACxC,uCAAmC;AAOpC,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,oCAAgC","sourcesContent":["export type {\n ProfileMetricsControllerActions,\n ProfileMetricsControllerEvents,\n ProfileMetricsControllerGetStateAction,\n ProfileMetricsControllerMessenger,\n ProfileMetricsControllerState,\n ProfileMetricsControllerStateChangeEvent,\n} from './ProfileMetricsController';\nexport {\n ProfileMetricsController,\n getDefaultProfileMetricsControllerState,\n} from './ProfileMetricsController';\nexport type {\n ProfileMetricsServiceActions,\n ProfileMetricsServiceEvents,\n ProfileMetricsServiceMessenger,\n ProfileMetricsSubmitMetricsRequest,\n} from './ProfileMetricsService';\nexport { ProfileMetricsService, serviceName } from './ProfileMetricsService';\nexport type { ProfileMetricsServiceMethodActions } from './ProfileMetricsService-method-action-types';\n"]}
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,wBAAwB,EACxB,uCAAuC,EACxC,uCAAmC;AAOpC,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,oCAAgC","sourcesContent":["export type {\n ProfileMetricsControllerActions,\n ProfileMetricsControllerEvents,\n ProfileMetricsControllerGetStateAction,\n ProfileMetricsControllerMessenger,\n ProfileMetricsControllerState,\n ProfileMetricsControllerStateChangeEvent,\n} from './ProfileMetricsController';\nexport {\n ProfileMetricsController,\n getDefaultProfileMetricsControllerState,\n} from './ProfileMetricsController';\nexport type {\n ProfileMetricsServiceActions,\n ProfileMetricsServiceEvents,\n ProfileMetricsServiceMessenger,\n ProfileMetricsSubmitMetricsRequest,\n} from './ProfileMetricsService';\nexport { ProfileMetricsService, serviceName } from './ProfileMetricsService';\nexport type { ProfileMetricsServiceMethodActions } from './ProfileMetricsService-method-action-types';\nexport type {\n ProfileMetricsControllerMethodActions,\n ProfileMetricsControllerSkipInitialDelayAction,\n} from './ProfileMetricsController-method-action-types';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/profile-metrics-controller",
3
- "version": "2.0.0-preview-1137ade3",
3
+ "version": "2.0.0-preview-fa81dffb",
4
4
  "description": "Sample package to illustrate best practices for controllers",
5
5
  "keywords": [
6
6
  "MetaMask",
@@ -48,13 +48,14 @@
48
48
  "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
49
49
  },
50
50
  "dependencies": {
51
- "@metamask/accounts-controller": "^35.0.0",
51
+ "@metamask/accounts-controller": "^35.0.2",
52
52
  "@metamask/base-controller": "^9.0.0",
53
53
  "@metamask/controller-utils": "^11.18.0",
54
54
  "@metamask/keyring-controller": "^25.0.0",
55
55
  "@metamask/messenger": "^0.3.0",
56
- "@metamask/polling-controller": "^16.0.0",
56
+ "@metamask/polling-controller": "^16.0.2",
57
57
  "@metamask/profile-sync-controller": "^27.0.0",
58
+ "@metamask/transaction-controller": "^62.9.2",
58
59
  "@metamask/utils": "^11.9.0",
59
60
  "async-mutex": "^0.5.0"
60
61
  },