Summary
The app currently logs performance and error events ad-hoc with no batching, compression, or delivery guarantee. On slow connections these fire-and-forget fetches compete with API calls and hurt page load times. This issue implements a dedicated telemetry pipeline that runs entirely off the critical path using a Web Worker, batches events every 5 seconds or at 50 events, compresses payloads with CompressionStream, and ships them via navigator.sendBeacon for guaranteed delivery even during page unload.
Scope
1. Telemetry event schema
- Define a typed union
TelemetryEvent:
PageLoad { page, ttfb, dcl, lcp, fid, cls, loadComplete }
ApiCall { endpoint, method, durationMs, statusCode, cacheHit }
SigningStep { step, durationMs, signerType }
ComponentError { componentName, errorMessage, errorStack, buildId }
UserAction { action, target, metadata }
- Every event includes:
sessionId, walletAddress (null if unauthenticated), buildId, timestamp, sequence (monotonically incrementing per session)
2. Web Worker pipeline
- All telemetry processing runs in a dedicated Web Worker (
telemetry.worker.ts) to never block the main thread
- The main thread posts events to the worker via
postMessage; the worker batches them in memory
- The worker flushes every 5 seconds OR when the batch reaches 50 events, whichever comes first
- On flush, the worker serialises the batch to JSON, compresses it with
CompressionStream (gzip), and sends it via fetch to POST /telemetry with Content-Encoding: gzip
3. Page unload guarantee
- Register a
visibilitychange listener on the main thread; when document.visibilityState === 'hidden', immediately post a FLUSH_AND_BEACON message to the worker
- The worker serialises the current batch and sends it via
navigator.sendBeacon (which survives page unload) instead of fetch
sendBeacon payload must be a Blob with type: 'application/octet-stream'; server must handle gzip-encoded beacon payloads
4. Sequence gap detection and replay
- The worker stores the last successfully delivered sequence number in a shared
SharedArrayBuffer
- On the next flush, the batch header includes
{ lastDeliveredSequence, currentBatchStartSequence } so the server can detect gaps
- If a flush fails (network error), the worker retains the batch and prepends it to the next flush (at-least-once delivery)
- Retained batches are capped at 500 events; older events are dropped with a
DroppedEvents sentinel entry
5. React integration
- Expose a
useTelemetry() hook that returns { track(event: TelemetryEvent): void }
track posts to the worker with zero synchronous work on the main thread
- Automatically instrument: React error boundaries (emit
ComponentError), React Query response times (emit ApiCall), and navigation events (emit PageLoad using Navigation Timing API)
6. Tests
- Unit tests: worker batches correctly at 50 events and 5 second interval
- Unit tests:
FLUSH_AND_BEACON path serialises and sends via sendBeacon
- Unit tests: sequence gap correctly reported in batch header after a failed flush
- Performance test:
track() call completes in under 0.1ms on the main thread (no synchronous work)
Acceptance Criteria
ETA: 24 hours
Coordinate on Telegram
Summary
The app currently logs performance and error events ad-hoc with no batching, compression, or delivery guarantee. On slow connections these fire-and-forget fetches compete with API calls and hurt page load times. This issue implements a dedicated telemetry pipeline that runs entirely off the critical path using a Web Worker, batches events every 5 seconds or at 50 events, compresses payloads with CompressionStream, and ships them via
navigator.sendBeaconfor guaranteed delivery even during page unload.Scope
1. Telemetry event schema
TelemetryEvent:PageLoad { page, ttfb, dcl, lcp, fid, cls, loadComplete }ApiCall { endpoint, method, durationMs, statusCode, cacheHit }SigningStep { step, durationMs, signerType }ComponentError { componentName, errorMessage, errorStack, buildId }UserAction { action, target, metadata }sessionId,walletAddress(null if unauthenticated),buildId,timestamp,sequence(monotonically incrementing per session)2. Web Worker pipeline
telemetry.worker.ts) to never block the main threadpostMessage; the worker batches them in memoryCompressionStream(gzip), and sends it viafetchtoPOST /telemetrywithContent-Encoding: gzip3. Page unload guarantee
visibilitychangelistener on the main thread; whendocument.visibilityState === 'hidden', immediately post aFLUSH_AND_BEACONmessage to the workernavigator.sendBeacon(which survives page unload) instead offetchsendBeaconpayload must be aBlobwithtype: 'application/octet-stream'; server must handle gzip-encoded beacon payloads4. Sequence gap detection and replay
SharedArrayBuffer{ lastDeliveredSequence, currentBatchStartSequence }so the server can detect gapsDroppedEventssentinel entry5. React integration
useTelemetry()hook that returns{ track(event: TelemetryEvent): void }trackposts to the worker with zero synchronous work on the main threadComponentError), React Query response times (emitApiCall), and navigation events (emitPageLoadusing Navigation Timing API)6. Tests
FLUSH_AND_BEACONpath serialises and sends viasendBeacontrack()call completes in under 0.1ms on the main thread (no synchronous work)Acceptance Criteria
CompressionStreambefore every flushsendBeaconfor guaranteed deliverytrack()completes in under 0.1ms on the main threadETA: 24 hours
Coordinate on Telegram