@metamask-previews/analytics-controller 4.1.0-preview-46de95678 → 4.2.0-preview-9a6250503
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 +8 -1
- package/README.md +20 -0
- package/dist/AnalyticsController-method-action-types.d.ts +34 -1
- package/dist/AnalyticsController-method-action-types.d.ts.map +1 -1
- package/dist/AnalyticsController-method-action-types.js.map +1 -1
- package/dist/AnalyticsController.d.ts +31 -0
- package/dist/AnalyticsController.d.ts.map +1 -1
- package/dist/AnalyticsController.js +59 -0
- package/dist/AnalyticsController.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/track-error-as-analytics.d.ts +108 -0
- package/dist/track-error-as-analytics.d.ts.map +1 -0
- package/dist/track-error-as-analytics.js +171 -0
- package/dist/track-error-as-analytics.js.map +1 -0
- package/package.json +2 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,sBAAsB,EACtB,kCAAkC,GACnC,MAAM,0BAA0B,CAAC;AAMlC,gBAAgB;AAChB,OAAO,EAAE,kCAAkC,EAAE,MAAM,yCAAyC,CAAC;AAqC7F,mBAAmB;AACnB,OAAO,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["// Export controller class and state utilities\nexport {\n AnalyticsController,\n AnalyticsPurpose,\n EVENT_FRAGMENT_MAX_AGE,\n getDefaultAnalyticsControllerState,\n} from './AnalyticsController.js';\nexport type {\n AnalyticsControllerInitialState,\n AnalyticsControllerOptions,\n} from './AnalyticsController.js';\n\n// Export errors\nexport { AnalyticsPlatformAdapterSetupError } from './AnalyticsPlatformAdapterSetupError.js';\n\n// Export types\nexport type {\n AnalyticsContext,\n AnalyticsEventProperties,\n AnalyticsDeliveryOptions,\n AnalyticsInvocationCallback,\n AnalyticsLocationContext,\n AnalyticsLocationField,\n AnalyticsUserTraits,\n AnalyticsPlatformAdapter,\n AnalyticsTrackingEvent,\n} from './AnalyticsPlatformAdapter.types.js';\n\n// Export event fragment types\nexport type {\n AnalyticsEventFragment,\n AnalyticsEventFragmentFinalizeOptions,\n AnalyticsEventFragmentOptions,\n AnalyticsEventFragmentPayload,\n AnalyticsEventFragments,\n ReadonlyAnalyticsEventFragment,\n} from './EventFragment.types.js';\n\n// Export state types\nexport type {\n AnalyticsControllerState,\n AnalyticsEventQueue,\n AnalyticsEventsConfig,\n AnalyticsQueuedEvent,\n AnalyticsQueuedEventType,\n AnalyticsQueuedTrackEvent,\n AnalyticsQueuedIdentifyEvent,\n AnalyticsQueuedViewEvent,\n} from './AnalyticsController.js';\n\n// Export selectors\nexport { analyticsControllerSelectors } from './selectors.js';\n\n// Export messenger types\nexport type { AnalyticsControllerMessenger } from './AnalyticsController.js';\n\n// Export action and event types\nexport type {\n AnalyticsControllerActions,\n AnalyticsControllerEvents,\n AnalyticsControllerGetStateAction,\n AnalyticsControllerStateChangeEvent,\n} from './AnalyticsController.js';\nexport type {\n AnalyticsControllerTrackEventAction,\n AnalyticsControllerIdentifyAction,\n AnalyticsControllerTrackViewAction,\n AnalyticsControllerOptInAction,\n AnalyticsControllerOptOutAction,\n AnalyticsControllerResetConsentDecisionAction,\n AnalyticsControllerOptInToMarketingAction,\n AnalyticsControllerOptOutOfMarketingAction,\n AnalyticsControllerResetMarketingConsentDecisionAction,\n AnalyticsControllerSetMarketingCampaignCookieIdAction,\n AnalyticsControllerCreateEventFragmentAction,\n AnalyticsControllerUpsertEventFragmentAction,\n AnalyticsControllerUpdateEventFragmentAction,\n AnalyticsControllerGetEventFragmentByIdAction,\n AnalyticsControllerDeleteEventFragmentAction,\n AnalyticsControllerFinalizeEventFragmentAction,\n AnalyticsControllerMethodActions,\n} from './AnalyticsController-method-action-types.js';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,sBAAsB,EACtB,kCAAkC,GACnC,MAAM,0BAA0B,CAAC;AAMlC,OAAO,EACL,8BAA8B,EAC9B,yBAAyB,GAC1B,MAAM,+BAA+B,CAAC;AAMvC,gBAAgB;AAChB,OAAO,EAAE,kCAAkC,EAAE,MAAM,yCAAyC,CAAC;AAqC7F,mBAAmB;AACnB,OAAO,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["// Export controller class and state utilities\nexport {\n AnalyticsController,\n AnalyticsPurpose,\n EVENT_FRAGMENT_MAX_AGE,\n getDefaultAnalyticsControllerState,\n} from './AnalyticsController.js';\nexport type {\n AnalyticsControllerInitialState,\n AnalyticsControllerOptions,\n} from './AnalyticsController.js';\n\nexport {\n DEFAULT_ERROR_TRACKING_OPTIONS,\n ERROR_OCCURRED_EVENT_NAME,\n} from './track-error-as-analytics.js';\nexport type {\n TrackErrorAsAnalyticsOptions,\n TrackErrorVolumeControls,\n} from './track-error-as-analytics.js';\n\n// Export errors\nexport { AnalyticsPlatformAdapterSetupError } from './AnalyticsPlatformAdapterSetupError.js';\n\n// Export types\nexport type {\n AnalyticsContext,\n AnalyticsEventProperties,\n AnalyticsDeliveryOptions,\n AnalyticsInvocationCallback,\n AnalyticsLocationContext,\n AnalyticsLocationField,\n AnalyticsUserTraits,\n AnalyticsPlatformAdapter,\n AnalyticsTrackingEvent,\n} from './AnalyticsPlatformAdapter.types.js';\n\n// Export event fragment types\nexport type {\n AnalyticsEventFragment,\n AnalyticsEventFragmentFinalizeOptions,\n AnalyticsEventFragmentOptions,\n AnalyticsEventFragmentPayload,\n AnalyticsEventFragments,\n ReadonlyAnalyticsEventFragment,\n} from './EventFragment.types.js';\n\n// Export state types\nexport type {\n AnalyticsControllerState,\n AnalyticsEventQueue,\n AnalyticsEventsConfig,\n AnalyticsQueuedEvent,\n AnalyticsQueuedEventType,\n AnalyticsQueuedTrackEvent,\n AnalyticsQueuedIdentifyEvent,\n AnalyticsQueuedViewEvent,\n} from './AnalyticsController.js';\n\n// Export selectors\nexport { analyticsControllerSelectors } from './selectors.js';\n\n// Export messenger types\nexport type { AnalyticsControllerMessenger } from './AnalyticsController.js';\n\n// Export action and event types\nexport type {\n AnalyticsControllerActions,\n AnalyticsControllerEvents,\n AnalyticsControllerGetStateAction,\n AnalyticsControllerStateChangeEvent,\n} from './AnalyticsController.js';\nexport type {\n AnalyticsControllerTrackEventAction,\n AnalyticsControllerTrackErrorAsAnalyticsAction,\n AnalyticsControllerIdentifyAction,\n AnalyticsControllerTrackViewAction,\n AnalyticsControllerOptInAction,\n AnalyticsControllerOptOutAction,\n AnalyticsControllerResetConsentDecisionAction,\n AnalyticsControllerOptInToMarketingAction,\n AnalyticsControllerOptOutOfMarketingAction,\n AnalyticsControllerResetMarketingConsentDecisionAction,\n AnalyticsControllerSetMarketingCampaignCookieIdAction,\n AnalyticsControllerCreateEventFragmentAction,\n AnalyticsControllerUpsertEventFragmentAction,\n AnalyticsControllerUpdateEventFragmentAction,\n AnalyticsControllerGetEventFragmentByIdAction,\n AnalyticsControllerDeleteEventFragmentAction,\n AnalyticsControllerFinalizeEventFragmentAction,\n AnalyticsControllerMethodActions,\n} from './AnalyticsController-method-action-types.js';\n"]}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { AnalyticsTrackingEvent } from './AnalyticsPlatformAdapter.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Analytics event emitted for a caught error.
|
|
4
|
+
*/
|
|
5
|
+
export declare const ERROR_OCCURRED_EVENT_NAME = "Error Occurred";
|
|
6
|
+
/**
|
|
7
|
+
* Default volume-control configuration for {@link reserveErrorOccurredEvent}.
|
|
8
|
+
*
|
|
9
|
+
* `samplingRate` is the probability from 0 to 1 that an individual event is emitted.
|
|
10
|
+
* `dedupWindowMs` suppresses a repeated signature inside this window.
|
|
11
|
+
* `sessionCap` is the maximum number of events emitted per controller instance.
|
|
12
|
+
* A caller-supplied cap above this default is clamped down to it.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEFAULT_ERROR_TRACKING_OPTIONS: {
|
|
15
|
+
readonly samplingRate: 1;
|
|
16
|
+
readonly dedupWindowMs: 10000;
|
|
17
|
+
readonly sessionCap: 50;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Volume-control knobs for {@link reserveErrorOccurredEvent}.
|
|
21
|
+
*/
|
|
22
|
+
export type TrackErrorVolumeControls = {
|
|
23
|
+
/**
|
|
24
|
+
* Per-event sampling rate between 0 and 1. 1 always emits.
|
|
25
|
+
*/
|
|
26
|
+
samplingRate?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Deduplication window in milliseconds. Events that share a signature
|
|
29
|
+
* inside this window are suppressed.
|
|
30
|
+
*/
|
|
31
|
+
dedupWindowMs?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Maximum number of events emitted for this controller instance.
|
|
34
|
+
* May only lower {@link DEFAULT_ERROR_TRACKING_OPTIONS.sessionCap}.
|
|
35
|
+
* Larger values are clamped so a caller cannot raise the session bound.
|
|
36
|
+
*/
|
|
37
|
+
sessionCap?: number;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Options for reporting a caught error as an `Error Occurred` event.
|
|
41
|
+
*/
|
|
42
|
+
export type TrackErrorAsAnalyticsOptions = {
|
|
43
|
+
/**
|
|
44
|
+
* Unique discriminator for this error case.
|
|
45
|
+
*
|
|
46
|
+
* Use one value per distinct failure circumstance (for example
|
|
47
|
+
* `swap-quote-fetch` or `clear-metametrics-data`), not a shared label for
|
|
48
|
+
* every error in a file or package.
|
|
49
|
+
*/
|
|
50
|
+
source: string;
|
|
51
|
+
/**
|
|
52
|
+
* Optional sampling, deduplication, and session-cap overrides.
|
|
53
|
+
*/
|
|
54
|
+
volumeControls?: TrackErrorVolumeControls;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* In-memory volume-control state for one controller instance.
|
|
58
|
+
*
|
|
59
|
+
* This is not persisted. A new controller starts with a fresh budget.
|
|
60
|
+
*/
|
|
61
|
+
export type ErrorTrackingSession = {
|
|
62
|
+
emittedCount: number;
|
|
63
|
+
lastEmittedAtBySignature: Map<string, number>;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Budget held for an event that has not yet been delivered.
|
|
67
|
+
*/
|
|
68
|
+
export type ErrorTrackingReservation = {
|
|
69
|
+
signature: string;
|
|
70
|
+
reservedAt: number;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* An event that passed volume controls, plus the reservation to release if
|
|
74
|
+
* delivery throws.
|
|
75
|
+
*/
|
|
76
|
+
export type ReservedErrorOccurredEvent = {
|
|
77
|
+
event: AnalyticsTrackingEvent;
|
|
78
|
+
reservation: ErrorTrackingReservation;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Applies volume controls and reserves session budget when the error should
|
|
82
|
+
* be emitted.
|
|
83
|
+
*
|
|
84
|
+
* The signature is an unambiguous JSON encoding of the event name, source,
|
|
85
|
+
* and sanitized message, so values that contain `:` cannot collide.
|
|
86
|
+
* Call {@link releaseErrorTrackingReservation} if delivery throws, so a
|
|
87
|
+
* failed report does not consume the cap or suppress the next attempt.
|
|
88
|
+
*
|
|
89
|
+
* @param session - Mutable per-controller volume-control state.
|
|
90
|
+
* @param error - The caught value to report.
|
|
91
|
+
* @param options - Source and optional volume controls.
|
|
92
|
+
* @param now - Current time in milliseconds. Defaults to `Date.now()`.
|
|
93
|
+
* @param randomSample - Sample in [0, 1). Defaults to `Math.random()`.
|
|
94
|
+
* @returns The event to send and its reservation, or `undefined` when the
|
|
95
|
+
* error is suppressed.
|
|
96
|
+
*/
|
|
97
|
+
export declare function reserveErrorOccurredEvent(session: ErrorTrackingSession, error: unknown, options: TrackErrorAsAnalyticsOptions, now?: number, randomSample?: number): ReservedErrorOccurredEvent | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Releases a reservation after delivery throws.
|
|
100
|
+
*
|
|
101
|
+
* The session budget is not consumed, and the next identical error is not
|
|
102
|
+
* suppressed by deduplication.
|
|
103
|
+
*
|
|
104
|
+
* @param session - Mutable per-controller volume-control state.
|
|
105
|
+
* @param reservation - The reservation returned with the event.
|
|
106
|
+
*/
|
|
107
|
+
export declare function releaseErrorTrackingReservation(session: ErrorTrackingSession, reservation: ErrorTrackingReservation): void;
|
|
108
|
+
//# sourceMappingURL=track-error-as-analytics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track-error-as-analytics.d.ts","sourceRoot":"","sources":["../src/track-error-as-analytics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAElF;;GAEG;AACH,eAAO,MAAM,yBAAyB,mBAAmB,CAAC;AAS1D;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B;aACzC,YAAY,EAAE,CAAC;aACf,aAAa,EAAE,KAAM;aACrB,UAAU,EAAE,EAAE;CACN,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,cAAc,CAAC,EAAE,wBAAwB,CAAC;CAC3C,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/C,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,sBAAsB,CAAC;IAC9B,WAAW,EAAE,wBAAwB,CAAC;CACvC,CAAC;AAyFF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,oBAAoB,EAC7B,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,4BAA4B,EACrC,GAAG,GAAE,MAAmB,EACxB,YAAY,GAAE,MAAsB,GACnC,0BAA0B,GAAG,SAAS,CA0DxC;AAED;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,oBAAoB,EAC7B,WAAW,EAAE,wBAAwB,GACpC,IAAI,CAQN"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics event emitted for a caught error.
|
|
3
|
+
*/
|
|
4
|
+
export const ERROR_OCCURRED_EVENT_NAME = 'Error Occurred';
|
|
5
|
+
/**
|
|
6
|
+
* Segment-safe cap for `error_message`, including the truncation ellipsis.
|
|
7
|
+
*/
|
|
8
|
+
const MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
9
|
+
const ELLIPSIS = '…';
|
|
10
|
+
/**
|
|
11
|
+
* Default volume-control configuration for {@link reserveErrorOccurredEvent}.
|
|
12
|
+
*
|
|
13
|
+
* `samplingRate` is the probability from 0 to 1 that an individual event is emitted.
|
|
14
|
+
* `dedupWindowMs` suppresses a repeated signature inside this window.
|
|
15
|
+
* `sessionCap` is the maximum number of events emitted per controller instance.
|
|
16
|
+
* A caller-supplied cap above this default is clamped down to it.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_ERROR_TRACKING_OPTIONS = {
|
|
19
|
+
samplingRate: 1,
|
|
20
|
+
dedupWindowMs: 10_000,
|
|
21
|
+
sessionCap: 50,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Normalizes whitespace and caps length for an analytics `error_message`.
|
|
25
|
+
*
|
|
26
|
+
* @param message - Raw error text.
|
|
27
|
+
* @returns Sanitized message safe to send in analytics.
|
|
28
|
+
*/
|
|
29
|
+
function sanitizeErrorMessage(message) {
|
|
30
|
+
const trimmed = message.replace(/\s+/gu, ' ').trim();
|
|
31
|
+
if (trimmed.length <= MAX_ERROR_MESSAGE_LENGTH) {
|
|
32
|
+
return trimmed;
|
|
33
|
+
}
|
|
34
|
+
return `${trimmed.slice(0, MAX_ERROR_MESSAGE_LENGTH - ELLIPSIS.length)}${ELLIPSIS}`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Normalizes any thrown value into a sanitized message.
|
|
38
|
+
*
|
|
39
|
+
* Stack traces are dropped. `null` and `undefined` become `Unknown error`.
|
|
40
|
+
*
|
|
41
|
+
* @param error - The caught value to normalize.
|
|
42
|
+
* @returns The sanitized error message.
|
|
43
|
+
*/
|
|
44
|
+
function normalizeError(error) {
|
|
45
|
+
if (error instanceof Error) {
|
|
46
|
+
return sanitizeErrorMessage(error.message);
|
|
47
|
+
}
|
|
48
|
+
if (error === null || error === undefined) {
|
|
49
|
+
return 'Unknown error';
|
|
50
|
+
}
|
|
51
|
+
if (typeof error === 'string') {
|
|
52
|
+
return sanitizeErrorMessage(error);
|
|
53
|
+
}
|
|
54
|
+
if (typeof error === 'object' &&
|
|
55
|
+
'message' in error &&
|
|
56
|
+
typeof error.message === 'string') {
|
|
57
|
+
return sanitizeErrorMessage(error.message);
|
|
58
|
+
}
|
|
59
|
+
if (typeof error === 'number') {
|
|
60
|
+
return sanitizeErrorMessage(error.toString());
|
|
61
|
+
}
|
|
62
|
+
return 'Unknown error';
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolves the sampling rate, clamping to [0, 1] and falling back to the
|
|
66
|
+
* default for non-finite values. A caller bug must not disable sampling:
|
|
67
|
+
* NaN comparisons are always false and would otherwise emit every event.
|
|
68
|
+
*
|
|
69
|
+
* @param samplingRate - Caller-supplied rate, or undefined for the default.
|
|
70
|
+
* @returns A finite sampling rate between 0 and 1.
|
|
71
|
+
*/
|
|
72
|
+
function resolveSamplingRate(samplingRate) {
|
|
73
|
+
const fallback = DEFAULT_ERROR_TRACKING_OPTIONS.samplingRate;
|
|
74
|
+
if (typeof samplingRate !== 'number' || !Number.isFinite(samplingRate)) {
|
|
75
|
+
return fallback;
|
|
76
|
+
}
|
|
77
|
+
return Math.min(Math.max(samplingRate, 0), 1);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Resolves a volume-control number, falling back to the default for
|
|
81
|
+
* non-finite or negative values.
|
|
82
|
+
*
|
|
83
|
+
* @param value - Caller-supplied value, or undefined for the default.
|
|
84
|
+
* @param fallback - Default used when the input is invalid.
|
|
85
|
+
* @returns A finite, non-negative number.
|
|
86
|
+
*/
|
|
87
|
+
function resolveNonNegativeNumber(value, fallback) {
|
|
88
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
|
|
89
|
+
return fallback;
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Applies volume controls and reserves session budget when the error should
|
|
95
|
+
* be emitted.
|
|
96
|
+
*
|
|
97
|
+
* The signature is an unambiguous JSON encoding of the event name, source,
|
|
98
|
+
* and sanitized message, so values that contain `:` cannot collide.
|
|
99
|
+
* Call {@link releaseErrorTrackingReservation} if delivery throws, so a
|
|
100
|
+
* failed report does not consume the cap or suppress the next attempt.
|
|
101
|
+
*
|
|
102
|
+
* @param session - Mutable per-controller volume-control state.
|
|
103
|
+
* @param error - The caught value to report.
|
|
104
|
+
* @param options - Source and optional volume controls.
|
|
105
|
+
* @param now - Current time in milliseconds. Defaults to `Date.now()`.
|
|
106
|
+
* @param randomSample - Sample in [0, 1). Defaults to `Math.random()`.
|
|
107
|
+
* @returns The event to send and its reservation, or `undefined` when the
|
|
108
|
+
* error is suppressed.
|
|
109
|
+
*/
|
|
110
|
+
export function reserveErrorOccurredEvent(session, error, options, now = Date.now(), randomSample = Math.random()) {
|
|
111
|
+
const { source } = options;
|
|
112
|
+
const volumeControls = options.volumeControls ?? {};
|
|
113
|
+
const samplingRate = resolveSamplingRate(volumeControls.samplingRate);
|
|
114
|
+
const dedupWindowMs = resolveNonNegativeNumber(volumeControls.dedupWindowMs, DEFAULT_ERROR_TRACKING_OPTIONS.dedupWindowMs);
|
|
115
|
+
// The global cap is a hard ceiling. A caller-supplied sessionCap may
|
|
116
|
+
// only lower it, never raise it.
|
|
117
|
+
const sessionCap = Math.min(resolveNonNegativeNumber(volumeControls.sessionCap, DEFAULT_ERROR_TRACKING_OPTIONS.sessionCap), DEFAULT_ERROR_TRACKING_OPTIONS.sessionCap);
|
|
118
|
+
if (session.emittedCount >= sessionCap) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
// Inclusive comparison so a rate of 0 never emits, including on a 0 draw.
|
|
122
|
+
if (randomSample >= samplingRate) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
const errorMessage = normalizeError(error);
|
|
126
|
+
const signature = JSON.stringify([
|
|
127
|
+
ERROR_OCCURRED_EVENT_NAME,
|
|
128
|
+
source,
|
|
129
|
+
errorMessage,
|
|
130
|
+
]);
|
|
131
|
+
const lastEmittedAt = session.lastEmittedAtBySignature.get(signature);
|
|
132
|
+
if (lastEmittedAt !== undefined && now - lastEmittedAt < dedupWindowMs) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
const reservedAt = now;
|
|
136
|
+
session.emittedCount += 1;
|
|
137
|
+
session.lastEmittedAtBySignature.set(signature, reservedAt);
|
|
138
|
+
return {
|
|
139
|
+
event: {
|
|
140
|
+
name: ERROR_OCCURRED_EVENT_NAME,
|
|
141
|
+
properties: {
|
|
142
|
+
source,
|
|
143
|
+
error_message: errorMessage,
|
|
144
|
+
},
|
|
145
|
+
sensitiveProperties: {},
|
|
146
|
+
saveDataRecording: false,
|
|
147
|
+
hasProperties: true,
|
|
148
|
+
},
|
|
149
|
+
reservation: {
|
|
150
|
+
signature,
|
|
151
|
+
reservedAt,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Releases a reservation after delivery throws.
|
|
157
|
+
*
|
|
158
|
+
* The session budget is not consumed, and the next identical error is not
|
|
159
|
+
* suppressed by deduplication.
|
|
160
|
+
*
|
|
161
|
+
* @param session - Mutable per-controller volume-control state.
|
|
162
|
+
* @param reservation - The reservation returned with the event.
|
|
163
|
+
*/
|
|
164
|
+
export function releaseErrorTrackingReservation(session, reservation) {
|
|
165
|
+
session.emittedCount = Math.max(0, session.emittedCount - 1);
|
|
166
|
+
if (session.lastEmittedAtBySignature.get(reservation.signature) ===
|
|
167
|
+
reservation.reservedAt) {
|
|
168
|
+
session.lastEmittedAtBySignature.delete(reservation.signature);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=track-error-as-analytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track-error-as-analytics.js","sourceRoot":"","sources":["../src/track-error-as-analytics.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,gBAAgB,CAAC;AAE1D;;GAEG;AACH,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAErC,MAAM,QAAQ,GAAG,GAAG,CAAC;AAErB;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG;IAC5C,YAAY,EAAE,CAAC;IACf,aAAa,EAAE,MAAM;IACrB,UAAU,EAAE,EAAE;CACN,CAAC;AAuEX;;;;;GAKG;AACH,SAAS,oBAAoB,CAAC,OAAe;IAC3C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACrD,IAAI,OAAO,CAAC,MAAM,IAAI,wBAAwB,EAAE,CAAC;QAC/C,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,wBAAwB,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,QAAQ,EAAE,CAAC;AACtF,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,cAAc,CAAC,KAAc;IACpC,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,oBAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC;IAED,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,eAAe,CAAC;IACzB,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,SAAS,IAAI,KAAK;QAClB,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,EACjC,CAAC;QACD,OAAO,oBAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,oBAAoB,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,eAAe,CAAC;AACzB,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,mBAAmB,CAAC,YAAqB;IAChD,MAAM,QAAQ,GAAG,8BAA8B,CAAC,YAAY,CAAC;IAC7D,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QACvE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAChD,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,wBAAwB,CAC/B,KAAyB,EACzB,QAAgB;IAEhB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACtE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,yBAAyB,CACvC,OAA6B,EAC7B,KAAc,EACd,OAAqC,EACrC,GAAG,GAAW,IAAI,CAAC,GAAG,EAAE,EACxB,YAAY,GAAW,IAAI,CAAC,MAAM,EAAE;IAEpC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC3B,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,EAAE,CAAC;IACpD,MAAM,YAAY,GAAG,mBAAmB,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IACtE,MAAM,aAAa,GAAG,wBAAwB,CAC5C,cAAc,CAAC,aAAa,EAC5B,8BAA8B,CAAC,aAAa,CAC7C,CAAC;IACF,qEAAqE;IACrE,iCAAiC;IACjC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CACzB,wBAAwB,CACtB,cAAc,CAAC,UAAU,EACzB,8BAA8B,CAAC,UAAU,CAC1C,EACD,8BAA8B,CAAC,UAAU,CAC1C,CAAC;IAEF,IAAI,OAAO,CAAC,YAAY,IAAI,UAAU,EAAE,CAAC;QACvC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,0EAA0E;IAC1E,IAAI,YAAY,IAAI,YAAY,EAAE,CAAC;QACjC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,YAAY,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAC/B,yBAAyB;QACzB,MAAM;QACN,YAAY;KACb,CAAC,CAAC;IACH,MAAM,aAAa,GAAG,OAAO,CAAC,wBAAwB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACtE,IAAI,aAAa,KAAK,SAAS,IAAI,GAAG,GAAG,aAAa,GAAG,aAAa,EAAE,CAAC;QACvE,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,UAAU,GAAG,GAAG,CAAC;IACvB,OAAO,CAAC,YAAY,IAAI,CAAC,CAAC;IAC1B,OAAO,CAAC,wBAAwB,CAAC,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAE5D,OAAO;QACL,KAAK,EAAE;YACL,IAAI,EAAE,yBAAyB;YAC/B,UAAU,EAAE;gBACV,MAAM;gBACN,aAAa,EAAE,YAAY;aAC5B;YACD,mBAAmB,EAAE,EAAE;YACvB,iBAAiB,EAAE,KAAK;YACxB,aAAa,EAAE,IAAI;SACpB;QACD,WAAW,EAAE;YACX,SAAS;YACT,UAAU;SACX;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,+BAA+B,CAC7C,OAA6B,EAC7B,WAAqC;IAErC,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC;IAC7D,IACE,OAAO,CAAC,wBAAwB,CAAC,GAAG,CAAC,WAAW,CAAC,SAAS,CAAC;QAC3D,WAAW,CAAC,UAAU,EACtB,CAAC;QACD,OAAO,CAAC,wBAAwB,CAAC,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IACjE,CAAC;AACH,CAAC","sourcesContent":["import type { AnalyticsTrackingEvent } from './AnalyticsPlatformAdapter.types.js';\n\n/**\n * Analytics event emitted for a caught error.\n */\nexport const ERROR_OCCURRED_EVENT_NAME = 'Error Occurred';\n\n/**\n * Segment-safe cap for `error_message`, including the truncation ellipsis.\n */\nconst MAX_ERROR_MESSAGE_LENGTH = 500;\n\nconst ELLIPSIS = '…';\n\n/**\n * Default volume-control configuration for {@link reserveErrorOccurredEvent}.\n *\n * `samplingRate` is the probability from 0 to 1 that an individual event is emitted.\n * `dedupWindowMs` suppresses a repeated signature inside this window.\n * `sessionCap` is the maximum number of events emitted per controller instance.\n * A caller-supplied cap above this default is clamped down to it.\n */\nexport const DEFAULT_ERROR_TRACKING_OPTIONS = {\n samplingRate: 1,\n dedupWindowMs: 10_000,\n sessionCap: 50,\n} as const;\n\n/**\n * Volume-control knobs for {@link reserveErrorOccurredEvent}.\n */\nexport type TrackErrorVolumeControls = {\n /**\n * Per-event sampling rate between 0 and 1. 1 always emits.\n */\n samplingRate?: number;\n\n /**\n * Deduplication window in milliseconds. Events that share a signature\n * inside this window are suppressed.\n */\n dedupWindowMs?: number;\n\n /**\n * Maximum number of events emitted for this controller instance.\n * May only lower {@link DEFAULT_ERROR_TRACKING_OPTIONS.sessionCap}.\n * Larger values are clamped so a caller cannot raise the session bound.\n */\n sessionCap?: number;\n};\n\n/**\n * Options for reporting a caught error as an `Error Occurred` event.\n */\nexport type TrackErrorAsAnalyticsOptions = {\n /**\n * Unique discriminator for this error case.\n *\n * Use one value per distinct failure circumstance (for example\n * `swap-quote-fetch` or `clear-metametrics-data`), not a shared label for\n * every error in a file or package.\n */\n source: string;\n\n /**\n * Optional sampling, deduplication, and session-cap overrides.\n */\n volumeControls?: TrackErrorVolumeControls;\n};\n\n/**\n * In-memory volume-control state for one controller instance.\n *\n * This is not persisted. A new controller starts with a fresh budget.\n */\nexport type ErrorTrackingSession = {\n emittedCount: number;\n lastEmittedAtBySignature: Map<string, number>;\n};\n\n/**\n * Budget held for an event that has not yet been delivered.\n */\nexport type ErrorTrackingReservation = {\n signature: string;\n reservedAt: number;\n};\n\n/**\n * An event that passed volume controls, plus the reservation to release if\n * delivery throws.\n */\nexport type ReservedErrorOccurredEvent = {\n event: AnalyticsTrackingEvent;\n reservation: ErrorTrackingReservation;\n};\n\n/**\n * Normalizes whitespace and caps length for an analytics `error_message`.\n *\n * @param message - Raw error text.\n * @returns Sanitized message safe to send in analytics.\n */\nfunction sanitizeErrorMessage(message: string): string {\n const trimmed = message.replace(/\\s+/gu, ' ').trim();\n if (trimmed.length <= MAX_ERROR_MESSAGE_LENGTH) {\n return trimmed;\n }\n\n return `${trimmed.slice(0, MAX_ERROR_MESSAGE_LENGTH - ELLIPSIS.length)}${ELLIPSIS}`;\n}\n\n/**\n * Normalizes any thrown value into a sanitized message.\n *\n * Stack traces are dropped. `null` and `undefined` become `Unknown error`.\n *\n * @param error - The caught value to normalize.\n * @returns The sanitized error message.\n */\nfunction normalizeError(error: unknown): string {\n if (error instanceof Error) {\n return sanitizeErrorMessage(error.message);\n }\n\n if (error === null || error === undefined) {\n return 'Unknown error';\n }\n\n if (typeof error === 'string') {\n return sanitizeErrorMessage(error);\n }\n\n if (\n typeof error === 'object' &&\n 'message' in error &&\n typeof error.message === 'string'\n ) {\n return sanitizeErrorMessage(error.message);\n }\n\n if (typeof error === 'number') {\n return sanitizeErrorMessage(error.toString());\n }\n\n return 'Unknown error';\n}\n\n/**\n * Resolves the sampling rate, clamping to [0, 1] and falling back to the\n * default for non-finite values. A caller bug must not disable sampling:\n * NaN comparisons are always false and would otherwise emit every event.\n *\n * @param samplingRate - Caller-supplied rate, or undefined for the default.\n * @returns A finite sampling rate between 0 and 1.\n */\nfunction resolveSamplingRate(samplingRate?: number): number {\n const fallback = DEFAULT_ERROR_TRACKING_OPTIONS.samplingRate;\n if (typeof samplingRate !== 'number' || !Number.isFinite(samplingRate)) {\n return fallback;\n }\n\n return Math.min(Math.max(samplingRate, 0), 1);\n}\n\n/**\n * Resolves a volume-control number, falling back to the default for\n * non-finite or negative values.\n *\n * @param value - Caller-supplied value, or undefined for the default.\n * @param fallback - Default used when the input is invalid.\n * @returns A finite, non-negative number.\n */\nfunction resolveNonNegativeNumber(\n value: number | undefined,\n fallback: number,\n): number {\n if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {\n return fallback;\n }\n\n return value;\n}\n\n/**\n * Applies volume controls and reserves session budget when the error should\n * be emitted.\n *\n * The signature is an unambiguous JSON encoding of the event name, source,\n * and sanitized message, so values that contain `:` cannot collide.\n * Call {@link releaseErrorTrackingReservation} if delivery throws, so a\n * failed report does not consume the cap or suppress the next attempt.\n *\n * @param session - Mutable per-controller volume-control state.\n * @param error - The caught value to report.\n * @param options - Source and optional volume controls.\n * @param now - Current time in milliseconds. Defaults to `Date.now()`.\n * @param randomSample - Sample in [0, 1). Defaults to `Math.random()`.\n * @returns The event to send and its reservation, or `undefined` when the\n * error is suppressed.\n */\nexport function reserveErrorOccurredEvent(\n session: ErrorTrackingSession,\n error: unknown,\n options: TrackErrorAsAnalyticsOptions,\n now: number = Date.now(),\n randomSample: number = Math.random(),\n): ReservedErrorOccurredEvent | undefined {\n const { source } = options;\n const volumeControls = options.volumeControls ?? {};\n const samplingRate = resolveSamplingRate(volumeControls.samplingRate);\n const dedupWindowMs = resolveNonNegativeNumber(\n volumeControls.dedupWindowMs,\n DEFAULT_ERROR_TRACKING_OPTIONS.dedupWindowMs,\n );\n // The global cap is a hard ceiling. A caller-supplied sessionCap may\n // only lower it, never raise it.\n const sessionCap = Math.min(\n resolveNonNegativeNumber(\n volumeControls.sessionCap,\n DEFAULT_ERROR_TRACKING_OPTIONS.sessionCap,\n ),\n DEFAULT_ERROR_TRACKING_OPTIONS.sessionCap,\n );\n\n if (session.emittedCount >= sessionCap) {\n return undefined;\n }\n\n // Inclusive comparison so a rate of 0 never emits, including on a 0 draw.\n if (randomSample >= samplingRate) {\n return undefined;\n }\n\n const errorMessage = normalizeError(error);\n const signature = JSON.stringify([\n ERROR_OCCURRED_EVENT_NAME,\n source,\n errorMessage,\n ]);\n const lastEmittedAt = session.lastEmittedAtBySignature.get(signature);\n if (lastEmittedAt !== undefined && now - lastEmittedAt < dedupWindowMs) {\n return undefined;\n }\n\n const reservedAt = now;\n session.emittedCount += 1;\n session.lastEmittedAtBySignature.set(signature, reservedAt);\n\n return {\n event: {\n name: ERROR_OCCURRED_EVENT_NAME,\n properties: {\n source,\n error_message: errorMessage,\n },\n sensitiveProperties: {},\n saveDataRecording: false,\n hasProperties: true,\n },\n reservation: {\n signature,\n reservedAt,\n },\n };\n}\n\n/**\n * Releases a reservation after delivery throws.\n *\n * The session budget is not consumed, and the next identical error is not\n * suppressed by deduplication.\n *\n * @param session - Mutable per-controller volume-control state.\n * @param reservation - The reservation returned with the event.\n */\nexport function releaseErrorTrackingReservation(\n session: ErrorTrackingSession,\n reservation: ErrorTrackingReservation,\n): void {\n session.emittedCount = Math.max(0, session.emittedCount - 1);\n if (\n session.lastEmittedAtBySignature.get(reservation.signature) ===\n reservation.reservedAt\n ) {\n session.lastEmittedAtBySignature.delete(reservation.signature);\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask-previews/analytics-controller",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0-preview-9a6250503",
|
|
4
4
|
"description": "Common Analytics controller for event tracking",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Ethereum",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"registry": "https://registry.npmjs.org/"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
|
+
"allow-scripts": "yarn workspace @metamask/core-monorepo run allow-scripts",
|
|
35
36
|
"build": "tsc --project tsconfig.build.json",
|
|
36
37
|
"build:all": "tsc --build tsconfig.build.json --verbose",
|
|
37
38
|
"build:clean": "yarn build:only-clean && yarn build",
|