@metamask-previews/profile-metrics-controller 4.0.2-preview-aa202ec10 → 4.0.2-preview-6cd1bb2f5
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 +0 -5
- package/dist/ProfileMetricsService.cjs +12 -54
- package/dist/ProfileMetricsService.cjs.map +1 -1
- package/dist/ProfileMetricsService.d.cts +0 -5
- package/dist/ProfileMetricsService.d.cts.map +1 -1
- package/dist/ProfileMetricsService.d.mts +0 -5
- package/dist/ProfileMetricsService.d.mts.map +1 -1
- package/dist/ProfileMetricsService.mjs +12 -54
- package/dist/ProfileMetricsService.mjs.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -11,11 +11,6 @@ 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
|
-
|
|
19
14
|
## [4.0.2]
|
|
20
15
|
|
|
21
16
|
### 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,
|
|
13
|
+
var _ProfileMetricsService_instances, _a, _ProfileMetricsService_messenger, _ProfileMetricsService_fetch, _ProfileMetricsService_policy, _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,11 +60,6 @@ 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.
|
|
68
63
|
* @param args.env - The environment to determine the correct API endpoints.
|
|
69
64
|
*/
|
|
70
65
|
constructor({ messenger, fetch: fetchFunction, policyOptions = {}, env = profile_sync_controller_1.SDK.Env.DEV, }) {
|
|
@@ -78,18 +73,11 @@ class ProfileMetricsService {
|
|
|
78
73
|
*/
|
|
79
74
|
_ProfileMetricsService_fetch.set(this, void 0);
|
|
80
75
|
/**
|
|
81
|
-
*
|
|
76
|
+
* The policy that wraps the request.
|
|
82
77
|
*
|
|
83
78
|
* @see {@link createServicePolicy}
|
|
84
79
|
*/
|
|
85
|
-
|
|
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);
|
|
80
|
+
_ProfileMetricsService_policy.set(this, void 0);
|
|
93
81
|
/**
|
|
94
82
|
* The API base URL environment.
|
|
95
83
|
*/
|
|
@@ -97,16 +85,7 @@ class ProfileMetricsService {
|
|
|
97
85
|
this.name = exports.serviceName;
|
|
98
86
|
__classPrivateFieldSet(this, _ProfileMetricsService_messenger, messenger, "f");
|
|
99
87
|
__classPrivateFieldSet(this, _ProfileMetricsService_fetch, fetchFunction, "f");
|
|
100
|
-
__classPrivateFieldSet(this,
|
|
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");
|
|
88
|
+
__classPrivateFieldSet(this, _ProfileMetricsService_policy, (0, controller_utils_1.createServicePolicy)(policyOptions), "f");
|
|
110
89
|
__classPrivateFieldSet(this, _ProfileMetricsService_baseURL, getAuthUrl(env), "f");
|
|
111
90
|
__classPrivateFieldGet(this, _ProfileMetricsService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
112
91
|
}
|
|
@@ -121,14 +100,7 @@ class ProfileMetricsService {
|
|
|
121
100
|
* @see {@link createServicePolicy}
|
|
122
101
|
*/
|
|
123
102
|
onRetry(listener) {
|
|
124
|
-
|
|
125
|
-
const submitDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_submitMetricsPolicy, "f").onRetry(listener);
|
|
126
|
-
return {
|
|
127
|
-
dispose: () => {
|
|
128
|
-
fetchDisposable.dispose();
|
|
129
|
-
submitDisposable.dispose();
|
|
130
|
-
},
|
|
131
|
-
};
|
|
103
|
+
return __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").onRetry(listener);
|
|
132
104
|
}
|
|
133
105
|
/**
|
|
134
106
|
* Registers a handler that will be called after a set number of retry rounds
|
|
@@ -140,14 +112,7 @@ class ProfileMetricsService {
|
|
|
140
112
|
* @see {@link createServicePolicy}
|
|
141
113
|
*/
|
|
142
114
|
onBreak(listener) {
|
|
143
|
-
|
|
144
|
-
const submitDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_submitMetricsPolicy, "f").onBreak(listener);
|
|
145
|
-
return {
|
|
146
|
-
dispose: () => {
|
|
147
|
-
fetchDisposable.dispose();
|
|
148
|
-
submitDisposable.dispose();
|
|
149
|
-
},
|
|
150
|
-
};
|
|
115
|
+
return __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").onBreak(listener);
|
|
151
116
|
}
|
|
152
117
|
/**
|
|
153
118
|
* Registers a handler that will be called under one of two circumstances:
|
|
@@ -167,14 +132,7 @@ class ProfileMetricsService {
|
|
|
167
132
|
* {@link CockatielEvent}.
|
|
168
133
|
*/
|
|
169
134
|
onDegraded(listener) {
|
|
170
|
-
|
|
171
|
-
const submitDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_submitMetricsPolicy, "f").onDegraded(listener);
|
|
172
|
-
return {
|
|
173
|
-
dispose: () => {
|
|
174
|
-
fetchDisposable.dispose();
|
|
175
|
-
submitDisposable.dispose();
|
|
176
|
-
},
|
|
177
|
-
};
|
|
135
|
+
return __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").onDegraded(listener);
|
|
178
136
|
}
|
|
179
137
|
/**
|
|
180
138
|
* Fetch single-use nonces from the auth API, one per identifier.
|
|
@@ -215,7 +173,7 @@ class ProfileMetricsService {
|
|
|
215
173
|
* @returns The response from the API.
|
|
216
174
|
*/
|
|
217
175
|
async submitMetrics(data) {
|
|
218
|
-
await __classPrivateFieldGet(this,
|
|
176
|
+
await __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").execute(async () => {
|
|
219
177
|
const authToken = await __classPrivateFieldGet(this, _ProfileMetricsService_messenger, "f").call('AuthenticationController:getBearerToken', data.entropySourceId ?? undefined);
|
|
220
178
|
const url = new URL(`${__classPrivateFieldGet(this, _ProfileMetricsService_baseURL, "f")}/profile/accounts`);
|
|
221
179
|
const localResponse = await __classPrivateFieldGet(this, _ProfileMetricsService_fetch, "f").call(this, url, {
|
|
@@ -242,11 +200,11 @@ class ProfileMetricsService {
|
|
|
242
200
|
}
|
|
243
201
|
}
|
|
244
202
|
exports.ProfileMetricsService = ProfileMetricsService;
|
|
245
|
-
_a = ProfileMetricsService, _ProfileMetricsService_messenger = new WeakMap(), _ProfileMetricsService_fetch = new WeakMap(),
|
|
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 =
|
|
246
204
|
/**
|
|
247
205
|
* Mint nonces for a single ≤ {@link MAX_NONCE_BATCH_SIZE}-sized chunk of
|
|
248
|
-
* identifiers. Wrapped in {@link #
|
|
249
|
-
*
|
|
206
|
+
* identifiers. Wrapped in {@link #policy} for retry / degraded / circuit
|
|
207
|
+
* semantics consistent with the rest of the service.
|
|
250
208
|
*
|
|
251
209
|
* @param identifiers - The identifiers in this chunk. Must be 1..MAX_NONCE_BATCH_SIZE.
|
|
252
210
|
* @param entropySourceId - The entropy source ID forwarded to the bearer
|
|
@@ -254,7 +212,7 @@ _a = ProfileMetricsService, _ProfileMetricsService_messenger = new WeakMap(), _P
|
|
|
254
212
|
* @returns A map of identifier -> nonce for this chunk.
|
|
255
213
|
*/
|
|
256
214
|
async function _ProfileMetricsService_fetchNoncesChunk(identifiers, entropySourceId) {
|
|
257
|
-
return await __classPrivateFieldGet(this,
|
|
215
|
+
return await __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").execute(async () => {
|
|
258
216
|
const authToken = await __classPrivateFieldGet(this, _ProfileMetricsService_messenger, "f").call('AuthenticationController:getBearerToken', entropySourceId ?? undefined);
|
|
259
217
|
const url = new URL(`${__classPrivateFieldGet(this, _ProfileMetricsService_baseURL, "f")}/nonce/batch`);
|
|
260
218
|
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 * 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"]}
|
|
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"]}
|
|
@@ -111,11 +111,6 @@ 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.
|
|
119
114
|
* @param args.env - The environment to determine the correct API endpoints.
|
|
120
115
|
*/
|
|
121
116
|
constructor({ messenger, fetch: fetchFunction, policyOptions, env, }: {
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
1
|
+
{"version":3,"file":"ProfileMetricsService.d.cts","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":"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"}
|
|
@@ -111,11 +111,6 @@ 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.
|
|
119
114
|
* @param args.env - The environment to determine the correct API endpoints.
|
|
120
115
|
*/
|
|
121
116
|
constructor({ messenger, fetch: fetchFunction, policyOptions, env, }: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProfileMetricsService.d.mts","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;
|
|
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,
|
|
12
|
+
var _ProfileMetricsService_instances, _a, _ProfileMetricsService_messenger, _ProfileMetricsService_fetch, _ProfileMetricsService_policy, _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,11 +57,6 @@ 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.
|
|
65
60
|
* @param args.env - The environment to determine the correct API endpoints.
|
|
66
61
|
*/
|
|
67
62
|
constructor({ messenger, fetch: fetchFunction, policyOptions = {}, env = SDK.Env.DEV, }) {
|
|
@@ -75,18 +70,11 @@ export class ProfileMetricsService {
|
|
|
75
70
|
*/
|
|
76
71
|
_ProfileMetricsService_fetch.set(this, void 0);
|
|
77
72
|
/**
|
|
78
|
-
*
|
|
73
|
+
* The policy that wraps the request.
|
|
79
74
|
*
|
|
80
75
|
* @see {@link createServicePolicy}
|
|
81
76
|
*/
|
|
82
|
-
|
|
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);
|
|
77
|
+
_ProfileMetricsService_policy.set(this, void 0);
|
|
90
78
|
/**
|
|
91
79
|
* The API base URL environment.
|
|
92
80
|
*/
|
|
@@ -94,16 +82,7 @@ export class ProfileMetricsService {
|
|
|
94
82
|
this.name = serviceName;
|
|
95
83
|
__classPrivateFieldSet(this, _ProfileMetricsService_messenger, messenger, "f");
|
|
96
84
|
__classPrivateFieldSet(this, _ProfileMetricsService_fetch, fetchFunction, "f");
|
|
97
|
-
__classPrivateFieldSet(this,
|
|
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");
|
|
85
|
+
__classPrivateFieldSet(this, _ProfileMetricsService_policy, createServicePolicy(policyOptions), "f");
|
|
107
86
|
__classPrivateFieldSet(this, _ProfileMetricsService_baseURL, getAuthUrl(env), "f");
|
|
108
87
|
__classPrivateFieldGet(this, _ProfileMetricsService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
109
88
|
}
|
|
@@ -118,14 +97,7 @@ export class ProfileMetricsService {
|
|
|
118
97
|
* @see {@link createServicePolicy}
|
|
119
98
|
*/
|
|
120
99
|
onRetry(listener) {
|
|
121
|
-
|
|
122
|
-
const submitDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_submitMetricsPolicy, "f").onRetry(listener);
|
|
123
|
-
return {
|
|
124
|
-
dispose: () => {
|
|
125
|
-
fetchDisposable.dispose();
|
|
126
|
-
submitDisposable.dispose();
|
|
127
|
-
},
|
|
128
|
-
};
|
|
100
|
+
return __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").onRetry(listener);
|
|
129
101
|
}
|
|
130
102
|
/**
|
|
131
103
|
* Registers a handler that will be called after a set number of retry rounds
|
|
@@ -137,14 +109,7 @@ export class ProfileMetricsService {
|
|
|
137
109
|
* @see {@link createServicePolicy}
|
|
138
110
|
*/
|
|
139
111
|
onBreak(listener) {
|
|
140
|
-
|
|
141
|
-
const submitDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_submitMetricsPolicy, "f").onBreak(listener);
|
|
142
|
-
return {
|
|
143
|
-
dispose: () => {
|
|
144
|
-
fetchDisposable.dispose();
|
|
145
|
-
submitDisposable.dispose();
|
|
146
|
-
},
|
|
147
|
-
};
|
|
112
|
+
return __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").onBreak(listener);
|
|
148
113
|
}
|
|
149
114
|
/**
|
|
150
115
|
* Registers a handler that will be called under one of two circumstances:
|
|
@@ -164,14 +129,7 @@ export class ProfileMetricsService {
|
|
|
164
129
|
* {@link CockatielEvent}.
|
|
165
130
|
*/
|
|
166
131
|
onDegraded(listener) {
|
|
167
|
-
|
|
168
|
-
const submitDisposable = __classPrivateFieldGet(this, _ProfileMetricsService_submitMetricsPolicy, "f").onDegraded(listener);
|
|
169
|
-
return {
|
|
170
|
-
dispose: () => {
|
|
171
|
-
fetchDisposable.dispose();
|
|
172
|
-
submitDisposable.dispose();
|
|
173
|
-
},
|
|
174
|
-
};
|
|
132
|
+
return __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").onDegraded(listener);
|
|
175
133
|
}
|
|
176
134
|
/**
|
|
177
135
|
* Fetch single-use nonces from the auth API, one per identifier.
|
|
@@ -212,7 +170,7 @@ export class ProfileMetricsService {
|
|
|
212
170
|
* @returns The response from the API.
|
|
213
171
|
*/
|
|
214
172
|
async submitMetrics(data) {
|
|
215
|
-
await __classPrivateFieldGet(this,
|
|
173
|
+
await __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").execute(async () => {
|
|
216
174
|
const authToken = await __classPrivateFieldGet(this, _ProfileMetricsService_messenger, "f").call('AuthenticationController:getBearerToken', data.entropySourceId ?? undefined);
|
|
217
175
|
const url = new URL(`${__classPrivateFieldGet(this, _ProfileMetricsService_baseURL, "f")}/profile/accounts`);
|
|
218
176
|
const localResponse = await __classPrivateFieldGet(this, _ProfileMetricsService_fetch, "f").call(this, url, {
|
|
@@ -238,11 +196,11 @@ export class ProfileMetricsService {
|
|
|
238
196
|
});
|
|
239
197
|
}
|
|
240
198
|
}
|
|
241
|
-
_a = ProfileMetricsService, _ProfileMetricsService_messenger = new WeakMap(), _ProfileMetricsService_fetch = new WeakMap(),
|
|
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 =
|
|
242
200
|
/**
|
|
243
201
|
* Mint nonces for a single ≤ {@link MAX_NONCE_BATCH_SIZE}-sized chunk of
|
|
244
|
-
* identifiers. Wrapped in {@link #
|
|
245
|
-
*
|
|
202
|
+
* identifiers. Wrapped in {@link #policy} for retry / degraded / circuit
|
|
203
|
+
* semantics consistent with the rest of the service.
|
|
246
204
|
*
|
|
247
205
|
* @param identifiers - The identifiers in this chunk. Must be 1..MAX_NONCE_BATCH_SIZE.
|
|
248
206
|
* @param entropySourceId - The entropy source ID forwarded to the bearer
|
|
@@ -250,7 +208,7 @@ _a = ProfileMetricsService, _ProfileMetricsService_messenger = new WeakMap(), _P
|
|
|
250
208
|
* @returns A map of identifier -> nonce for this chunk.
|
|
251
209
|
*/
|
|
252
210
|
async function _ProfileMetricsService_fetchNoncesChunk(identifiers, entropySourceId) {
|
|
253
|
-
return await __classPrivateFieldGet(this,
|
|
211
|
+
return await __classPrivateFieldGet(this, _ProfileMetricsService_policy, "f").execute(async () => {
|
|
254
212
|
const authToken = await __classPrivateFieldGet(this, _ProfileMetricsService_messenger, "f").call('AuthenticationController:getBearerToken', entropySourceId ?? undefined);
|
|
255
213
|
const url = new URL(`${__classPrivateFieldGet(this, _ProfileMetricsService_baseURL, "f")}/nonce/batch`);
|
|
256
214
|
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 * 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"]}
|
|
1
|
+
{"version":3,"file":"ProfileMetricsService.mjs","sourceRoot":"","sources":["../src/ProfileMetricsService.ts"],"names":[],"mappings":";;;;;;;;;;;;AAIA,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,mCAAmC;AAE5E,OAAO,EAAE,GAAG,EAAE,0CAA0C;AAExD,OAAO,EACL,KAAK,EACL,MAAM,EACN,MAAM,EACN,IAAI,IAAI,UAAU,EACnB,8BAA8B;AAK/B;;;;;;;GAOG;AACH,MAAM,wBAAwB,GAAG,KAAK,CACpC,UAAU,CAAC;IACT,UAAU,EAAE,MAAM,EAAE;IACpB,UAAU,EAAE,MAAM,EAAE;IACpB,KAAK,EAAE,MAAM,EAAE;CAChB,CAAC,CACH,CAAC;AAEF,kBAAkB;AAElB;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,uBAAuB,CAAC;AA2DnD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEvC,oBAAoB;AAEpB,MAAM,yBAAyB,GAAG,CAAC,eAAe,EAAE,aAAa,CAAU,CAAC;AAkC5E,6BAA6B;AAE7B;;GAEG;AACH,MAAM,OAAO,qBAAqB;IAgChC;;;;;;;;;;;;OAYG;IACH,YAAY,EACV,SAAS,EACT,KAAK,EAAE,aAAa,EACpB,aAAa,GAAG,EAAE,EAClB,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,GAMlB;;QAjDD;;WAEG;QACM,mDAES;QAElB;;WAEG;QACM,+CAEK;QAEd;;;;WAIG;QACM,gDAAuB;QAEhC;;WAEG;QACM,iDAAiB;QA0BxB,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,uBAAA,IAAI,oCAAc,SAAS,MAAA,CAAC;QAC5B,uBAAA,IAAI,gCAAU,aAAa,MAAA,CAAC;QAC5B,uBAAA,IAAI,iCAAW,mBAAmB,CAAC,aAAa,CAAC,MAAA,CAAC;QAClD,uBAAA,IAAI,kCAAY,UAAU,CAAC,GAAG,CAAC,MAAA,CAAC;QAEhC,uBAAA,IAAI,wCAAW,CAAC,4BAA4B,CAC1C,IAAI,EACJ,yBAAyB,CAC1B,CAAC;IACJ,CAAC;IAED;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAiD;QACvD,OAAO,uBAAA,IAAI,qCAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED;;;;;;;;OAQG;IACH,OAAO,CAAC,QAAiD;QACvD,OAAO,uBAAA,IAAI,qCAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,CACR,QAAoD;QAEpD,OAAO,uBAAA,IAAI,qCAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,KAAK,CAAC,WAAW,CACf,IAAsC;QAEtC,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,UAAU,CAClB,mEAAmE,CACpE,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAe,EAAE,CAAC;QAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,IAAI,oBAAoB,EAAE,CAAC;YACvE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC;QACnE,CAAC;QACD,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,GAAG,CACpC,MAAM,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CACzB,uBAAA,IAAI,iFAAkB,MAAtB,IAAI,EAAmB,WAAW,EAAE,IAAI,CAAC,eAAe,CAAC,CAC1D,CACF,CAAC;QACF,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,YAAY,CAAC,CAAC;IAC5C,CAAC;IA2DD;;;;;OAKG;IACH,KAAK,CAAC,aAAa,CAAC,IAAwC;QAC1D,MAAM,uBAAA,IAAI,qCAAQ,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;YACpC,MAAM,SAAS,GAAG,MAAM,uBAAA,IAAI,wCAAW,CAAC,IAAI,CAC1C,yCAAyC,EACzC,IAAI,CAAC,eAAe,IAAI,SAAS,CAClC,CAAC;YACF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,uBAAA,IAAI,sCAAS,mBAAmB,CAAC,CAAC;YACzD,MAAM,aAAa,GAAG,MAAM,uBAAA,IAAI,oCAAO,MAAX,IAAI,EAAQ,GAAG,EAAE;gBAC3C,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,SAAS,EAAE;oBACpC,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,cAAc,EAAE,IAAI,CAAC,aAAa;oBAClC,QAAQ,EAAE,IAAI,CAAC,QAAQ;iBACxB,CAAC;gBACF,8CAA8C;gBAC9C,sCAAsC;gBACtC,iDAAiD;gBACjD,qDAAqD;gBACrD,WAAW,EAAE,MAAM;aACpB,CAAC,CAAC;YACH,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC;gBACtB,MAAM,IAAI,SAAS,CACjB,aAAa,CAAC,MAAM,EACpB,aAAa,GAAG,CAAC,QAAQ,EAAE,yBAAyB,aAAa,CAAC,MAAM,GAAG,CAC5E,CAAC;YACJ,CAAC;YACD,OAAO,aAAa,CAAC;QACvB,CAAC,CAAC,CAAC;IACL,CAAC;CACF;;AA/FC;;;;;;;;;GASG;AACH,KAAK,kDACH,WAAqB,EACrB,eAA0C;IAE1C,OAAO,MAAM,uBAAA,IAAI,qCAAQ,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;QAC3C,MAAM,SAAS,GAAG,MAAM,uBAAA,IAAI,wCAAW,CAAC,IAAI,CAC1C,yCAAyC,EACzC,eAAe,IAAI,SAAS,CAC7B,CAAC;QACF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,uBAAA,IAAI,sCAAS,cAAc,CAAC,CAAC;QACpD,MAAM,aAAa,GAAG,MAAM,uBAAA,IAAI,oCAAO,MAAX,IAAI,EAAQ,GAAG,EAAE;YAC3C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,SAAS,EAAE;gBACpC,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,CAAC;YACrC,WAAW,EAAE,MAAM;SACpB,CAAC,CAAC;QACH,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC;YACtB,MAAM,IAAI,SAAS,CACjB,aAAa,CAAC,MAAM,EACpB,aAAa,GAAG,CAAC,QAAQ,EAAE,yBAAyB,aAAa,CAAC,MAAM,GAAG,CAC5E,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAY,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC;QACjD,IAAI,CAAC,wBAAwB,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CAAC,qCAAqC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,MAAM,GAA2B,EAAE,CAAC;QAC1C,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;YACzB,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC;QACzC,CAAC;QACD,MAAM,aAAa,GACjB,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM;YAClC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CACvB,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CACjD,CAAC;QACJ,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CACb,aAAa,GAAG,CAAC,QAAQ,EAAE,uEAAuE,CACnG,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC;AA0CH;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,GAAY;IACrC,OAAO,GAAG,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,UAAU,SAAS,CAAC;AACpD,CAAC","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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask-previews/profile-metrics-controller",
|
|
3
|
-
"version": "4.0.2-preview-
|
|
3
|
+
"version": "4.0.2-preview-6cd1bb2f5",
|
|
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.
|
|
76
|
+
"@metamask/keyring-internal-api": "^11.0.1",
|
|
77
77
|
"@ts-bridge/cli": "^0.6.4",
|
|
78
78
|
"@types/jest": "^30.0.0",
|
|
79
79
|
"deepmerge": "^4.2.2",
|