@tracelog/lib 2.9.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/browser/tracelog-shopify-pixel.iife.js +1 -1
- package/dist/browser/tracelog-shopify-pixel.iife.js.map +1 -1
- package/dist/browser/tracelog.esm.js +663 -451
- package/dist/browser/tracelog.esm.js.map +1 -1
- package/dist/browser/tracelog.js +2 -2
- package/dist/browser/tracelog.js.map +1 -1
- package/dist/pixel/index.cjs +2 -2
- package/dist/pixel/index.cjs.map +1 -1
- package/dist/pixel/index.js +2 -2
- package/dist/pixel/index.js.map +1 -1
- package/dist/public-api.cjs +2 -2
- package/dist/public-api.cjs.map +1 -1
- package/dist/public-api.d.mts +159 -7
- package/dist/public-api.d.ts +159 -7
- package/dist/public-api.js +2 -2
- package/dist/public-api.js.map +1 -1
- package/package.json +1 -1
package/dist/public-api.d.mts
CHANGED
|
@@ -301,6 +301,33 @@ interface CustomEventData {
|
|
|
301
301
|
/** Additional event metadata */
|
|
302
302
|
metadata?: Record<string, MetadataType> | Record<string, MetadataType>[];
|
|
303
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Optional flags for `tracelog.event()`.
|
|
306
|
+
*/
|
|
307
|
+
interface EventOptions {
|
|
308
|
+
/**
|
|
309
|
+
* If `true`, the event queue is flushed via `navigator.sendBeacon()`
|
|
310
|
+
* immediately after this event is tracked. The browser guarantees the
|
|
311
|
+
* request is queued for delivery even if the page is about to unload
|
|
312
|
+
* (e.g., `window.location.href = '/thanks'` right after tracking a
|
|
313
|
+
* purchase). Async fetch would be cancelled by the navigation;
|
|
314
|
+
* sendBeacon survives.
|
|
315
|
+
*
|
|
316
|
+
* Use for high-value events where loss is unacceptable (Purchase, Signup,
|
|
317
|
+
* AddPaymentInfo). The critical event plus any previously queued events
|
|
318
|
+
* are sent in a single request — this does not bypass the queue.
|
|
319
|
+
*
|
|
320
|
+
* **Limitations** (inherited from `sendBeacon`):
|
|
321
|
+
* - 64KB payload cap. If the combined queue + critical event exceeds it,
|
|
322
|
+
* the failed batch is persisted to localStorage and recovered on next
|
|
323
|
+
* `init()` via its idempotency token.
|
|
324
|
+
* - No retry on failure (fire-and-forget).
|
|
325
|
+
* - Custom headers are not applied (browser API limitation).
|
|
326
|
+
*
|
|
327
|
+
* @default false
|
|
328
|
+
*/
|
|
329
|
+
critical?: boolean;
|
|
330
|
+
}
|
|
304
331
|
/**
|
|
305
332
|
* Web performance metrics data
|
|
306
333
|
*/
|
|
@@ -318,6 +345,8 @@ interface ErrorData {
|
|
|
318
345
|
type: ErrorType;
|
|
319
346
|
/** Error message text */
|
|
320
347
|
message: string;
|
|
348
|
+
/** Error constructor name (TypeError, ReferenceError, etc.) when available */
|
|
349
|
+
name?: string;
|
|
321
350
|
/** Source file where error occurred */
|
|
322
351
|
filename?: string;
|
|
323
352
|
/** Line number in source file */
|
|
@@ -406,6 +435,22 @@ interface EventData {
|
|
|
406
435
|
/** Campaign tracking parameters */
|
|
407
436
|
utm?: UTM;
|
|
408
437
|
}
|
|
438
|
+
/**
|
|
439
|
+
* Internal queue entry: an `EventData` enriched with the session ID frozen at
|
|
440
|
+
* `track()` time. Survives session renewal — when the user is idle past the
|
|
441
|
+
* timeout, `state.sessionId` is nulled but events already in the queue keep
|
|
442
|
+
* their original `_session_id`, so `EventManager.buildBatchesWithIds()` can
|
|
443
|
+
* still attribute them correctly instead of emitting `session_id: null` to the
|
|
444
|
+
* wire.
|
|
445
|
+
*
|
|
446
|
+
* **Internal only.** The leading underscore mirrors `EventsQueue._metadata` —
|
|
447
|
+
* this field is stripped from `events[]` at batch construction time, since the
|
|
448
|
+
* backend's `EventDto` uses `forbidNonWhitelisted: true` and the wrapper
|
|
449
|
+
* `EventsQueue.session_id` is the contract.
|
|
450
|
+
*/
|
|
451
|
+
interface QueuedEvent extends EventData {
|
|
452
|
+
_session_id: string;
|
|
453
|
+
}
|
|
409
454
|
|
|
410
455
|
/**
|
|
411
456
|
* Web Vitals filtering mode
|
|
@@ -449,6 +494,24 @@ interface Config {
|
|
|
449
494
|
webVitalsThresholds?: Partial<Record<WebVitalType, number>>;
|
|
450
495
|
/** Interval in milliseconds between event batch sends. @default 10000 (10 seconds) */
|
|
451
496
|
sendIntervalMs?: number;
|
|
497
|
+
/**
|
|
498
|
+
* Opt-in: when `true`, the event queue is flushed after every SPA navigation
|
|
499
|
+
* (`pushState`, `replaceState`, `popstate`, `hashchange`). Defaults to `false`
|
|
500
|
+
* because per-route flushing can multiply request volume on SPA-heavy apps
|
|
501
|
+
* (one request per route change vs. one per `sendIntervalMs`). Enable only
|
|
502
|
+
* if you need delivery between route changes that's faster than
|
|
503
|
+
* `sendIntervalMs`; `flushOnPageHidden` (default `true`) already covers the
|
|
504
|
+
* common tab-close / app-background case on every stack. No-op for MPAs.
|
|
505
|
+
* @default false
|
|
506
|
+
*/
|
|
507
|
+
flushOnSpaNavigation?: boolean;
|
|
508
|
+
/**
|
|
509
|
+
* If true, the event queue is flushed when `document.hidden` becomes `true`
|
|
510
|
+
* (tab switch, lock screen, app backgrounding). Especially relevant on mobile Safari
|
|
511
|
+
* where `pagehide`/`beforeunload` may not fire reliably.
|
|
512
|
+
* @default true
|
|
513
|
+
*/
|
|
514
|
+
flushOnPageHidden?: boolean;
|
|
452
515
|
/** Optional configuration for third-party integrations. */
|
|
453
516
|
integrations?: {
|
|
454
517
|
/** TraceLog integration options. */
|
|
@@ -1320,6 +1383,7 @@ declare class EventManager extends StateManager {
|
|
|
1320
1383
|
private rateLimitCounter;
|
|
1321
1384
|
private rateLimitWindowStart;
|
|
1322
1385
|
private lastSessionId;
|
|
1386
|
+
private pendingSyncFlush;
|
|
1323
1387
|
private sessionEventCounts;
|
|
1324
1388
|
private readonly saveSessionCountsDebounced;
|
|
1325
1389
|
/**
|
|
@@ -1477,7 +1541,10 @@ declare class EventManager extends StateManager {
|
|
|
1477
1541
|
* **Note**: For page unload, use `flushImmediatelySync()` instead,
|
|
1478
1542
|
* which uses `sendBeacon()` for guaranteed delivery.
|
|
1479
1543
|
*
|
|
1480
|
-
* @returns Promise resolving to `true` if
|
|
1544
|
+
* @returns Promise resolving to `true` if at least one integration accepted
|
|
1545
|
+
* the batch during this call (optimistic removal — failures
|
|
1546
|
+
* persist per-integration for retry). `false` if no events, all
|
|
1547
|
+
* senders failed, or a flush is already in flight.
|
|
1481
1548
|
*
|
|
1482
1549
|
* @example
|
|
1483
1550
|
* ```typescript
|
|
@@ -1513,7 +1580,14 @@ declare class EventManager extends StateManager {
|
|
|
1513
1580
|
* - No retry on failure (sendBeacon is fire-and-forget)
|
|
1514
1581
|
* - 64KB payload limit (large batches may be truncated)
|
|
1515
1582
|
*
|
|
1516
|
-
*
|
|
1583
|
+
* **In-flight contract**: if an async send is already running this call is
|
|
1584
|
+
* deferred (queued for replay in the async send's `finally` block) and
|
|
1585
|
+
* returns `false` — nothing has been delivered yet at the point of return.
|
|
1586
|
+
* Mirrors `flushImmediately()`'s behaviour for the same condition.
|
|
1587
|
+
*
|
|
1588
|
+
* @returns `true` if at least one integration accepted the beacon batch
|
|
1589
|
+
* *during this call*, `false` otherwise (no events, all senders
|
|
1590
|
+
* failed, or the call was deferred behind an in-flight async send)
|
|
1517
1591
|
*
|
|
1518
1592
|
* @example
|
|
1519
1593
|
* ```typescript
|
|
@@ -1628,9 +1702,79 @@ declare class EventManager extends StateManager {
|
|
|
1628
1702
|
flushPendingEvents(): void;
|
|
1629
1703
|
private clearSendTimeout;
|
|
1630
1704
|
private isSuccessfulResult;
|
|
1705
|
+
/**
|
|
1706
|
+
* Groups the queue by frozen `_session_id`, preserving insertion order.
|
|
1707
|
+
* Single pass — `buildBatchesWithIds()` builds one batch + one eventIds list
|
|
1708
|
+
* per group, so the grouping cost is O(N) per flush regardless of session
|
|
1709
|
+
* count.
|
|
1710
|
+
*
|
|
1711
|
+
* **Self-heal**: any entry missing `_session_id` (an internal invariant
|
|
1712
|
+
* violation — `buildEventPayload` always stamps it) is removed from the
|
|
1713
|
+
* queue rather than left behind, otherwise a single corrupted entry would
|
|
1714
|
+
* keep `eventsQueue.length > 0` forever and re-trigger periodic sends.
|
|
1715
|
+
*/
|
|
1716
|
+
private groupQueuedEventsBySession;
|
|
1717
|
+
/**
|
|
1718
|
+
* Builds a parallel list of `(batch, eventIds)` for sending. The eventIds are
|
|
1719
|
+
* the original `_session_id`-tagged event IDs in the queue that map to this
|
|
1720
|
+
* batch — used for optimistic removal. We can't read them off the wrapper's
|
|
1721
|
+
* `events[]` because dedup may have removed some signatures.
|
|
1722
|
+
*/
|
|
1723
|
+
private buildBatchesWithIds;
|
|
1631
1724
|
private flushEvents;
|
|
1725
|
+
/**
|
|
1726
|
+
* Reconciles the periodic send timer after a flush attempt. Clears the
|
|
1727
|
+
* timer when the queue is empty, otherwise (re)schedules a retry tick.
|
|
1728
|
+
*
|
|
1729
|
+
* **Why**: a `flushImmediately()` / `flushImmediatelySync()` call where all
|
|
1730
|
+
* integrations fail leaves events in `eventsQueue` for retry. The periodic
|
|
1731
|
+
* timer is the safety net that drains them when the backend recovers — if
|
|
1732
|
+
* we cleared it unconditionally here, the queue would sit untouched until
|
|
1733
|
+
* the next tracked event resurrects the timer in `addToQueue`. Mirrors the
|
|
1734
|
+
* pattern in `sendEventsQueue()` (the periodic path).
|
|
1735
|
+
*/
|
|
1736
|
+
private settleSendTimeout;
|
|
1737
|
+
/**
|
|
1738
|
+
* Re-runs a sync flush that was deferred while an async send was in flight.
|
|
1739
|
+
*
|
|
1740
|
+
* Called from the `finally` blocks of `flushEvents(false)` and
|
|
1741
|
+
* `sendEventsQueue()`. If `pendingSyncFlush` is set, clears the flag and
|
|
1742
|
+
* invokes `flushImmediatelySync()` synchronously so any events that arrived
|
|
1743
|
+
* after the deferred sync call are delivered before the next event loop
|
|
1744
|
+
* tick. Critical for high-stakes events tracked mid-async-send.
|
|
1745
|
+
*/
|
|
1746
|
+
private drainPendingSyncFlush;
|
|
1747
|
+
/**
|
|
1748
|
+
* Sends one batch synchronously across all integrations (sendBeacon path).
|
|
1749
|
+
* Optimistic removal: if any integration succeeds, we remove the batch's
|
|
1750
|
+
* events from the queue and emit it locally. Failures persist per-integration.
|
|
1751
|
+
*/
|
|
1752
|
+
private sendBatchSync;
|
|
1753
|
+
/**
|
|
1754
|
+
* Sends one batch asynchronously across all integrations (fetch path).
|
|
1755
|
+
*/
|
|
1756
|
+
private sendBatchAsync;
|
|
1632
1757
|
private sendEventsQueue;
|
|
1633
|
-
|
|
1758
|
+
/**
|
|
1759
|
+
* Builds a single batch from a per-session group: dedup by signature,
|
|
1760
|
+
* SESSION_START first, then timestamp order, strip `_session_id`, apply
|
|
1761
|
+
* `beforeBatch` transformer when running standalone.
|
|
1762
|
+
*
|
|
1763
|
+
* **Why N batches per flush**: events freeze their `_session_id` at `track()`
|
|
1764
|
+
* time. If the session was renewed (idle timeout) between two `track()`
|
|
1765
|
+
* calls, the queue contains events from multiple sessions. `buildBatchesWithIds()`
|
|
1766
|
+
* emits one batch per session so the backend's `EventsQueueDto.session_id`
|
|
1767
|
+
* remains the single source of truth and stays consistent with the events it
|
|
1768
|
+
* carries.
|
|
1769
|
+
*
|
|
1770
|
+
* **Strip**: `_session_id` is removed from each event in the wrapper's
|
|
1771
|
+
* `events[]` because the backend uses `forbidNonWhitelisted: true` and would
|
|
1772
|
+
* reject the batch if the field leaked through.
|
|
1773
|
+
*
|
|
1774
|
+
* **Transformer note**: `beforeBatch` is invoked **once per session-batch**,
|
|
1775
|
+
* not once per flush. A queue spanning N sessions triggers N invocations.
|
|
1776
|
+
*/
|
|
1777
|
+
private buildBatchFromGroup;
|
|
1634
1778
|
private buildEventPayload;
|
|
1635
1779
|
private isDuplicateEvent;
|
|
1636
1780
|
private pruneOldFingerprints;
|
|
@@ -2333,6 +2477,11 @@ type BeforeSendTransformer = (event: EventData) => EventData | null;
|
|
|
2333
2477
|
* Transform entire batch before sending to backend.
|
|
2334
2478
|
* Applied per-batch (10s interval or 50 events), can filter by returning null.
|
|
2335
2479
|
*
|
|
2480
|
+
* **Multi-batch flushes**: when the queue spans more than one session (e.g.
|
|
2481
|
+
* after an idle-timeout renewal), one flush produces one batch per session,
|
|
2482
|
+
* and this transformer is invoked **once per session-batch**. Avoid stateful
|
|
2483
|
+
* transformers that assume a single invocation per flush.
|
|
2484
|
+
*
|
|
2336
2485
|
* @param batch - The batch to transform
|
|
2337
2486
|
* @returns Transformed batch or null to filter
|
|
2338
2487
|
*/
|
|
@@ -2384,8 +2533,8 @@ interface TraceLogTestBridge {
|
|
|
2384
2533
|
readonly initialized: boolean;
|
|
2385
2534
|
init(config?: Config): Promise<InitResult>;
|
|
2386
2535
|
destroy(force?: boolean): void;
|
|
2387
|
-
sendCustomEvent(name: string, data?: Record<string, unknown> | Record<string, unknown>[]): void;
|
|
2388
|
-
event(name: string, metadata?: Record<string, unknown> | Record<string, unknown>[]): void;
|
|
2536
|
+
sendCustomEvent(name: string, data?: Record<string, unknown> | Record<string, unknown>[], options?: EventOptions): void;
|
|
2537
|
+
event(name: string, metadata?: Record<string, unknown> | Record<string, unknown>[], options?: EventOptions): void;
|
|
2389
2538
|
on(event: string, callback: (data: any) => void): void;
|
|
2390
2539
|
off(event: string, callback: (data: any) => void): void;
|
|
2391
2540
|
get<T extends keyof State>(key: T): State[T];
|
|
@@ -2572,7 +2721,7 @@ declare const getWebVitalsThresholds: (mode?: WebVitalsMode) => Record<WebVitalT
|
|
|
2572
2721
|
|
|
2573
2722
|
declare const tracelog: {
|
|
2574
2723
|
init: (config?: Config) => Promise<InitResult>;
|
|
2575
|
-
event: (name: string, metadata?: Record<string, MetadataType> | Record<string, MetadataType>[]) => void;
|
|
2724
|
+
event: (name: string, metadata?: Record<string, MetadataType> | Record<string, MetadataType>[], options?: EventOptions) => void;
|
|
2576
2725
|
on: <K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>) => void;
|
|
2577
2726
|
off: <K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>) => void;
|
|
2578
2727
|
setTransformer: typeof setTransformer;
|
|
@@ -2581,12 +2730,15 @@ declare const tracelog: {
|
|
|
2581
2730
|
removeCustomHeaders: () => void;
|
|
2582
2731
|
isInitialized: () => boolean;
|
|
2583
2732
|
getSessionId: () => string | null;
|
|
2733
|
+
getUserId: () => string | null;
|
|
2584
2734
|
destroy: () => void;
|
|
2585
2735
|
setQaMode: (enabled: boolean) => void;
|
|
2586
2736
|
updateGlobalMetadata: (metadata: Record<string, MetadataType>) => void;
|
|
2587
2737
|
mergeGlobalMetadata: (metadata: Record<string, MetadataType>) => void;
|
|
2588
2738
|
identify: (userId: string, traits?: Record<string, string>) => void;
|
|
2589
2739
|
resetIdentity: () => Promise<void>;
|
|
2740
|
+
flushImmediately: () => Promise<boolean>;
|
|
2741
|
+
flushImmediatelySync: () => boolean;
|
|
2590
2742
|
};
|
|
2591
2743
|
|
|
2592
|
-
export { AppConfigValidationError, type BeforeBatchTransformer, type BeforeSendTransformer, type ClickCoordinates, type ClickData, type ClickTrackingElementData, type Config, type CustomEventData, type CustomHeadersProvider, DEFAULT_SESSION_TIMEOUT, DEFAULT_WEB_VITALS_MODE, type DeviceInfo, DeviceType, type EmitterCallback, EmitterEvent, type EmitterMap, type ErrorData, ErrorType, type EventData, EventType, type EventTypeName, type EventsQueue, type IdentifyData, type InitResult, InitializationTimeoutError, IntegrationValidationError, MAX_ARRAY_LENGTH, MAX_CUSTOM_EVENT_ARRAY_SIZE, MAX_CUSTOM_EVENT_KEYS, MAX_CUSTOM_EVENT_NAME_LENGTH, MAX_CUSTOM_EVENT_STRING_SIZE, MAX_NESTED_OBJECT_KEYS, MAX_STRING_LENGTH, MAX_STRING_LENGTH_IN_ARRAY, type MetadataType, Mode, PII_PATTERNS, type PageViewData, PermanentError, type PersistedEventsQueue, type PrimaryScrollEvent, type QueueMetadata, RateLimitError, SamplingRateValidationError, type ScrollData, ScrollDirection, type SecondaryScrollEvent, type SessionEventCounts, SessionTimeoutValidationError, SpecialApiUrl, type State, TimeoutError, type TraceLogTestBridge, TraceLogValidationError, type TransformerHook, type TransformerMap, type UTM, type ViewportConfig, type ViewportElement, type ViewportEventData, WEB_VITALS_GOOD_THRESHOLDS, WEB_VITALS_NEEDS_IMPROVEMENT_THRESHOLDS, WEB_VITALS_POOR_THRESHOLDS, type WebVitalType, type WebVitalsData, type WebVitalsMode, getWebVitalsThresholds, isPrimaryScrollEvent, isSecondaryScrollEvent, tracelog };
|
|
2744
|
+
export { AppConfigValidationError, type BeforeBatchTransformer, type BeforeSendTransformer, type ClickCoordinates, type ClickData, type ClickTrackingElementData, type Config, type CustomEventData, type CustomHeadersProvider, DEFAULT_SESSION_TIMEOUT, DEFAULT_WEB_VITALS_MODE, type DeviceInfo, DeviceType, type EmitterCallback, EmitterEvent, type EmitterMap, type ErrorData, ErrorType, type EventData, type EventOptions, EventType, type EventTypeName, type EventsQueue, type IdentifyData, type InitResult, InitializationTimeoutError, IntegrationValidationError, MAX_ARRAY_LENGTH, MAX_CUSTOM_EVENT_ARRAY_SIZE, MAX_CUSTOM_EVENT_KEYS, MAX_CUSTOM_EVENT_NAME_LENGTH, MAX_CUSTOM_EVENT_STRING_SIZE, MAX_NESTED_OBJECT_KEYS, MAX_STRING_LENGTH, MAX_STRING_LENGTH_IN_ARRAY, type MetadataType, Mode, PII_PATTERNS, type PageViewData, PermanentError, type PersistedEventsQueue, type PrimaryScrollEvent, type QueueMetadata, type QueuedEvent, RateLimitError, SamplingRateValidationError, type ScrollData, ScrollDirection, type SecondaryScrollEvent, type SessionEventCounts, SessionTimeoutValidationError, SpecialApiUrl, type State, TimeoutError, type TraceLogTestBridge, TraceLogValidationError, type TransformerHook, type TransformerMap, type UTM, type ViewportConfig, type ViewportElement, type ViewportEventData, WEB_VITALS_GOOD_THRESHOLDS, WEB_VITALS_NEEDS_IMPROVEMENT_THRESHOLDS, WEB_VITALS_POOR_THRESHOLDS, type WebVitalType, type WebVitalsData, type WebVitalsMode, getWebVitalsThresholds, isPrimaryScrollEvent, isSecondaryScrollEvent, tracelog };
|
package/dist/public-api.d.ts
CHANGED
|
@@ -301,6 +301,33 @@ interface CustomEventData {
|
|
|
301
301
|
/** Additional event metadata */
|
|
302
302
|
metadata?: Record<string, MetadataType> | Record<string, MetadataType>[];
|
|
303
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Optional flags for `tracelog.event()`.
|
|
306
|
+
*/
|
|
307
|
+
interface EventOptions {
|
|
308
|
+
/**
|
|
309
|
+
* If `true`, the event queue is flushed via `navigator.sendBeacon()`
|
|
310
|
+
* immediately after this event is tracked. The browser guarantees the
|
|
311
|
+
* request is queued for delivery even if the page is about to unload
|
|
312
|
+
* (e.g., `window.location.href = '/thanks'` right after tracking a
|
|
313
|
+
* purchase). Async fetch would be cancelled by the navigation;
|
|
314
|
+
* sendBeacon survives.
|
|
315
|
+
*
|
|
316
|
+
* Use for high-value events where loss is unacceptable (Purchase, Signup,
|
|
317
|
+
* AddPaymentInfo). The critical event plus any previously queued events
|
|
318
|
+
* are sent in a single request — this does not bypass the queue.
|
|
319
|
+
*
|
|
320
|
+
* **Limitations** (inherited from `sendBeacon`):
|
|
321
|
+
* - 64KB payload cap. If the combined queue + critical event exceeds it,
|
|
322
|
+
* the failed batch is persisted to localStorage and recovered on next
|
|
323
|
+
* `init()` via its idempotency token.
|
|
324
|
+
* - No retry on failure (fire-and-forget).
|
|
325
|
+
* - Custom headers are not applied (browser API limitation).
|
|
326
|
+
*
|
|
327
|
+
* @default false
|
|
328
|
+
*/
|
|
329
|
+
critical?: boolean;
|
|
330
|
+
}
|
|
304
331
|
/**
|
|
305
332
|
* Web performance metrics data
|
|
306
333
|
*/
|
|
@@ -318,6 +345,8 @@ interface ErrorData {
|
|
|
318
345
|
type: ErrorType;
|
|
319
346
|
/** Error message text */
|
|
320
347
|
message: string;
|
|
348
|
+
/** Error constructor name (TypeError, ReferenceError, etc.) when available */
|
|
349
|
+
name?: string;
|
|
321
350
|
/** Source file where error occurred */
|
|
322
351
|
filename?: string;
|
|
323
352
|
/** Line number in source file */
|
|
@@ -406,6 +435,22 @@ interface EventData {
|
|
|
406
435
|
/** Campaign tracking parameters */
|
|
407
436
|
utm?: UTM;
|
|
408
437
|
}
|
|
438
|
+
/**
|
|
439
|
+
* Internal queue entry: an `EventData` enriched with the session ID frozen at
|
|
440
|
+
* `track()` time. Survives session renewal — when the user is idle past the
|
|
441
|
+
* timeout, `state.sessionId` is nulled but events already in the queue keep
|
|
442
|
+
* their original `_session_id`, so `EventManager.buildBatchesWithIds()` can
|
|
443
|
+
* still attribute them correctly instead of emitting `session_id: null` to the
|
|
444
|
+
* wire.
|
|
445
|
+
*
|
|
446
|
+
* **Internal only.** The leading underscore mirrors `EventsQueue._metadata` —
|
|
447
|
+
* this field is stripped from `events[]` at batch construction time, since the
|
|
448
|
+
* backend's `EventDto` uses `forbidNonWhitelisted: true` and the wrapper
|
|
449
|
+
* `EventsQueue.session_id` is the contract.
|
|
450
|
+
*/
|
|
451
|
+
interface QueuedEvent extends EventData {
|
|
452
|
+
_session_id: string;
|
|
453
|
+
}
|
|
409
454
|
|
|
410
455
|
/**
|
|
411
456
|
* Web Vitals filtering mode
|
|
@@ -449,6 +494,24 @@ interface Config {
|
|
|
449
494
|
webVitalsThresholds?: Partial<Record<WebVitalType, number>>;
|
|
450
495
|
/** Interval in milliseconds between event batch sends. @default 10000 (10 seconds) */
|
|
451
496
|
sendIntervalMs?: number;
|
|
497
|
+
/**
|
|
498
|
+
* Opt-in: when `true`, the event queue is flushed after every SPA navigation
|
|
499
|
+
* (`pushState`, `replaceState`, `popstate`, `hashchange`). Defaults to `false`
|
|
500
|
+
* because per-route flushing can multiply request volume on SPA-heavy apps
|
|
501
|
+
* (one request per route change vs. one per `sendIntervalMs`). Enable only
|
|
502
|
+
* if you need delivery between route changes that's faster than
|
|
503
|
+
* `sendIntervalMs`; `flushOnPageHidden` (default `true`) already covers the
|
|
504
|
+
* common tab-close / app-background case on every stack. No-op for MPAs.
|
|
505
|
+
* @default false
|
|
506
|
+
*/
|
|
507
|
+
flushOnSpaNavigation?: boolean;
|
|
508
|
+
/**
|
|
509
|
+
* If true, the event queue is flushed when `document.hidden` becomes `true`
|
|
510
|
+
* (tab switch, lock screen, app backgrounding). Especially relevant on mobile Safari
|
|
511
|
+
* where `pagehide`/`beforeunload` may not fire reliably.
|
|
512
|
+
* @default true
|
|
513
|
+
*/
|
|
514
|
+
flushOnPageHidden?: boolean;
|
|
452
515
|
/** Optional configuration for third-party integrations. */
|
|
453
516
|
integrations?: {
|
|
454
517
|
/** TraceLog integration options. */
|
|
@@ -1320,6 +1383,7 @@ declare class EventManager extends StateManager {
|
|
|
1320
1383
|
private rateLimitCounter;
|
|
1321
1384
|
private rateLimitWindowStart;
|
|
1322
1385
|
private lastSessionId;
|
|
1386
|
+
private pendingSyncFlush;
|
|
1323
1387
|
private sessionEventCounts;
|
|
1324
1388
|
private readonly saveSessionCountsDebounced;
|
|
1325
1389
|
/**
|
|
@@ -1477,7 +1541,10 @@ declare class EventManager extends StateManager {
|
|
|
1477
1541
|
* **Note**: For page unload, use `flushImmediatelySync()` instead,
|
|
1478
1542
|
* which uses `sendBeacon()` for guaranteed delivery.
|
|
1479
1543
|
*
|
|
1480
|
-
* @returns Promise resolving to `true` if
|
|
1544
|
+
* @returns Promise resolving to `true` if at least one integration accepted
|
|
1545
|
+
* the batch during this call (optimistic removal — failures
|
|
1546
|
+
* persist per-integration for retry). `false` if no events, all
|
|
1547
|
+
* senders failed, or a flush is already in flight.
|
|
1481
1548
|
*
|
|
1482
1549
|
* @example
|
|
1483
1550
|
* ```typescript
|
|
@@ -1513,7 +1580,14 @@ declare class EventManager extends StateManager {
|
|
|
1513
1580
|
* - No retry on failure (sendBeacon is fire-and-forget)
|
|
1514
1581
|
* - 64KB payload limit (large batches may be truncated)
|
|
1515
1582
|
*
|
|
1516
|
-
*
|
|
1583
|
+
* **In-flight contract**: if an async send is already running this call is
|
|
1584
|
+
* deferred (queued for replay in the async send's `finally` block) and
|
|
1585
|
+
* returns `false` — nothing has been delivered yet at the point of return.
|
|
1586
|
+
* Mirrors `flushImmediately()`'s behaviour for the same condition.
|
|
1587
|
+
*
|
|
1588
|
+
* @returns `true` if at least one integration accepted the beacon batch
|
|
1589
|
+
* *during this call*, `false` otherwise (no events, all senders
|
|
1590
|
+
* failed, or the call was deferred behind an in-flight async send)
|
|
1517
1591
|
*
|
|
1518
1592
|
* @example
|
|
1519
1593
|
* ```typescript
|
|
@@ -1628,9 +1702,79 @@ declare class EventManager extends StateManager {
|
|
|
1628
1702
|
flushPendingEvents(): void;
|
|
1629
1703
|
private clearSendTimeout;
|
|
1630
1704
|
private isSuccessfulResult;
|
|
1705
|
+
/**
|
|
1706
|
+
* Groups the queue by frozen `_session_id`, preserving insertion order.
|
|
1707
|
+
* Single pass — `buildBatchesWithIds()` builds one batch + one eventIds list
|
|
1708
|
+
* per group, so the grouping cost is O(N) per flush regardless of session
|
|
1709
|
+
* count.
|
|
1710
|
+
*
|
|
1711
|
+
* **Self-heal**: any entry missing `_session_id` (an internal invariant
|
|
1712
|
+
* violation — `buildEventPayload` always stamps it) is removed from the
|
|
1713
|
+
* queue rather than left behind, otherwise a single corrupted entry would
|
|
1714
|
+
* keep `eventsQueue.length > 0` forever and re-trigger periodic sends.
|
|
1715
|
+
*/
|
|
1716
|
+
private groupQueuedEventsBySession;
|
|
1717
|
+
/**
|
|
1718
|
+
* Builds a parallel list of `(batch, eventIds)` for sending. The eventIds are
|
|
1719
|
+
* the original `_session_id`-tagged event IDs in the queue that map to this
|
|
1720
|
+
* batch — used for optimistic removal. We can't read them off the wrapper's
|
|
1721
|
+
* `events[]` because dedup may have removed some signatures.
|
|
1722
|
+
*/
|
|
1723
|
+
private buildBatchesWithIds;
|
|
1631
1724
|
private flushEvents;
|
|
1725
|
+
/**
|
|
1726
|
+
* Reconciles the periodic send timer after a flush attempt. Clears the
|
|
1727
|
+
* timer when the queue is empty, otherwise (re)schedules a retry tick.
|
|
1728
|
+
*
|
|
1729
|
+
* **Why**: a `flushImmediately()` / `flushImmediatelySync()` call where all
|
|
1730
|
+
* integrations fail leaves events in `eventsQueue` for retry. The periodic
|
|
1731
|
+
* timer is the safety net that drains them when the backend recovers — if
|
|
1732
|
+
* we cleared it unconditionally here, the queue would sit untouched until
|
|
1733
|
+
* the next tracked event resurrects the timer in `addToQueue`. Mirrors the
|
|
1734
|
+
* pattern in `sendEventsQueue()` (the periodic path).
|
|
1735
|
+
*/
|
|
1736
|
+
private settleSendTimeout;
|
|
1737
|
+
/**
|
|
1738
|
+
* Re-runs a sync flush that was deferred while an async send was in flight.
|
|
1739
|
+
*
|
|
1740
|
+
* Called from the `finally` blocks of `flushEvents(false)` and
|
|
1741
|
+
* `sendEventsQueue()`. If `pendingSyncFlush` is set, clears the flag and
|
|
1742
|
+
* invokes `flushImmediatelySync()` synchronously so any events that arrived
|
|
1743
|
+
* after the deferred sync call are delivered before the next event loop
|
|
1744
|
+
* tick. Critical for high-stakes events tracked mid-async-send.
|
|
1745
|
+
*/
|
|
1746
|
+
private drainPendingSyncFlush;
|
|
1747
|
+
/**
|
|
1748
|
+
* Sends one batch synchronously across all integrations (sendBeacon path).
|
|
1749
|
+
* Optimistic removal: if any integration succeeds, we remove the batch's
|
|
1750
|
+
* events from the queue and emit it locally. Failures persist per-integration.
|
|
1751
|
+
*/
|
|
1752
|
+
private sendBatchSync;
|
|
1753
|
+
/**
|
|
1754
|
+
* Sends one batch asynchronously across all integrations (fetch path).
|
|
1755
|
+
*/
|
|
1756
|
+
private sendBatchAsync;
|
|
1632
1757
|
private sendEventsQueue;
|
|
1633
|
-
|
|
1758
|
+
/**
|
|
1759
|
+
* Builds a single batch from a per-session group: dedup by signature,
|
|
1760
|
+
* SESSION_START first, then timestamp order, strip `_session_id`, apply
|
|
1761
|
+
* `beforeBatch` transformer when running standalone.
|
|
1762
|
+
*
|
|
1763
|
+
* **Why N batches per flush**: events freeze their `_session_id` at `track()`
|
|
1764
|
+
* time. If the session was renewed (idle timeout) between two `track()`
|
|
1765
|
+
* calls, the queue contains events from multiple sessions. `buildBatchesWithIds()`
|
|
1766
|
+
* emits one batch per session so the backend's `EventsQueueDto.session_id`
|
|
1767
|
+
* remains the single source of truth and stays consistent with the events it
|
|
1768
|
+
* carries.
|
|
1769
|
+
*
|
|
1770
|
+
* **Strip**: `_session_id` is removed from each event in the wrapper's
|
|
1771
|
+
* `events[]` because the backend uses `forbidNonWhitelisted: true` and would
|
|
1772
|
+
* reject the batch if the field leaked through.
|
|
1773
|
+
*
|
|
1774
|
+
* **Transformer note**: `beforeBatch` is invoked **once per session-batch**,
|
|
1775
|
+
* not once per flush. A queue spanning N sessions triggers N invocations.
|
|
1776
|
+
*/
|
|
1777
|
+
private buildBatchFromGroup;
|
|
1634
1778
|
private buildEventPayload;
|
|
1635
1779
|
private isDuplicateEvent;
|
|
1636
1780
|
private pruneOldFingerprints;
|
|
@@ -2333,6 +2477,11 @@ type BeforeSendTransformer = (event: EventData) => EventData | null;
|
|
|
2333
2477
|
* Transform entire batch before sending to backend.
|
|
2334
2478
|
* Applied per-batch (10s interval or 50 events), can filter by returning null.
|
|
2335
2479
|
*
|
|
2480
|
+
* **Multi-batch flushes**: when the queue spans more than one session (e.g.
|
|
2481
|
+
* after an idle-timeout renewal), one flush produces one batch per session,
|
|
2482
|
+
* and this transformer is invoked **once per session-batch**. Avoid stateful
|
|
2483
|
+
* transformers that assume a single invocation per flush.
|
|
2484
|
+
*
|
|
2336
2485
|
* @param batch - The batch to transform
|
|
2337
2486
|
* @returns Transformed batch or null to filter
|
|
2338
2487
|
*/
|
|
@@ -2384,8 +2533,8 @@ interface TraceLogTestBridge {
|
|
|
2384
2533
|
readonly initialized: boolean;
|
|
2385
2534
|
init(config?: Config): Promise<InitResult>;
|
|
2386
2535
|
destroy(force?: boolean): void;
|
|
2387
|
-
sendCustomEvent(name: string, data?: Record<string, unknown> | Record<string, unknown>[]): void;
|
|
2388
|
-
event(name: string, metadata?: Record<string, unknown> | Record<string, unknown>[]): void;
|
|
2536
|
+
sendCustomEvent(name: string, data?: Record<string, unknown> | Record<string, unknown>[], options?: EventOptions): void;
|
|
2537
|
+
event(name: string, metadata?: Record<string, unknown> | Record<string, unknown>[], options?: EventOptions): void;
|
|
2389
2538
|
on(event: string, callback: (data: any) => void): void;
|
|
2390
2539
|
off(event: string, callback: (data: any) => void): void;
|
|
2391
2540
|
get<T extends keyof State>(key: T): State[T];
|
|
@@ -2572,7 +2721,7 @@ declare const getWebVitalsThresholds: (mode?: WebVitalsMode) => Record<WebVitalT
|
|
|
2572
2721
|
|
|
2573
2722
|
declare const tracelog: {
|
|
2574
2723
|
init: (config?: Config) => Promise<InitResult>;
|
|
2575
|
-
event: (name: string, metadata?: Record<string, MetadataType> | Record<string, MetadataType>[]) => void;
|
|
2724
|
+
event: (name: string, metadata?: Record<string, MetadataType> | Record<string, MetadataType>[], options?: EventOptions) => void;
|
|
2576
2725
|
on: <K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>) => void;
|
|
2577
2726
|
off: <K extends keyof EmitterMap>(event: K, callback: EmitterCallback<EmitterMap[K]>) => void;
|
|
2578
2727
|
setTransformer: typeof setTransformer;
|
|
@@ -2581,12 +2730,15 @@ declare const tracelog: {
|
|
|
2581
2730
|
removeCustomHeaders: () => void;
|
|
2582
2731
|
isInitialized: () => boolean;
|
|
2583
2732
|
getSessionId: () => string | null;
|
|
2733
|
+
getUserId: () => string | null;
|
|
2584
2734
|
destroy: () => void;
|
|
2585
2735
|
setQaMode: (enabled: boolean) => void;
|
|
2586
2736
|
updateGlobalMetadata: (metadata: Record<string, MetadataType>) => void;
|
|
2587
2737
|
mergeGlobalMetadata: (metadata: Record<string, MetadataType>) => void;
|
|
2588
2738
|
identify: (userId: string, traits?: Record<string, string>) => void;
|
|
2589
2739
|
resetIdentity: () => Promise<void>;
|
|
2740
|
+
flushImmediately: () => Promise<boolean>;
|
|
2741
|
+
flushImmediatelySync: () => boolean;
|
|
2590
2742
|
};
|
|
2591
2743
|
|
|
2592
|
-
export { AppConfigValidationError, type BeforeBatchTransformer, type BeforeSendTransformer, type ClickCoordinates, type ClickData, type ClickTrackingElementData, type Config, type CustomEventData, type CustomHeadersProvider, DEFAULT_SESSION_TIMEOUT, DEFAULT_WEB_VITALS_MODE, type DeviceInfo, DeviceType, type EmitterCallback, EmitterEvent, type EmitterMap, type ErrorData, ErrorType, type EventData, EventType, type EventTypeName, type EventsQueue, type IdentifyData, type InitResult, InitializationTimeoutError, IntegrationValidationError, MAX_ARRAY_LENGTH, MAX_CUSTOM_EVENT_ARRAY_SIZE, MAX_CUSTOM_EVENT_KEYS, MAX_CUSTOM_EVENT_NAME_LENGTH, MAX_CUSTOM_EVENT_STRING_SIZE, MAX_NESTED_OBJECT_KEYS, MAX_STRING_LENGTH, MAX_STRING_LENGTH_IN_ARRAY, type MetadataType, Mode, PII_PATTERNS, type PageViewData, PermanentError, type PersistedEventsQueue, type PrimaryScrollEvent, type QueueMetadata, RateLimitError, SamplingRateValidationError, type ScrollData, ScrollDirection, type SecondaryScrollEvent, type SessionEventCounts, SessionTimeoutValidationError, SpecialApiUrl, type State, TimeoutError, type TraceLogTestBridge, TraceLogValidationError, type TransformerHook, type TransformerMap, type UTM, type ViewportConfig, type ViewportElement, type ViewportEventData, WEB_VITALS_GOOD_THRESHOLDS, WEB_VITALS_NEEDS_IMPROVEMENT_THRESHOLDS, WEB_VITALS_POOR_THRESHOLDS, type WebVitalType, type WebVitalsData, type WebVitalsMode, getWebVitalsThresholds, isPrimaryScrollEvent, isSecondaryScrollEvent, tracelog };
|
|
2744
|
+
export { AppConfigValidationError, type BeforeBatchTransformer, type BeforeSendTransformer, type ClickCoordinates, type ClickData, type ClickTrackingElementData, type Config, type CustomEventData, type CustomHeadersProvider, DEFAULT_SESSION_TIMEOUT, DEFAULT_WEB_VITALS_MODE, type DeviceInfo, DeviceType, type EmitterCallback, EmitterEvent, type EmitterMap, type ErrorData, ErrorType, type EventData, type EventOptions, EventType, type EventTypeName, type EventsQueue, type IdentifyData, type InitResult, InitializationTimeoutError, IntegrationValidationError, MAX_ARRAY_LENGTH, MAX_CUSTOM_EVENT_ARRAY_SIZE, MAX_CUSTOM_EVENT_KEYS, MAX_CUSTOM_EVENT_NAME_LENGTH, MAX_CUSTOM_EVENT_STRING_SIZE, MAX_NESTED_OBJECT_KEYS, MAX_STRING_LENGTH, MAX_STRING_LENGTH_IN_ARRAY, type MetadataType, Mode, PII_PATTERNS, type PageViewData, PermanentError, type PersistedEventsQueue, type PrimaryScrollEvent, type QueueMetadata, type QueuedEvent, RateLimitError, SamplingRateValidationError, type ScrollData, ScrollDirection, type SecondaryScrollEvent, type SessionEventCounts, SessionTimeoutValidationError, SpecialApiUrl, type State, TimeoutError, type TraceLogTestBridge, TraceLogValidationError, type TransformerHook, type TransformerMap, type UTM, type ViewportConfig, type ViewportElement, type ViewportEventData, WEB_VITALS_GOOD_THRESHOLDS, WEB_VITALS_NEEDS_IMPROVEMENT_THRESHOLDS, WEB_VITALS_POOR_THRESHOLDS, type WebVitalType, type WebVitalsData, type WebVitalsMode, getWebVitalsThresholds, isPrimaryScrollEvent, isSecondaryScrollEvent, tracelog };
|