@metamask-previews/profile-metrics-controller 4.0.2-preview-764a19ce8 → 4.0.2-preview-97f066f

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
@@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
12
  - Bump `@metamask/transaction-controller` from `^69.0.0` to `^69.2.1` ([#9568](https://github.com/MetaMask/core/pull/9568), [#9589](https://github.com/MetaMask/core/pull/9589), [#9593](https://github.com/MetaMask/core/pull/9593))
13
13
 
14
+ ### Fixed
15
+
16
+ - Disable in-request retries on `ProfileMetricsService:submitMetrics` by default (`maxRetries: 0`) ([#9667](https://github.com/MetaMask/core/pull/9667))
17
+ - Proof nonces are single-use, so retrying a spent `submitMetrics` payload caused `PUT /profile/accounts` 400s; `fetchNonces` still retries because a failed fetch soft-degrades to a proof-less submit that clears the queue.
18
+
14
19
  ## [4.0.2]
15
20
 
16
21
  ### Changed
@@ -10,7 +10,7 @@ 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 _ProfileMetricsService_instances, _a, _ProfileMetricsService_messenger, _ProfileMetricsService_fetch, _ProfileMetricsService_policy, _ProfileMetricsService_baseURL, _ProfileMetricsService_fetchNoncesChunk;
13
+ var _ProfileMetricsService_instances, _a, _ProfileMetricsService_messenger, _ProfileMetricsService_fetch, _ProfileMetricsService_fetchNoncesPolicy, _ProfileMetricsService_submitMetricsPolicy, _ProfileMetricsService_baseURL, _ProfileMetricsService_fetchNoncesChunk;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.getAuthUrl = exports.ProfileMetricsService = exports.MAX_NONCE_BATCH_SIZE = exports.serviceName = void 0;
16
16
  const controller_utils_1 = require("@metamask/controller-utils");
@@ -60,6 +60,11 @@ class ProfileMetricsService {
60
60
  * `node-fetch`).
61
61
  * @param args.policyOptions - Options to pass to `createServicePolicy`, which
62
62
  * is used to wrap each request. See {@link CreateServicePolicyOptions}.
63
+ * `submitMetrics` defaults to `maxRetries: 0` (proof nonces are single-use;
64
+ * the controller poll re-fetches nonces and retries failed submits).
65
+ * `fetchNonces` keeps the normal retry defaults — a failed nonce fetch
66
+ * soft-degrades to a proof-less submit that clears the queue, so retries
67
+ * here are what preserve proofs across transient errors.
63
68
  * @param args.env - The environment to determine the correct API endpoints.
64
69
  */
65
70
  constructor({ messenger, fetch: fetchFunction, policyOptions = {}, env = profile_sync_controller_1.SDK.Env.DEV, }) {
@@ -73,11 +78,18 @@ class ProfileMetricsService {
73
78
  */
74
79
  _ProfileMetricsService_fetch.set(this, void 0);
75
80
  /**
76
- * The policy that wraps the request.
81
+ * Policy for `fetchNonces`. Minting new nonces is safe to retry.
77
82
  *
78
83
  * @see {@link createServicePolicy}
79
84
  */
80
- _ProfileMetricsService_policy.set(this, void 0);
85
+ _ProfileMetricsService_fetchNoncesPolicy.set(this, void 0);
86
+ /**
87
+ * Policy for `submitMetrics`. Proof payloads embed single-use nonces, so
88
+ * in-request retries are disabled by default.
89
+ *
90
+ * @see {@link createServicePolicy}
91
+ */
92
+ _ProfileMetricsService_submitMetricsPolicy.set(this, void 0);
81
93
  /**
82
94
  * The API base URL environment.
83
95
  */
@@ -85,7 +97,16 @@ class ProfileMetricsService {
85
97
  this.name = exports.serviceName;
86
98
  __classPrivateFieldSet(this, _ProfileMetricsService_messenger, messenger, "f");
87
99
  __classPrivateFieldSet(this, _ProfileMetricsService_fetch, fetchFunction, "f");
88
- __classPrivateFieldSet(this, _ProfileMetricsService_policy, (0, controller_utils_1.createServicePolicy)(policyOptions), "f");
100
+ __classPrivateFieldSet(this, _ProfileMetricsService_fetchNoncesPolicy, (0, controller_utils_1.createServicePolicy)(policyOptions), "f");
101
+ // Keep the circuit breaker scaled to the retry count the same way
102
+ // `createServicePolicy` derives `DEFAULT_MAX_CONSECUTIVE_FAILURES`:
103
+ // `(1 + maxRetries) * 3` failed attempts ≈ 3 failed `execute()` calls.
104
+ const submitMaxRetries = policyOptions.maxRetries ?? 0;
105
+ __classPrivateFieldSet(this, _ProfileMetricsService_submitMetricsPolicy, (0, controller_utils_1.createServicePolicy)({
106
+ ...policyOptions,
107
+ maxRetries: submitMaxRetries,
108
+ maxConsecutiveFailures: policyOptions.maxConsecutiveFailures ?? (1 + submitMaxRetries) * 3,
109
+ }), "f");
89
110
  __classPrivateFieldSet(this, _ProfileMetricsService_baseURL, getAuthUrl(env), "f");
90
111
  __classPrivateFieldGet(this, _ProfileMetricsService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
91
112
  }
@@ -100,7 +121,14 @@ class ProfileMetricsService {
100
121
  * @see {@link createServicePolicy}
101
122
  */
102
123
  onRetry(listener) {
103
- return __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").onRetry(listener);
124
+ const fetchDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_fetchNoncesPolicy, "f").onRetry(listener);
125
+ const submitDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_submitMetricsPolicy, "f").onRetry(listener);
126
+ return {
127
+ dispose: () => {
128
+ fetchDisposable.dispose();
129
+ submitDisposable.dispose();
130
+ },
131
+ };
104
132
  }
105
133
  /**
106
134
  * Registers a handler that will be called after a set number of retry rounds
@@ -112,7 +140,14 @@ class ProfileMetricsService {
112
140
  * @see {@link createServicePolicy}
113
141
  */
114
142
  onBreak(listener) {
115
- return __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").onBreak(listener);
143
+ const fetchDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_fetchNoncesPolicy, "f").onBreak(listener);
144
+ const submitDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_submitMetricsPolicy, "f").onBreak(listener);
145
+ return {
146
+ dispose: () => {
147
+ fetchDisposable.dispose();
148
+ submitDisposable.dispose();
149
+ },
150
+ };
116
151
  }
117
152
  /**
118
153
  * Registers a handler that will be called under one of two circumstances:
@@ -132,7 +167,14 @@ class ProfileMetricsService {
132
167
  * {@link CockatielEvent}.
133
168
  */
134
169
  onDegraded(listener) {
135
- return __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").onDegraded(listener);
170
+ const fetchDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_fetchNoncesPolicy, "f").onDegraded(listener);
171
+ const submitDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_submitMetricsPolicy, "f").onDegraded(listener);
172
+ return {
173
+ dispose: () => {
174
+ fetchDisposable.dispose();
175
+ submitDisposable.dispose();
176
+ },
177
+ };
136
178
  }
137
179
  /**
138
180
  * Fetch single-use nonces from the auth API, one per identifier.
@@ -173,7 +215,7 @@ class ProfileMetricsService {
173
215
  * @returns The response from the API.
174
216
  */
175
217
  async submitMetrics(data) {
176
- await __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").execute(async () => {
218
+ await __classPrivateFieldGet(this, _ProfileMetricsService_submitMetricsPolicy, "f").execute(async () => {
177
219
  const authToken = await __classPrivateFieldGet(this, _ProfileMetricsService_messenger, "f").call('AuthenticationController:getBearerToken', data.entropySourceId ?? undefined);
178
220
  const url = new URL(`${__classPrivateFieldGet(this, _ProfileMetricsService_baseURL, "f")}/profile/accounts`);
179
221
  const localResponse = await __classPrivateFieldGet(this, _ProfileMetricsService_fetch, "f").call(this, url, {
@@ -200,11 +242,11 @@ class ProfileMetricsService {
200
242
  }
201
243
  }
202
244
  exports.ProfileMetricsService = ProfileMetricsService;
203
- _a = ProfileMetricsService, _ProfileMetricsService_messenger = new WeakMap(), _ProfileMetricsService_fetch = new WeakMap(), _ProfileMetricsService_policy = new WeakMap(), _ProfileMetricsService_baseURL = new WeakMap(), _ProfileMetricsService_instances = new WeakSet(), _ProfileMetricsService_fetchNoncesChunk =
245
+ _a = ProfileMetricsService, _ProfileMetricsService_messenger = new WeakMap(), _ProfileMetricsService_fetch = new WeakMap(), _ProfileMetricsService_fetchNoncesPolicy = new WeakMap(), _ProfileMetricsService_submitMetricsPolicy = new WeakMap(), _ProfileMetricsService_baseURL = new WeakMap(), _ProfileMetricsService_instances = new WeakSet(), _ProfileMetricsService_fetchNoncesChunk =
204
246
  /**
205
247
  * Mint nonces for a single ≤ {@link MAX_NONCE_BATCH_SIZE}-sized chunk of
206
- * identifiers. Wrapped in {@link #policy} for retry / degraded / circuit
207
- * semantics consistent with the rest of the service.
248
+ * identifiers. Wrapped in {@link #fetchNoncesPolicy} for retry / degraded /
249
+ * circuit semantics.
208
250
  *
209
251
  * @param identifiers - The identifiers in this chunk. Must be 1..MAX_NONCE_BATCH_SIZE.
210
252
  * @param entropySourceId - The entropy source ID forwarded to the bearer
@@ -212,7 +254,7 @@ _a = ProfileMetricsService, _ProfileMetricsService_messenger = new WeakMap(), _P
212
254
  * @returns A map of identifier -> nonce for this chunk.
213
255
  */
214
256
  async function _ProfileMetricsService_fetchNoncesChunk(identifiers, entropySourceId) {
215
- return await __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").execute(async () => {
257
+ return await __classPrivateFieldGet(this, _ProfileMetricsService_fetchNoncesPolicy, "f").execute(async () => {
216
258
  const authToken = await __classPrivateFieldGet(this, _ProfileMetricsService_messenger, "f").call('AuthenticationController:getBearerToken', entropySourceId ?? undefined);
217
259
  const url = new URL(`${__classPrivateFieldGet(this, _ProfileMetricsService_baseURL, "f")}/nonce/batch`);
218
260
  const localResponse = await __classPrivateFieldGet(this, _ProfileMetricsService_fetch, "f").call(this, url, {
@@ -1 +1 @@
1
- {"version":3,"file":"ProfileMetricsService.cjs","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CreateServicePolicyOptions,\n ServicePolicy,\n} from '@metamask/controller-utils';\nimport { createServicePolicy, HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport { SDK } from '@metamask/profile-sync-controller';\nimport type { AuthenticationController } from '@metamask/profile-sync-controller';\nimport {\n array,\n number,\n string,\n type as structType,\n} from '@metamask/superstruct';\nimport type { IDisposable } from 'cockatiel';\n\nimport type { ProfileMetricsServiceMethodActions } from './ProfileMetricsService-method-action-types.js';\n\n/**\n * The shape of an entry in the `POST /api/v2/nonce/batch` response body.\n *\n * `identifier` echoes the request identifier verbatim, mirroring the\n * documented behavior of the single-account `GET /api/v2/nonce` endpoint on\n * the same auth service. Defined with `type()` (not `object()`) so the\n * client tolerates additive server-side schema changes.\n */\nconst NonceBatchResponseStruct = array(\n structType({\n expires_in: number(),\n identifier: string(),\n nonce: string(),\n }),\n);\n\n// === GENERAL ===\n\n/**\n * The name of the {@link ProfileMetricsService}, used to namespace the\n * service's actions and events.\n */\nexport const serviceName = 'ProfileMetricsService';\n\n/**\n * A cryptographic proof that the caller controls the private key of an\n * account, as defined by the `PUT /api/v2/profile/accounts` endpoint of the\n * auth API. When present, the server verifies the signature against\n * `metamask:proof-of-ownership:<nonce>:<canonical address>` and permanently\n * marks the account as `verified: true`.\n */\nexport type AccountOwnershipProof = {\n /**\n * Single-use nonce obtained from {@link ProfileMetricsService.fetchNonces}.\n * Consumed by the server on verification; replay is not possible.\n */\n nonce: string;\n /**\n * Chain-native signature of `metamask:proof-of-ownership:<nonce>:<address>`,\n * always 0x-prefixed. The exact format varies by chain (see the auth API\n * spec — EIP-191 for `eip155`, ed25519 for `solana`, TIP-191 for `tron`,\n * BIP-322 for `bip122`).\n */\n signature: string;\n};\n\n/**\n * An account address along with its associated scopes and an optional\n * ownership proof.\n */\nexport type AccountWithScopes = {\n address: string;\n scopes: `${string}:${string}`[];\n proof?: AccountOwnershipProof;\n};\n\n/**\n * The shape of the request object for submitting metrics.\n */\nexport type ProfileMetricsSubmitMetricsRequest = {\n metametricsId: string;\n entropySourceId?: string | null;\n accounts: AccountWithScopes[];\n};\n\n/**\n * The shape of the request object for fetching a batch of single-use nonces.\n */\nexport type ProfileMetricsFetchNoncesRequest = {\n /**\n * The identifiers (canonical addresses) to mint a nonce for. The auth API\n * accepts between 1 and {@link MAX_NONCE_BATCH_SIZE} identifiers per call.\n */\n identifiers: string[];\n /**\n * The entropy source ID to use when fetching a bearer token. Pass `null` or\n * omit for accounts that do not belong to any entropy source.\n */\n entropySourceId?: string | null;\n};\n\n/**\n * Maximum number of identifiers the auth API will mint nonces for in a single\n * `POST /api/v2/nonce/batch` request. {@link ProfileMetricsService.fetchNonces}\n * uses this as the chunk size when the caller requests more than this many\n * nonces at once.\n */\nexport const MAX_NONCE_BATCH_SIZE = 50;\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = ['submitMetrics', 'fetchNonces'] as const;\n\n/**\n * Actions that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceActions = ProfileMetricsServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link ProfileMetricsService} calls.\n */\ntype AllowedActions =\n AuthenticationController.AuthenticationControllerGetBearerTokenAction;\n\n/**\n * Events that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceEvents = never;\n\n/**\n * Events from other messengers that {@link ProfileMetricsService} subscribes\n * to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger which is restricted to actions and events accessed by\n * {@link ProfileMetricsService}.\n */\nexport type ProfileMetricsServiceMessenger = Messenger<\n typeof serviceName,\n ProfileMetricsServiceActions | AllowedActions,\n ProfileMetricsServiceEvents | AllowedEvents\n>;\n\n// === SERVICE DEFINITION ===\n\n/**\n * A service for submitting user profile metrics (metrics ID and accounts).\n */\nexport class ProfileMetricsService {\n /**\n * The name of the service.\n */\n readonly name: typeof serviceName;\n\n /**\n * The messenger suited for this service.\n */\n readonly #messenger: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['messenger'];\n\n /**\n * A function that can be used to make an HTTP request.\n */\n readonly #fetch: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['fetch'];\n\n /**\n * The policy that wraps the request.\n *\n * @see {@link createServicePolicy}\n */\n readonly #policy: ServicePolicy;\n\n /**\n * The API base URL environment.\n */\n readonly #baseURL: string;\n\n /**\n * Constructs a new ProfileMetricsService object.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this service.\n * @param args.fetch - A function that can be used to make an HTTP request. If\n * your JavaScript environment supports `fetch` natively, you'll probably want\n * to pass that; otherwise you can pass an equivalent (such as `fetch` via\n * `node-fetch`).\n * @param args.policyOptions - Options to pass to `createServicePolicy`, which\n * is used to wrap each request. See {@link CreateServicePolicyOptions}.\n * @param args.env - The environment to determine the correct API endpoints.\n */\n constructor({\n messenger,\n fetch: fetchFunction,\n policyOptions = {},\n env = SDK.Env.DEV,\n }: {\n messenger: ProfileMetricsServiceMessenger;\n fetch: typeof fetch;\n policyOptions?: CreateServicePolicyOptions;\n env?: SDK.Env;\n }) {\n this.name = serviceName;\n this.#messenger = messenger;\n this.#fetch = fetchFunction;\n this.#policy = createServicePolicy(policyOptions);\n this.#baseURL = getAuthUrl(env);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Registers a handler that will be called after a request returns a non-500\n * response, causing a retry. Primarily useful in tests where timers are being\n * mocked.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onRetry(listener: Parameters<ServicePolicy['onRetry']>[0]): IDisposable {\n return this.#policy.onRetry(listener);\n }\n\n /**\n * Registers a handler that will be called after a set number of retry rounds\n * prove that requests to the API endpoint consistently return a 5xx response.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onBreak(listener: Parameters<ServicePolicy['onBreak']>[0]): IDisposable {\n return this.#policy.onBreak(listener);\n }\n\n /**\n * Registers a handler that will be called under one of two circumstances:\n *\n * 1. After a set number of retries prove that requests to the API\n * consistently result in one of the following failures:\n * 1. A connection initiation error\n * 2. A connection reset error\n * 3. A timeout error\n * 4. A non-JSON response\n * 5. A 502, 503, or 504 response\n * 2. After a successful request is made to the API, but the response takes\n * longer than a set duration to return.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n */\n onDegraded(\n listener: Parameters<ServicePolicy['onDegraded']>[0],\n ): IDisposable {\n return this.#policy.onDegraded(listener);\n }\n\n /**\n * Fetch single-use nonces from the auth API, one per identifier.\n *\n * Requests larger than {@link MAX_NONCE_BATCH_SIZE} are split into multiple\n * `POST /api/v2/nonce/batch` calls fired in parallel; the resulting maps are\n * merged into a single record. Each chunk independently goes through the\n * service policy (retry, circuit-breaker, degraded). If any chunk ultimately\n * fails, the whole call rejects so the caller can soft-degrade the entire\n * entropy-source batch consistently.\n *\n * The returned record is keyed by the auth API's echoed `identifier` field\n * (`response[i].identifier -> response[i].nonce`). The call asserts that\n * the response identifier set is exactly the requested set; any mismatch\n * (missing, extra, or duplicated identifier) causes the chunk to throw so\n * the caller never silently proceeds with partial nonces.\n *\n * @param data - The identifiers to mint nonces for, plus the optional\n * entropy source ID used to scope the bearer token.\n * @returns A map of identifier -> nonce.\n * @throws {RangeError} if no identifiers are provided.\n */\n async fetchNonces(\n data: ProfileMetricsFetchNoncesRequest,\n ): Promise<Record<string, string>> {\n if (data.identifiers.length === 0) {\n throw new RangeError(\n 'ProfileMetricsService.fetchNonces requires at least 1 identifier.',\n );\n }\n const chunks: string[][] = [];\n for (let i = 0; i < data.identifiers.length; i += MAX_NONCE_BATCH_SIZE) {\n chunks.push(data.identifiers.slice(i, i + MAX_NONCE_BATCH_SIZE));\n }\n const chunkResults = await Promise.all(\n chunks.map((identifiers) =>\n this.#fetchNoncesChunk(identifiers, data.entropySourceId),\n ),\n );\n return Object.assign({}, ...chunkResults);\n }\n\n /**\n * Mint nonces for a single ≤ {@link MAX_NONCE_BATCH_SIZE}-sized chunk of\n * identifiers. Wrapped in {@link #policy} for retry / degraded / circuit\n * semantics consistent with the rest of the service.\n *\n * @param identifiers - The identifiers in this chunk. Must be 1..MAX_NONCE_BATCH_SIZE.\n * @param entropySourceId - The entropy source ID forwarded to the bearer\n * token resolver.\n * @returns A map of identifier -> nonce for this chunk.\n */\n async #fetchNoncesChunk(\n identifiers: string[],\n entropySourceId: string | null | undefined,\n ): Promise<Record<string, string>> {\n return await this.#policy.execute(async () => {\n const authToken = await this.#messenger.call(\n 'AuthenticationController:getBearerToken',\n entropySourceId ?? undefined,\n );\n const url = new URL(`${this.#baseURL}/nonce/batch`);\n const localResponse = await this.#fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ identifiers }),\n credentials: 'omit',\n });\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Fetching '${url.toString()}' failed with status '${localResponse.status}'`,\n );\n }\n const body: unknown = await localResponse.json();\n if (!NonceBatchResponseStruct.is(body)) {\n throw new Error(`Malformed response received from '${url.toString()}'`);\n }\n const result: Record<string, string> = {};\n for (const entry of body) {\n result[entry.identifier] = entry.nonce;\n }\n const echoesRequest =\n body.length === identifiers.length &&\n identifiers.every((id) =>\n Object.prototype.hasOwnProperty.call(result, id),\n );\n if (!echoesRequest) {\n throw new Error(\n `Fetching '${url.toString()}' returned a response whose identifier set does not match the request`,\n );\n }\n return result;\n });\n }\n\n /**\n * Submit metrics to the API.\n *\n * @param data - The data to send in the metrics update request.\n * @returns The response from the API.\n */\n async submitMetrics(data: ProfileMetricsSubmitMetricsRequest): Promise<void> {\n await this.#policy.execute(async () => {\n const authToken = await this.#messenger.call(\n 'AuthenticationController:getBearerToken',\n data.entropySourceId ?? undefined,\n );\n const url = new URL(`${this.#baseURL}/profile/accounts`);\n const localResponse = await this.#fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n metametrics_id: data.metametricsId,\n accounts: data.accounts,\n }),\n // The auth API is stateless (no cookies used)\n // prevent marketing cookies scoped to\n // .metamask.io from being forwarded to api which\n // causes 431 Request Header Fields Too Large errors.\n credentials: 'omit',\n });\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Fetching '${url.toString()}' failed with status '${localResponse.status}'`,\n );\n }\n return localResponse;\n });\n }\n}\n\n/**\n * Returns the base URL for the given environment.\n *\n * @param env - The environment to get the URL for.\n * @returns The base URL for the environment.\n */\nexport function getAuthUrl(env: SDK.Env): string {\n return `${SDK.getEnvUrls(env).authApiUrl}/api/v2`;\n}\n"]}
1
+ {"version":3,"file":"ProfileMetricsService.cjs","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CreateServicePolicyOptions,\n ServicePolicy,\n} from '@metamask/controller-utils';\nimport { createServicePolicy, HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport { SDK } from '@metamask/profile-sync-controller';\nimport type { AuthenticationController } from '@metamask/profile-sync-controller';\nimport {\n array,\n number,\n string,\n type as structType,\n} from '@metamask/superstruct';\nimport type { IDisposable } from 'cockatiel';\n\nimport type { ProfileMetricsServiceMethodActions } from './ProfileMetricsService-method-action-types.js';\n\n/**\n * The shape of an entry in the `POST /api/v2/nonce/batch` response body.\n *\n * `identifier` echoes the request identifier verbatim, mirroring the\n * documented behavior of the single-account `GET /api/v2/nonce` endpoint on\n * the same auth service. Defined with `type()` (not `object()`) so the\n * client tolerates additive server-side schema changes.\n */\nconst NonceBatchResponseStruct = array(\n structType({\n expires_in: number(),\n identifier: string(),\n nonce: string(),\n }),\n);\n\n// === GENERAL ===\n\n/**\n * The name of the {@link ProfileMetricsService}, used to namespace the\n * service's actions and events.\n */\nexport const serviceName = 'ProfileMetricsService';\n\n/**\n * A cryptographic proof that the caller controls the private key of an\n * account, as defined by the `PUT /api/v2/profile/accounts` endpoint of the\n * auth API. When present, the server verifies the signature against\n * `metamask:proof-of-ownership:<nonce>:<canonical address>` and permanently\n * marks the account as `verified: true`.\n */\nexport type AccountOwnershipProof = {\n /**\n * Single-use nonce obtained from {@link ProfileMetricsService.fetchNonces}.\n * Consumed by the server on verification; replay is not possible.\n */\n nonce: string;\n /**\n * Chain-native signature of `metamask:proof-of-ownership:<nonce>:<address>`,\n * always 0x-prefixed. The exact format varies by chain (see the auth API\n * spec — EIP-191 for `eip155`, ed25519 for `solana`, TIP-191 for `tron`,\n * BIP-322 for `bip122`).\n */\n signature: string;\n};\n\n/**\n * An account address along with its associated scopes and an optional\n * ownership proof.\n */\nexport type AccountWithScopes = {\n address: string;\n scopes: `${string}:${string}`[];\n proof?: AccountOwnershipProof;\n};\n\n/**\n * The shape of the request object for submitting metrics.\n */\nexport type ProfileMetricsSubmitMetricsRequest = {\n metametricsId: string;\n entropySourceId?: string | null;\n accounts: AccountWithScopes[];\n};\n\n/**\n * The shape of the request object for fetching a batch of single-use nonces.\n */\nexport type ProfileMetricsFetchNoncesRequest = {\n /**\n * The identifiers (canonical addresses) to mint a nonce for. The auth API\n * accepts between 1 and {@link MAX_NONCE_BATCH_SIZE} identifiers per call.\n */\n identifiers: string[];\n /**\n * The entropy source ID to use when fetching a bearer token. Pass `null` or\n * omit for accounts that do not belong to any entropy source.\n */\n entropySourceId?: string | null;\n};\n\n/**\n * Maximum number of identifiers the auth API will mint nonces for in a single\n * `POST /api/v2/nonce/batch` request. {@link ProfileMetricsService.fetchNonces}\n * uses this as the chunk size when the caller requests more than this many\n * nonces at once.\n */\nexport const MAX_NONCE_BATCH_SIZE = 50;\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = ['submitMetrics', 'fetchNonces'] as const;\n\n/**\n * Actions that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceActions = ProfileMetricsServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link ProfileMetricsService} calls.\n */\ntype AllowedActions =\n AuthenticationController.AuthenticationControllerGetBearerTokenAction;\n\n/**\n * Events that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceEvents = never;\n\n/**\n * Events from other messengers that {@link ProfileMetricsService} subscribes\n * to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger which is restricted to actions and events accessed by\n * {@link ProfileMetricsService}.\n */\nexport type ProfileMetricsServiceMessenger = Messenger<\n typeof serviceName,\n ProfileMetricsServiceActions | AllowedActions,\n ProfileMetricsServiceEvents | AllowedEvents\n>;\n\n// === SERVICE DEFINITION ===\n\n/**\n * A service for submitting user profile metrics (metrics ID and accounts).\n */\nexport class ProfileMetricsService {\n /**\n * The name of the service.\n */\n readonly name: typeof serviceName;\n\n /**\n * The messenger suited for this service.\n */\n readonly #messenger: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['messenger'];\n\n /**\n * A function that can be used to make an HTTP request.\n */\n readonly #fetch: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['fetch'];\n\n /**\n * Policy for `fetchNonces`. Minting new nonces is safe to retry.\n *\n * @see {@link createServicePolicy}\n */\n readonly #fetchNoncesPolicy: ServicePolicy;\n\n /**\n * Policy for `submitMetrics`. Proof payloads embed single-use nonces, so\n * in-request retries are disabled by default.\n *\n * @see {@link createServicePolicy}\n */\n readonly #submitMetricsPolicy: ServicePolicy;\n\n /**\n * The API base URL environment.\n */\n readonly #baseURL: string;\n\n /**\n * Constructs a new ProfileMetricsService object.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this service.\n * @param args.fetch - A function that can be used to make an HTTP request. If\n * your JavaScript environment supports `fetch` natively, you'll probably want\n * to pass that; otherwise you can pass an equivalent (such as `fetch` via\n * `node-fetch`).\n * @param args.policyOptions - Options to pass to `createServicePolicy`, which\n * is used to wrap each request. See {@link CreateServicePolicyOptions}.\n * `submitMetrics` defaults to `maxRetries: 0` (proof nonces are single-use;\n * the controller poll re-fetches nonces and retries failed submits).\n * `fetchNonces` keeps the normal retry defaults — a failed nonce fetch\n * soft-degrades to a proof-less submit that clears the queue, so retries\n * here are what preserve proofs across transient errors.\n * @param args.env - The environment to determine the correct API endpoints.\n */\n constructor({\n messenger,\n fetch: fetchFunction,\n policyOptions = {},\n env = SDK.Env.DEV,\n }: {\n messenger: ProfileMetricsServiceMessenger;\n fetch: typeof fetch;\n policyOptions?: CreateServicePolicyOptions;\n env?: SDK.Env;\n }) {\n this.name = serviceName;\n this.#messenger = messenger;\n this.#fetch = fetchFunction;\n this.#fetchNoncesPolicy = createServicePolicy(policyOptions);\n // Keep the circuit breaker scaled to the retry count the same way\n // `createServicePolicy` derives `DEFAULT_MAX_CONSECUTIVE_FAILURES`:\n // `(1 + maxRetries) * 3` failed attempts ≈ 3 failed `execute()` calls.\n const submitMaxRetries = policyOptions.maxRetries ?? 0;\n this.#submitMetricsPolicy = createServicePolicy({\n ...policyOptions,\n maxRetries: submitMaxRetries,\n maxConsecutiveFailures:\n policyOptions.maxConsecutiveFailures ?? (1 + submitMaxRetries) * 3,\n });\n this.#baseURL = getAuthUrl(env);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Registers a handler that will be called after a request returns a non-500\n * response, causing a retry. Primarily useful in tests where timers are being\n * mocked.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onRetry(listener: Parameters<ServicePolicy['onRetry']>[0]): IDisposable {\n const fetchDisposable = this.#fetchNoncesPolicy.onRetry(listener);\n const submitDisposable = this.#submitMetricsPolicy.onRetry(listener);\n return {\n dispose: (): void => {\n fetchDisposable.dispose();\n submitDisposable.dispose();\n },\n };\n }\n\n /**\n * Registers a handler that will be called after a set number of retry rounds\n * prove that requests to the API endpoint consistently return a 5xx response.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onBreak(listener: Parameters<ServicePolicy['onBreak']>[0]): IDisposable {\n const fetchDisposable = this.#fetchNoncesPolicy.onBreak(listener);\n const submitDisposable = this.#submitMetricsPolicy.onBreak(listener);\n return {\n dispose: (): void => {\n fetchDisposable.dispose();\n submitDisposable.dispose();\n },\n };\n }\n\n /**\n * Registers a handler that will be called under one of two circumstances:\n *\n * 1. After a set number of retries prove that requests to the API\n * consistently result in one of the following failures:\n * 1. A connection initiation error\n * 2. A connection reset error\n * 3. A timeout error\n * 4. A non-JSON response\n * 5. A 502, 503, or 504 response\n * 2. After a successful request is made to the API, but the response takes\n * longer than a set duration to return.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n */\n onDegraded(\n listener: Parameters<ServicePolicy['onDegraded']>[0],\n ): IDisposable {\n const fetchDisposable = this.#fetchNoncesPolicy.onDegraded(listener);\n const submitDisposable = this.#submitMetricsPolicy.onDegraded(listener);\n return {\n dispose: (): void => {\n fetchDisposable.dispose();\n submitDisposable.dispose();\n },\n };\n }\n\n /**\n * Fetch single-use nonces from the auth API, one per identifier.\n *\n * Requests larger than {@link MAX_NONCE_BATCH_SIZE} are split into multiple\n * `POST /api/v2/nonce/batch` calls fired in parallel; the resulting maps are\n * merged into a single record. Each chunk independently goes through the\n * service policy (retry, circuit-breaker, degraded). If any chunk ultimately\n * fails, the whole call rejects so the caller can soft-degrade the entire\n * entropy-source batch consistently.\n *\n * The returned record is keyed by the auth API's echoed `identifier` field\n * (`response[i].identifier -> response[i].nonce`). The call asserts that\n * the response identifier set is exactly the requested set; any mismatch\n * (missing, extra, or duplicated identifier) causes the chunk to throw so\n * the caller never silently proceeds with partial nonces.\n *\n * @param data - The identifiers to mint nonces for, plus the optional\n * entropy source ID used to scope the bearer token.\n * @returns A map of identifier -> nonce.\n * @throws {RangeError} if no identifiers are provided.\n */\n async fetchNonces(\n data: ProfileMetricsFetchNoncesRequest,\n ): Promise<Record<string, string>> {\n if (data.identifiers.length === 0) {\n throw new RangeError(\n 'ProfileMetricsService.fetchNonces requires at least 1 identifier.',\n );\n }\n const chunks: string[][] = [];\n for (let i = 0; i < data.identifiers.length; i += MAX_NONCE_BATCH_SIZE) {\n chunks.push(data.identifiers.slice(i, i + MAX_NONCE_BATCH_SIZE));\n }\n const chunkResults = await Promise.all(\n chunks.map((identifiers) =>\n this.#fetchNoncesChunk(identifiers, data.entropySourceId),\n ),\n );\n return Object.assign({}, ...chunkResults);\n }\n\n /**\n * Mint nonces for a single ≤ {@link MAX_NONCE_BATCH_SIZE}-sized chunk of\n * identifiers. Wrapped in {@link #fetchNoncesPolicy} for retry / degraded /\n * circuit semantics.\n *\n * @param identifiers - The identifiers in this chunk. Must be 1..MAX_NONCE_BATCH_SIZE.\n * @param entropySourceId - The entropy source ID forwarded to the bearer\n * token resolver.\n * @returns A map of identifier -> nonce for this chunk.\n */\n async #fetchNoncesChunk(\n identifiers: string[],\n entropySourceId: string | null | undefined,\n ): Promise<Record<string, string>> {\n return await this.#fetchNoncesPolicy.execute(async () => {\n const authToken = await this.#messenger.call(\n 'AuthenticationController:getBearerToken',\n entropySourceId ?? undefined,\n );\n const url = new URL(`${this.#baseURL}/nonce/batch`);\n const localResponse = await this.#fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ identifiers }),\n credentials: 'omit',\n });\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Fetching '${url.toString()}' failed with status '${localResponse.status}'`,\n );\n }\n const body: unknown = await localResponse.json();\n if (!NonceBatchResponseStruct.is(body)) {\n throw new Error(`Malformed response received from '${url.toString()}'`);\n }\n const result: Record<string, string> = {};\n for (const entry of body) {\n result[entry.identifier] = entry.nonce;\n }\n const echoesRequest =\n body.length === identifiers.length &&\n identifiers.every((id) =>\n Object.prototype.hasOwnProperty.call(result, id),\n );\n if (!echoesRequest) {\n throw new Error(\n `Fetching '${url.toString()}' returned a response whose identifier set does not match the request`,\n );\n }\n return result;\n });\n }\n\n /**\n * Submit metrics to the API.\n *\n * @param data - The data to send in the metrics update request.\n * @returns The response from the API.\n */\n async submitMetrics(data: ProfileMetricsSubmitMetricsRequest): Promise<void> {\n await this.#submitMetricsPolicy.execute(async () => {\n const authToken = await this.#messenger.call(\n 'AuthenticationController:getBearerToken',\n data.entropySourceId ?? undefined,\n );\n const url = new URL(`${this.#baseURL}/profile/accounts`);\n const localResponse = await this.#fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n metametrics_id: data.metametricsId,\n accounts: data.accounts,\n }),\n // The auth API is stateless (no cookies used)\n // prevent marketing cookies scoped to\n // .metamask.io from being forwarded to api which\n // causes 431 Request Header Fields Too Large errors.\n credentials: 'omit',\n });\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Fetching '${url.toString()}' failed with status '${localResponse.status}'`,\n );\n }\n return localResponse;\n });\n }\n}\n\n/**\n * Returns the base URL for the given environment.\n *\n * @param env - The environment to get the URL for.\n * @returns The base URL for the environment.\n */\nexport function getAuthUrl(env: SDK.Env): string {\n return `${SDK.getEnvUrls(env).authApiUrl}/api/v2`;\n}\n"]}
@@ -111,6 +111,11 @@ export declare class ProfileMetricsService {
111
111
  * `node-fetch`).
112
112
  * @param args.policyOptions - Options to pass to `createServicePolicy`, which
113
113
  * is used to wrap each request. See {@link CreateServicePolicyOptions}.
114
+ * `submitMetrics` defaults to `maxRetries: 0` (proof nonces are single-use;
115
+ * the controller poll re-fetches nonces and retries failed submits).
116
+ * `fetchNonces` keeps the normal retry defaults — a failed nonce fetch
117
+ * soft-degrades to a proof-less submit that clears the queue, so retries
118
+ * here are what preserve proofs across transient errors.
114
119
  * @param args.env - The environment to determine the correct API endpoints.
115
120
  */
116
121
  constructor({ messenger, fetch: fetchFunction, policyOptions, env, }: {
@@ -1 +1 @@
1
- {"version":3,"file":"ProfileMetricsService.d.cts","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ProfileMetricsService.d.cts","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,aAAa,EACd,mCAAmC;AAEpC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,EAAE,GAAG,EAAE,0CAA0C;AACxD,OAAO,KAAK,EAAE,wBAAwB,EAAE,0CAA0C;AAOlF,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB;AAE7C,OAAO,KAAK,EAAE,kCAAkC,EAAE,wDAAuD;AAoBzG;;;GAGG;AACH,eAAO,MAAM,WAAW,0BAA0B,CAAC;AAEnD;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,GAAG,MAAM,IAAI,MAAM,EAAE,EAAE,CAAC;IAChC,KAAK,CAAC,EAAE,qBAAqB,CAAC;CAC/B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kCAAkC,GAAG;IAC/C,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gCAAgC,GAAG;IAC7C;;;OAGG;IACH,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAMvC;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG,kCAAkC,CAAC;AAE9E;;GAEG;AACH,KAAK,cAAc,GACjB,wBAAwB,CAAC,4CAA4C,CAAC;AAExE;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG,KAAK,CAAC;AAEhD;;;GAGG;AACH,KAAK,aAAa,GAAG,KAAK,CAAC;AAE3B;;;GAGG;AACH,MAAM,MAAM,8BAA8B,GAAG,SAAS,CACpD,OAAO,WAAW,EAClB,4BAA4B,GAAG,cAAc,EAC7C,2BAA2B,GAAG,aAAa,CAC5C,CAAC;AAIF;;GAEG;AACH,qBAAa,qBAAqB;;IAChC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,WAAW,CAAC;IAoClC;;;;;;;;;;;;;;;;;OAiBG;gBACS,EACV,SAAS,EACT,KAAK,EAAE,aAAa,EACpB,aAAkB,EAClB,GAAiB,GAClB,EAAE;QACD,SAAS,EAAE,8BAA8B,CAAC;QAC1C,KAAK,EAAE,OAAO,KAAK,CAAC;QACpB,aAAa,CAAC,EAAE,0BAA0B,CAAC;QAC3C,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC;KACf;IAuBD;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW;IAWvE;;;;;;;;OAQG;IACH,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW;IAWvE;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,CACR,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,GACnD,WAAW;IAWd;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,WAAW,CACf,IAAI,EAAE,gCAAgC,GACrC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IA2ElC;;;;;OAKG;IACG,aAAa,CAAC,IAAI,EAAE,kCAAkC,GAAG,OAAO,CAAC,IAAI,CAAC;CAgC7E;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,GAAG,MAAM,CAE/C"}
@@ -111,6 +111,11 @@ export declare class ProfileMetricsService {
111
111
  * `node-fetch`).
112
112
  * @param args.policyOptions - Options to pass to `createServicePolicy`, which
113
113
  * is used to wrap each request. See {@link CreateServicePolicyOptions}.
114
+ * `submitMetrics` defaults to `maxRetries: 0` (proof nonces are single-use;
115
+ * the controller poll re-fetches nonces and retries failed submits).
116
+ * `fetchNonces` keeps the normal retry defaults — a failed nonce fetch
117
+ * soft-degrades to a proof-less submit that clears the queue, so retries
118
+ * here are what preserve proofs across transient errors.
114
119
  * @param args.env - The environment to determine the correct API endpoints.
115
120
  */
116
121
  constructor({ messenger, fetch: fetchFunction, policyOptions, env, }: {
@@ -1 +1 @@
1
- {"version":3,"file":"ProfileMetricsService.d.mts","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ProfileMetricsService.d.mts","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"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"}
@@ -9,7 +9,7 @@ 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 _ProfileMetricsService_instances, _a, _ProfileMetricsService_messenger, _ProfileMetricsService_fetch, _ProfileMetricsService_policy, _ProfileMetricsService_baseURL, _ProfileMetricsService_fetchNoncesChunk;
12
+ var _ProfileMetricsService_instances, _a, _ProfileMetricsService_messenger, _ProfileMetricsService_fetch, _ProfileMetricsService_fetchNoncesPolicy, _ProfileMetricsService_submitMetricsPolicy, _ProfileMetricsService_baseURL, _ProfileMetricsService_fetchNoncesChunk;
13
13
  import { createServicePolicy, HttpError } from "@metamask/controller-utils";
14
14
  import { SDK } from "@metamask/profile-sync-controller";
15
15
  import { array, number, string, type as structType } from "@metamask/superstruct";
@@ -57,6 +57,11 @@ export class ProfileMetricsService {
57
57
  * `node-fetch`).
58
58
  * @param args.policyOptions - Options to pass to `createServicePolicy`, which
59
59
  * is used to wrap each request. See {@link CreateServicePolicyOptions}.
60
+ * `submitMetrics` defaults to `maxRetries: 0` (proof nonces are single-use;
61
+ * the controller poll re-fetches nonces and retries failed submits).
62
+ * `fetchNonces` keeps the normal retry defaults — a failed nonce fetch
63
+ * soft-degrades to a proof-less submit that clears the queue, so retries
64
+ * here are what preserve proofs across transient errors.
60
65
  * @param args.env - The environment to determine the correct API endpoints.
61
66
  */
62
67
  constructor({ messenger, fetch: fetchFunction, policyOptions = {}, env = SDK.Env.DEV, }) {
@@ -70,11 +75,18 @@ export class ProfileMetricsService {
70
75
  */
71
76
  _ProfileMetricsService_fetch.set(this, void 0);
72
77
  /**
73
- * The policy that wraps the request.
78
+ * Policy for `fetchNonces`. Minting new nonces is safe to retry.
74
79
  *
75
80
  * @see {@link createServicePolicy}
76
81
  */
77
- _ProfileMetricsService_policy.set(this, void 0);
82
+ _ProfileMetricsService_fetchNoncesPolicy.set(this, void 0);
83
+ /**
84
+ * Policy for `submitMetrics`. Proof payloads embed single-use nonces, so
85
+ * in-request retries are disabled by default.
86
+ *
87
+ * @see {@link createServicePolicy}
88
+ */
89
+ _ProfileMetricsService_submitMetricsPolicy.set(this, void 0);
78
90
  /**
79
91
  * The API base URL environment.
80
92
  */
@@ -82,7 +94,16 @@ export class ProfileMetricsService {
82
94
  this.name = serviceName;
83
95
  __classPrivateFieldSet(this, _ProfileMetricsService_messenger, messenger, "f");
84
96
  __classPrivateFieldSet(this, _ProfileMetricsService_fetch, fetchFunction, "f");
85
- __classPrivateFieldSet(this, _ProfileMetricsService_policy, createServicePolicy(policyOptions), "f");
97
+ __classPrivateFieldSet(this, _ProfileMetricsService_fetchNoncesPolicy, createServicePolicy(policyOptions), "f");
98
+ // Keep the circuit breaker scaled to the retry count the same way
99
+ // `createServicePolicy` derives `DEFAULT_MAX_CONSECUTIVE_FAILURES`:
100
+ // `(1 + maxRetries) * 3` failed attempts ≈ 3 failed `execute()` calls.
101
+ const submitMaxRetries = policyOptions.maxRetries ?? 0;
102
+ __classPrivateFieldSet(this, _ProfileMetricsService_submitMetricsPolicy, createServicePolicy({
103
+ ...policyOptions,
104
+ maxRetries: submitMaxRetries,
105
+ maxConsecutiveFailures: policyOptions.maxConsecutiveFailures ?? (1 + submitMaxRetries) * 3,
106
+ }), "f");
86
107
  __classPrivateFieldSet(this, _ProfileMetricsService_baseURL, getAuthUrl(env), "f");
87
108
  __classPrivateFieldGet(this, _ProfileMetricsService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
88
109
  }
@@ -97,7 +118,14 @@ export class ProfileMetricsService {
97
118
  * @see {@link createServicePolicy}
98
119
  */
99
120
  onRetry(listener) {
100
- return __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").onRetry(listener);
121
+ const fetchDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_fetchNoncesPolicy, "f").onRetry(listener);
122
+ const submitDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_submitMetricsPolicy, "f").onRetry(listener);
123
+ return {
124
+ dispose: () => {
125
+ fetchDisposable.dispose();
126
+ submitDisposable.dispose();
127
+ },
128
+ };
101
129
  }
102
130
  /**
103
131
  * Registers a handler that will be called after a set number of retry rounds
@@ -109,7 +137,14 @@ export class ProfileMetricsService {
109
137
  * @see {@link createServicePolicy}
110
138
  */
111
139
  onBreak(listener) {
112
- return __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").onBreak(listener);
140
+ const fetchDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_fetchNoncesPolicy, "f").onBreak(listener);
141
+ const submitDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_submitMetricsPolicy, "f").onBreak(listener);
142
+ return {
143
+ dispose: () => {
144
+ fetchDisposable.dispose();
145
+ submitDisposable.dispose();
146
+ },
147
+ };
113
148
  }
114
149
  /**
115
150
  * Registers a handler that will be called under one of two circumstances:
@@ -129,7 +164,14 @@ export class ProfileMetricsService {
129
164
  * {@link CockatielEvent}.
130
165
  */
131
166
  onDegraded(listener) {
132
- return __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").onDegraded(listener);
167
+ const fetchDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_fetchNoncesPolicy, "f").onDegraded(listener);
168
+ const submitDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_submitMetricsPolicy, "f").onDegraded(listener);
169
+ return {
170
+ dispose: () => {
171
+ fetchDisposable.dispose();
172
+ submitDisposable.dispose();
173
+ },
174
+ };
133
175
  }
134
176
  /**
135
177
  * Fetch single-use nonces from the auth API, one per identifier.
@@ -170,7 +212,7 @@ export class ProfileMetricsService {
170
212
  * @returns The response from the API.
171
213
  */
172
214
  async submitMetrics(data) {
173
- await __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").execute(async () => {
215
+ await __classPrivateFieldGet(this, _ProfileMetricsService_submitMetricsPolicy, "f").execute(async () => {
174
216
  const authToken = await __classPrivateFieldGet(this, _ProfileMetricsService_messenger, "f").call('AuthenticationController:getBearerToken', data.entropySourceId ?? undefined);
175
217
  const url = new URL(`${__classPrivateFieldGet(this, _ProfileMetricsService_baseURL, "f")}/profile/accounts`);
176
218
  const localResponse = await __classPrivateFieldGet(this, _ProfileMetricsService_fetch, "f").call(this, url, {
@@ -196,11 +238,11 @@ export class ProfileMetricsService {
196
238
  });
197
239
  }
198
240
  }
199
- _a = ProfileMetricsService, _ProfileMetricsService_messenger = new WeakMap(), _ProfileMetricsService_fetch = new WeakMap(), _ProfileMetricsService_policy = new WeakMap(), _ProfileMetricsService_baseURL = new WeakMap(), _ProfileMetricsService_instances = new WeakSet(), _ProfileMetricsService_fetchNoncesChunk =
241
+ _a = ProfileMetricsService, _ProfileMetricsService_messenger = new WeakMap(), _ProfileMetricsService_fetch = new WeakMap(), _ProfileMetricsService_fetchNoncesPolicy = new WeakMap(), _ProfileMetricsService_submitMetricsPolicy = new WeakMap(), _ProfileMetricsService_baseURL = new WeakMap(), _ProfileMetricsService_instances = new WeakSet(), _ProfileMetricsService_fetchNoncesChunk =
200
242
  /**
201
243
  * Mint nonces for a single ≤ {@link MAX_NONCE_BATCH_SIZE}-sized chunk of
202
- * identifiers. Wrapped in {@link #policy} for retry / degraded / circuit
203
- * semantics consistent with the rest of the service.
244
+ * identifiers. Wrapped in {@link #fetchNoncesPolicy} for retry / degraded /
245
+ * circuit semantics.
204
246
  *
205
247
  * @param identifiers - The identifiers in this chunk. Must be 1..MAX_NONCE_BATCH_SIZE.
206
248
  * @param entropySourceId - The entropy source ID forwarded to the bearer
@@ -208,7 +250,7 @@ _a = ProfileMetricsService, _ProfileMetricsService_messenger = new WeakMap(), _P
208
250
  * @returns A map of identifier -> nonce for this chunk.
209
251
  */
210
252
  async function _ProfileMetricsService_fetchNoncesChunk(identifiers, entropySourceId) {
211
- return await __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").execute(async () => {
253
+ return await __classPrivateFieldGet(this, _ProfileMetricsService_fetchNoncesPolicy, "f").execute(async () => {
212
254
  const authToken = await __classPrivateFieldGet(this, _ProfileMetricsService_messenger, "f").call('AuthenticationController:getBearerToken', entropySourceId ?? undefined);
213
255
  const url = new URL(`${__classPrivateFieldGet(this, _ProfileMetricsService_baseURL, "f")}/nonce/batch`);
214
256
  const localResponse = await __classPrivateFieldGet(this, _ProfileMetricsService_fetch, "f").call(this, url, {
@@ -1 +1 @@
1
- {"version":3,"file":"ProfileMetricsService.mjs","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CreateServicePolicyOptions,\n ServicePolicy,\n} from '@metamask/controller-utils';\nimport { createServicePolicy, HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport { SDK } from '@metamask/profile-sync-controller';\nimport type { AuthenticationController } from '@metamask/profile-sync-controller';\nimport {\n array,\n number,\n string,\n type as structType,\n} from '@metamask/superstruct';\nimport type { IDisposable } from 'cockatiel';\n\nimport type { ProfileMetricsServiceMethodActions } from './ProfileMetricsService-method-action-types.js';\n\n/**\n * The shape of an entry in the `POST /api/v2/nonce/batch` response body.\n *\n * `identifier` echoes the request identifier verbatim, mirroring the\n * documented behavior of the single-account `GET /api/v2/nonce` endpoint on\n * the same auth service. Defined with `type()` (not `object()`) so the\n * client tolerates additive server-side schema changes.\n */\nconst NonceBatchResponseStruct = array(\n structType({\n expires_in: number(),\n identifier: string(),\n nonce: string(),\n }),\n);\n\n// === GENERAL ===\n\n/**\n * The name of the {@link ProfileMetricsService}, used to namespace the\n * service's actions and events.\n */\nexport const serviceName = 'ProfileMetricsService';\n\n/**\n * A cryptographic proof that the caller controls the private key of an\n * account, as defined by the `PUT /api/v2/profile/accounts` endpoint of the\n * auth API. When present, the server verifies the signature against\n * `metamask:proof-of-ownership:<nonce>:<canonical address>` and permanently\n * marks the account as `verified: true`.\n */\nexport type AccountOwnershipProof = {\n /**\n * Single-use nonce obtained from {@link ProfileMetricsService.fetchNonces}.\n * Consumed by the server on verification; replay is not possible.\n */\n nonce: string;\n /**\n * Chain-native signature of `metamask:proof-of-ownership:<nonce>:<address>`,\n * always 0x-prefixed. The exact format varies by chain (see the auth API\n * spec — EIP-191 for `eip155`, ed25519 for `solana`, TIP-191 for `tron`,\n * BIP-322 for `bip122`).\n */\n signature: string;\n};\n\n/**\n * An account address along with its associated scopes and an optional\n * ownership proof.\n */\nexport type AccountWithScopes = {\n address: string;\n scopes: `${string}:${string}`[];\n proof?: AccountOwnershipProof;\n};\n\n/**\n * The shape of the request object for submitting metrics.\n */\nexport type ProfileMetricsSubmitMetricsRequest = {\n metametricsId: string;\n entropySourceId?: string | null;\n accounts: AccountWithScopes[];\n};\n\n/**\n * The shape of the request object for fetching a batch of single-use nonces.\n */\nexport type ProfileMetricsFetchNoncesRequest = {\n /**\n * The identifiers (canonical addresses) to mint a nonce for. The auth API\n * accepts between 1 and {@link MAX_NONCE_BATCH_SIZE} identifiers per call.\n */\n identifiers: string[];\n /**\n * The entropy source ID to use when fetching a bearer token. Pass `null` or\n * omit for accounts that do not belong to any entropy source.\n */\n entropySourceId?: string | null;\n};\n\n/**\n * Maximum number of identifiers the auth API will mint nonces for in a single\n * `POST /api/v2/nonce/batch` request. {@link ProfileMetricsService.fetchNonces}\n * uses this as the chunk size when the caller requests more than this many\n * nonces at once.\n */\nexport const MAX_NONCE_BATCH_SIZE = 50;\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = ['submitMetrics', 'fetchNonces'] as const;\n\n/**\n * Actions that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceActions = ProfileMetricsServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link ProfileMetricsService} calls.\n */\ntype AllowedActions =\n AuthenticationController.AuthenticationControllerGetBearerTokenAction;\n\n/**\n * Events that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceEvents = never;\n\n/**\n * Events from other messengers that {@link ProfileMetricsService} subscribes\n * to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger which is restricted to actions and events accessed by\n * {@link ProfileMetricsService}.\n */\nexport type ProfileMetricsServiceMessenger = Messenger<\n typeof serviceName,\n ProfileMetricsServiceActions | AllowedActions,\n ProfileMetricsServiceEvents | AllowedEvents\n>;\n\n// === SERVICE DEFINITION ===\n\n/**\n * A service for submitting user profile metrics (metrics ID and accounts).\n */\nexport class ProfileMetricsService {\n /**\n * The name of the service.\n */\n readonly name: typeof serviceName;\n\n /**\n * The messenger suited for this service.\n */\n readonly #messenger: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['messenger'];\n\n /**\n * A function that can be used to make an HTTP request.\n */\n readonly #fetch: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['fetch'];\n\n /**\n * The policy that wraps the request.\n *\n * @see {@link createServicePolicy}\n */\n readonly #policy: ServicePolicy;\n\n /**\n * The API base URL environment.\n */\n readonly #baseURL: string;\n\n /**\n * Constructs a new ProfileMetricsService object.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this service.\n * @param args.fetch - A function that can be used to make an HTTP request. If\n * your JavaScript environment supports `fetch` natively, you'll probably want\n * to pass that; otherwise you can pass an equivalent (such as `fetch` via\n * `node-fetch`).\n * @param args.policyOptions - Options to pass to `createServicePolicy`, which\n * is used to wrap each request. See {@link CreateServicePolicyOptions}.\n * @param args.env - The environment to determine the correct API endpoints.\n */\n constructor({\n messenger,\n fetch: fetchFunction,\n policyOptions = {},\n env = SDK.Env.DEV,\n }: {\n messenger: ProfileMetricsServiceMessenger;\n fetch: typeof fetch;\n policyOptions?: CreateServicePolicyOptions;\n env?: SDK.Env;\n }) {\n this.name = serviceName;\n this.#messenger = messenger;\n this.#fetch = fetchFunction;\n this.#policy = createServicePolicy(policyOptions);\n this.#baseURL = getAuthUrl(env);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Registers a handler that will be called after a request returns a non-500\n * response, causing a retry. Primarily useful in tests where timers are being\n * mocked.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onRetry(listener: Parameters<ServicePolicy['onRetry']>[0]): IDisposable {\n return this.#policy.onRetry(listener);\n }\n\n /**\n * Registers a handler that will be called after a set number of retry rounds\n * prove that requests to the API endpoint consistently return a 5xx response.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onBreak(listener: Parameters<ServicePolicy['onBreak']>[0]): IDisposable {\n return this.#policy.onBreak(listener);\n }\n\n /**\n * Registers a handler that will be called under one of two circumstances:\n *\n * 1. After a set number of retries prove that requests to the API\n * consistently result in one of the following failures:\n * 1. A connection initiation error\n * 2. A connection reset error\n * 3. A timeout error\n * 4. A non-JSON response\n * 5. A 502, 503, or 504 response\n * 2. After a successful request is made to the API, but the response takes\n * longer than a set duration to return.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n */\n onDegraded(\n listener: Parameters<ServicePolicy['onDegraded']>[0],\n ): IDisposable {\n return this.#policy.onDegraded(listener);\n }\n\n /**\n * Fetch single-use nonces from the auth API, one per identifier.\n *\n * Requests larger than {@link MAX_NONCE_BATCH_SIZE} are split into multiple\n * `POST /api/v2/nonce/batch` calls fired in parallel; the resulting maps are\n * merged into a single record. Each chunk independently goes through the\n * service policy (retry, circuit-breaker, degraded). If any chunk ultimately\n * fails, the whole call rejects so the caller can soft-degrade the entire\n * entropy-source batch consistently.\n *\n * The returned record is keyed by the auth API's echoed `identifier` field\n * (`response[i].identifier -> response[i].nonce`). The call asserts that\n * the response identifier set is exactly the requested set; any mismatch\n * (missing, extra, or duplicated identifier) causes the chunk to throw so\n * the caller never silently proceeds with partial nonces.\n *\n * @param data - The identifiers to mint nonces for, plus the optional\n * entropy source ID used to scope the bearer token.\n * @returns A map of identifier -> nonce.\n * @throws {RangeError} if no identifiers are provided.\n */\n async fetchNonces(\n data: ProfileMetricsFetchNoncesRequest,\n ): Promise<Record<string, string>> {\n if (data.identifiers.length === 0) {\n throw new RangeError(\n 'ProfileMetricsService.fetchNonces requires at least 1 identifier.',\n );\n }\n const chunks: string[][] = [];\n for (let i = 0; i < data.identifiers.length; i += MAX_NONCE_BATCH_SIZE) {\n chunks.push(data.identifiers.slice(i, i + MAX_NONCE_BATCH_SIZE));\n }\n const chunkResults = await Promise.all(\n chunks.map((identifiers) =>\n this.#fetchNoncesChunk(identifiers, data.entropySourceId),\n ),\n );\n return Object.assign({}, ...chunkResults);\n }\n\n /**\n * Mint nonces for a single ≤ {@link MAX_NONCE_BATCH_SIZE}-sized chunk of\n * identifiers. Wrapped in {@link #policy} for retry / degraded / circuit\n * semantics consistent with the rest of the service.\n *\n * @param identifiers - The identifiers in this chunk. Must be 1..MAX_NONCE_BATCH_SIZE.\n * @param entropySourceId - The entropy source ID forwarded to the bearer\n * token resolver.\n * @returns A map of identifier -> nonce for this chunk.\n */\n async #fetchNoncesChunk(\n identifiers: string[],\n entropySourceId: string | null | undefined,\n ): Promise<Record<string, string>> {\n return await this.#policy.execute(async () => {\n const authToken = await this.#messenger.call(\n 'AuthenticationController:getBearerToken',\n entropySourceId ?? undefined,\n );\n const url = new URL(`${this.#baseURL}/nonce/batch`);\n const localResponse = await this.#fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ identifiers }),\n credentials: 'omit',\n });\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Fetching '${url.toString()}' failed with status '${localResponse.status}'`,\n );\n }\n const body: unknown = await localResponse.json();\n if (!NonceBatchResponseStruct.is(body)) {\n throw new Error(`Malformed response received from '${url.toString()}'`);\n }\n const result: Record<string, string> = {};\n for (const entry of body) {\n result[entry.identifier] = entry.nonce;\n }\n const echoesRequest =\n body.length === identifiers.length &&\n identifiers.every((id) =>\n Object.prototype.hasOwnProperty.call(result, id),\n );\n if (!echoesRequest) {\n throw new Error(\n `Fetching '${url.toString()}' returned a response whose identifier set does not match the request`,\n );\n }\n return result;\n });\n }\n\n /**\n * Submit metrics to the API.\n *\n * @param data - The data to send in the metrics update request.\n * @returns The response from the API.\n */\n async submitMetrics(data: ProfileMetricsSubmitMetricsRequest): Promise<void> {\n await this.#policy.execute(async () => {\n const authToken = await this.#messenger.call(\n 'AuthenticationController:getBearerToken',\n data.entropySourceId ?? undefined,\n );\n const url = new URL(`${this.#baseURL}/profile/accounts`);\n const localResponse = await this.#fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n metametrics_id: data.metametricsId,\n accounts: data.accounts,\n }),\n // The auth API is stateless (no cookies used)\n // prevent marketing cookies scoped to\n // .metamask.io from being forwarded to api which\n // causes 431 Request Header Fields Too Large errors.\n credentials: 'omit',\n });\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Fetching '${url.toString()}' failed with status '${localResponse.status}'`,\n );\n }\n return localResponse;\n });\n }\n}\n\n/**\n * Returns the base URL for the given environment.\n *\n * @param env - The environment to get the URL for.\n * @returns The base URL for the environment.\n */\nexport function getAuthUrl(env: SDK.Env): string {\n return `${SDK.getEnvUrls(env).authApiUrl}/api/v2`;\n}\n"]}
1
+ {"version":3,"file":"ProfileMetricsService.mjs","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CreateServicePolicyOptions,\n ServicePolicy,\n} from '@metamask/controller-utils';\nimport { createServicePolicy, HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport { SDK } from '@metamask/profile-sync-controller';\nimport type { AuthenticationController } from '@metamask/profile-sync-controller';\nimport {\n array,\n number,\n string,\n type as structType,\n} from '@metamask/superstruct';\nimport type { IDisposable } from 'cockatiel';\n\nimport type { ProfileMetricsServiceMethodActions } from './ProfileMetricsService-method-action-types.js';\n\n/**\n * The shape of an entry in the `POST /api/v2/nonce/batch` response body.\n *\n * `identifier` echoes the request identifier verbatim, mirroring the\n * documented behavior of the single-account `GET /api/v2/nonce` endpoint on\n * the same auth service. Defined with `type()` (not `object()`) so the\n * client tolerates additive server-side schema changes.\n */\nconst NonceBatchResponseStruct = array(\n structType({\n expires_in: number(),\n identifier: string(),\n nonce: string(),\n }),\n);\n\n// === GENERAL ===\n\n/**\n * The name of the {@link ProfileMetricsService}, used to namespace the\n * service's actions and events.\n */\nexport const serviceName = 'ProfileMetricsService';\n\n/**\n * A cryptographic proof that the caller controls the private key of an\n * account, as defined by the `PUT /api/v2/profile/accounts` endpoint of the\n * auth API. When present, the server verifies the signature against\n * `metamask:proof-of-ownership:<nonce>:<canonical address>` and permanently\n * marks the account as `verified: true`.\n */\nexport type AccountOwnershipProof = {\n /**\n * Single-use nonce obtained from {@link ProfileMetricsService.fetchNonces}.\n * Consumed by the server on verification; replay is not possible.\n */\n nonce: string;\n /**\n * Chain-native signature of `metamask:proof-of-ownership:<nonce>:<address>`,\n * always 0x-prefixed. The exact format varies by chain (see the auth API\n * spec — EIP-191 for `eip155`, ed25519 for `solana`, TIP-191 for `tron`,\n * BIP-322 for `bip122`).\n */\n signature: string;\n};\n\n/**\n * An account address along with its associated scopes and an optional\n * ownership proof.\n */\nexport type AccountWithScopes = {\n address: string;\n scopes: `${string}:${string}`[];\n proof?: AccountOwnershipProof;\n};\n\n/**\n * The shape of the request object for submitting metrics.\n */\nexport type ProfileMetricsSubmitMetricsRequest = {\n metametricsId: string;\n entropySourceId?: string | null;\n accounts: AccountWithScopes[];\n};\n\n/**\n * The shape of the request object for fetching a batch of single-use nonces.\n */\nexport type ProfileMetricsFetchNoncesRequest = {\n /**\n * The identifiers (canonical addresses) to mint a nonce for. The auth API\n * accepts between 1 and {@link MAX_NONCE_BATCH_SIZE} identifiers per call.\n */\n identifiers: string[];\n /**\n * The entropy source ID to use when fetching a bearer token. Pass `null` or\n * omit for accounts that do not belong to any entropy source.\n */\n entropySourceId?: string | null;\n};\n\n/**\n * Maximum number of identifiers the auth API will mint nonces for in a single\n * `POST /api/v2/nonce/batch` request. {@link ProfileMetricsService.fetchNonces}\n * uses this as the chunk size when the caller requests more than this many\n * nonces at once.\n */\nexport const MAX_NONCE_BATCH_SIZE = 50;\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = ['submitMetrics', 'fetchNonces'] as const;\n\n/**\n * Actions that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceActions = ProfileMetricsServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link ProfileMetricsService} calls.\n */\ntype AllowedActions =\n AuthenticationController.AuthenticationControllerGetBearerTokenAction;\n\n/**\n * Events that {@link ProfileMetricsService} exposes to other consumers.\n */\nexport type ProfileMetricsServiceEvents = never;\n\n/**\n * Events from other messengers that {@link ProfileMetricsService} subscribes\n * to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger which is restricted to actions and events accessed by\n * {@link ProfileMetricsService}.\n */\nexport type ProfileMetricsServiceMessenger = Messenger<\n typeof serviceName,\n ProfileMetricsServiceActions | AllowedActions,\n ProfileMetricsServiceEvents | AllowedEvents\n>;\n\n// === SERVICE DEFINITION ===\n\n/**\n * A service for submitting user profile metrics (metrics ID and accounts).\n */\nexport class ProfileMetricsService {\n /**\n * The name of the service.\n */\n readonly name: typeof serviceName;\n\n /**\n * The messenger suited for this service.\n */\n readonly #messenger: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['messenger'];\n\n /**\n * A function that can be used to make an HTTP request.\n */\n readonly #fetch: ConstructorParameters<\n typeof ProfileMetricsService\n >[0]['fetch'];\n\n /**\n * Policy for `fetchNonces`. Minting new nonces is safe to retry.\n *\n * @see {@link createServicePolicy}\n */\n readonly #fetchNoncesPolicy: ServicePolicy;\n\n /**\n * Policy for `submitMetrics`. Proof payloads embed single-use nonces, so\n * in-request retries are disabled by default.\n *\n * @see {@link createServicePolicy}\n */\n readonly #submitMetricsPolicy: ServicePolicy;\n\n /**\n * The API base URL environment.\n */\n readonly #baseURL: string;\n\n /**\n * Constructs a new ProfileMetricsService object.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this service.\n * @param args.fetch - A function that can be used to make an HTTP request. If\n * your JavaScript environment supports `fetch` natively, you'll probably want\n * to pass that; otherwise you can pass an equivalent (such as `fetch` via\n * `node-fetch`).\n * @param args.policyOptions - Options to pass to `createServicePolicy`, which\n * is used to wrap each request. See {@link CreateServicePolicyOptions}.\n * `submitMetrics` defaults to `maxRetries: 0` (proof nonces are single-use;\n * the controller poll re-fetches nonces and retries failed submits).\n * `fetchNonces` keeps the normal retry defaults — a failed nonce fetch\n * soft-degrades to a proof-less submit that clears the queue, so retries\n * here are what preserve proofs across transient errors.\n * @param args.env - The environment to determine the correct API endpoints.\n */\n constructor({\n messenger,\n fetch: fetchFunction,\n policyOptions = {},\n env = SDK.Env.DEV,\n }: {\n messenger: ProfileMetricsServiceMessenger;\n fetch: typeof fetch;\n policyOptions?: CreateServicePolicyOptions;\n env?: SDK.Env;\n }) {\n this.name = serviceName;\n this.#messenger = messenger;\n this.#fetch = fetchFunction;\n this.#fetchNoncesPolicy = createServicePolicy(policyOptions);\n // Keep the circuit breaker scaled to the retry count the same way\n // `createServicePolicy` derives `DEFAULT_MAX_CONSECUTIVE_FAILURES`:\n // `(1 + maxRetries) * 3` failed attempts ≈ 3 failed `execute()` calls.\n const submitMaxRetries = policyOptions.maxRetries ?? 0;\n this.#submitMetricsPolicy = createServicePolicy({\n ...policyOptions,\n maxRetries: submitMaxRetries,\n maxConsecutiveFailures:\n policyOptions.maxConsecutiveFailures ?? (1 + submitMaxRetries) * 3,\n });\n this.#baseURL = getAuthUrl(env);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Registers a handler that will be called after a request returns a non-500\n * response, causing a retry. Primarily useful in tests where timers are being\n * mocked.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onRetry(listener: Parameters<ServicePolicy['onRetry']>[0]): IDisposable {\n const fetchDisposable = this.#fetchNoncesPolicy.onRetry(listener);\n const submitDisposable = this.#submitMetricsPolicy.onRetry(listener);\n return {\n dispose: (): void => {\n fetchDisposable.dispose();\n submitDisposable.dispose();\n },\n };\n }\n\n /**\n * Registers a handler that will be called after a set number of retry rounds\n * prove that requests to the API endpoint consistently return a 5xx response.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n * @see {@link createServicePolicy}\n */\n onBreak(listener: Parameters<ServicePolicy['onBreak']>[0]): IDisposable {\n const fetchDisposable = this.#fetchNoncesPolicy.onBreak(listener);\n const submitDisposable = this.#submitMetricsPolicy.onBreak(listener);\n return {\n dispose: (): void => {\n fetchDisposable.dispose();\n submitDisposable.dispose();\n },\n };\n }\n\n /**\n * Registers a handler that will be called under one of two circumstances:\n *\n * 1. After a set number of retries prove that requests to the API\n * consistently result in one of the following failures:\n * 1. A connection initiation error\n * 2. A connection reset error\n * 3. A timeout error\n * 4. A non-JSON response\n * 5. A 502, 503, or 504 response\n * 2. After a successful request is made to the API, but the response takes\n * longer than a set duration to return.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler. See\n * {@link CockatielEvent}.\n */\n onDegraded(\n listener: Parameters<ServicePolicy['onDegraded']>[0],\n ): IDisposable {\n const fetchDisposable = this.#fetchNoncesPolicy.onDegraded(listener);\n const submitDisposable = this.#submitMetricsPolicy.onDegraded(listener);\n return {\n dispose: (): void => {\n fetchDisposable.dispose();\n submitDisposable.dispose();\n },\n };\n }\n\n /**\n * Fetch single-use nonces from the auth API, one per identifier.\n *\n * Requests larger than {@link MAX_NONCE_BATCH_SIZE} are split into multiple\n * `POST /api/v2/nonce/batch` calls fired in parallel; the resulting maps are\n * merged into a single record. Each chunk independently goes through the\n * service policy (retry, circuit-breaker, degraded). If any chunk ultimately\n * fails, the whole call rejects so the caller can soft-degrade the entire\n * entropy-source batch consistently.\n *\n * The returned record is keyed by the auth API's echoed `identifier` field\n * (`response[i].identifier -> response[i].nonce`). The call asserts that\n * the response identifier set is exactly the requested set; any mismatch\n * (missing, extra, or duplicated identifier) causes the chunk to throw so\n * the caller never silently proceeds with partial nonces.\n *\n * @param data - The identifiers to mint nonces for, plus the optional\n * entropy source ID used to scope the bearer token.\n * @returns A map of identifier -> nonce.\n * @throws {RangeError} if no identifiers are provided.\n */\n async fetchNonces(\n data: ProfileMetricsFetchNoncesRequest,\n ): Promise<Record<string, string>> {\n if (data.identifiers.length === 0) {\n throw new RangeError(\n 'ProfileMetricsService.fetchNonces requires at least 1 identifier.',\n );\n }\n const chunks: string[][] = [];\n for (let i = 0; i < data.identifiers.length; i += MAX_NONCE_BATCH_SIZE) {\n chunks.push(data.identifiers.slice(i, i + MAX_NONCE_BATCH_SIZE));\n }\n const chunkResults = await Promise.all(\n chunks.map((identifiers) =>\n this.#fetchNoncesChunk(identifiers, data.entropySourceId),\n ),\n );\n return Object.assign({}, ...chunkResults);\n }\n\n /**\n * Mint nonces for a single ≤ {@link MAX_NONCE_BATCH_SIZE}-sized chunk of\n * identifiers. Wrapped in {@link #fetchNoncesPolicy} for retry / degraded /\n * circuit semantics.\n *\n * @param identifiers - The identifiers in this chunk. Must be 1..MAX_NONCE_BATCH_SIZE.\n * @param entropySourceId - The entropy source ID forwarded to the bearer\n * token resolver.\n * @returns A map of identifier -> nonce for this chunk.\n */\n async #fetchNoncesChunk(\n identifiers: string[],\n entropySourceId: string | null | undefined,\n ): Promise<Record<string, string>> {\n return await this.#fetchNoncesPolicy.execute(async () => {\n const authToken = await this.#messenger.call(\n 'AuthenticationController:getBearerToken',\n entropySourceId ?? undefined,\n );\n const url = new URL(`${this.#baseURL}/nonce/batch`);\n const localResponse = await this.#fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ identifiers }),\n credentials: 'omit',\n });\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Fetching '${url.toString()}' failed with status '${localResponse.status}'`,\n );\n }\n const body: unknown = await localResponse.json();\n if (!NonceBatchResponseStruct.is(body)) {\n throw new Error(`Malformed response received from '${url.toString()}'`);\n }\n const result: Record<string, string> = {};\n for (const entry of body) {\n result[entry.identifier] = entry.nonce;\n }\n const echoesRequest =\n body.length === identifiers.length &&\n identifiers.every((id) =>\n Object.prototype.hasOwnProperty.call(result, id),\n );\n if (!echoesRequest) {\n throw new Error(\n `Fetching '${url.toString()}' returned a response whose identifier set does not match the request`,\n );\n }\n return result;\n });\n }\n\n /**\n * Submit metrics to the API.\n *\n * @param data - The data to send in the metrics update request.\n * @returns The response from the API.\n */\n async submitMetrics(data: ProfileMetricsSubmitMetricsRequest): Promise<void> {\n await this.#submitMetricsPolicy.execute(async () => {\n const authToken = await this.#messenger.call(\n 'AuthenticationController:getBearerToken',\n data.entropySourceId ?? undefined,\n );\n const url = new URL(`${this.#baseURL}/profile/accounts`);\n const localResponse = await this.#fetch(url, {\n method: 'PUT',\n headers: {\n Authorization: `Bearer ${authToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n metametrics_id: data.metametricsId,\n accounts: data.accounts,\n }),\n // The auth API is stateless (no cookies used)\n // prevent marketing cookies scoped to\n // .metamask.io from being forwarded to api which\n // causes 431 Request Header Fields Too Large errors.\n credentials: 'omit',\n });\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Fetching '${url.toString()}' failed with status '${localResponse.status}'`,\n );\n }\n return localResponse;\n });\n }\n}\n\n/**\n * Returns the base URL for the given environment.\n *\n * @param env - The environment to get the URL for.\n * @returns The base URL for the environment.\n */\nexport function getAuthUrl(env: SDK.Env): string {\n return `${SDK.getEnvUrls(env).authApiUrl}/api/v2`;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/profile-metrics-controller",
3
- "version": "4.0.2-preview-764a19ce8",
3
+ "version": "4.0.2-preview-97f066f",
4
4
  "description": "Manages user profile metrics",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -73,7 +73,7 @@
73
73
  },
74
74
  "devDependencies": {
75
75
  "@metamask/auto-changelog": "^6.1.0",
76
- "@metamask/keyring-internal-api": "^11.0.1",
76
+ "@metamask/keyring-internal-api": "^11.0.2",
77
77
  "@ts-bridge/cli": "^0.6.4",
78
78
  "@types/jest": "^30.0.0",
79
79
  "deepmerge": "^4.2.2",