Appearance
Architecture & Batching
Sending an HTTP request per click would overwhelm the backend and slow the browser, so the SDK batches analytics into single requests with a documented delivery contract.
1. Time-based batching (5 seconds)
Events queue in memory and flush in one request every 5 seconds. Under rapid interaction, 20 queued events trigger an immediate flush. Both constants are platform-fixed.
2. Retry-and-drop delivery
text
GIVEN a batch whose flush fails (network error or server error)
WHEN the failure occurs
THEN the batch retries on the next flush cycle, up to 3 attempts total,
with the queue capped at 100 events; after the third failed attempt
the batch is DROPPED (a warning is logged internally).Analytics never throws and never blocks the app. That is a guarantee. track() is synchronous and cannot fail; flush failures are internal and non-observable; worst case is documented data loss of a single batch after bounded retries, never a broken UI.
3. Best-effort unload flush
If a player triggers an event and closes the tab immediately, the 5-second timer would miss it. The SDK listens to pagehide and visibilitychange and flushes on unload, using the browser's beacon mechanism (sendBeacon, with a keep-alive fetch fallback), which is designed for exactly this case. This is best-effort: unload delivery is the browser's to guarantee, not the platform's; the retry-and-drop contract covers everything short of a hard unload.
4. Anonymous ingest policy
Anonymous events (no session) are accepted under the backend's anonymous-ingest policy:
- rate-limited per API key;
- payload cap ~32 KB per event;
- operator rule: never send PII in analytics payloads; the SDK does not scrub payloads; treat everything tracked as analytics-grade data only.
Error codes: deliberately none
Analytics defines no error codes, by design: track() never throws, and flush outcomes are internal. This is the one module with an explicitly empty error contract; common transport errors never surface here either (they are absorbed by the retry-and-drop policy above).
Lifecycle
Call sdk.analytics.destroy() when your app tears down (route unload in SPA tests, SSR shutdown): it stops the flush timer, removes the browser-event listeners, and performs a final flush. For deterministic testing, flush() is public; call it directly instead of waiting on the timer.