Skip to content

Implement a client-side observability pipeline that batches, compresses, and ships structured performance and error telemetry to the server with zero impact on the critical rendering path #756

Description

@Chucks1093

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

  • All telemetry processing in a Web Worker — zero synchronous work on main thread
  • Batch flushed every 5 seconds or at 50 events
  • Gzip compression applied via CompressionStream before every flush
  • Page unload path uses sendBeacon for guaranteed delivery
  • Failed flushes retained and prepended to next batch (at-least-once)
  • track() completes in under 0.1ms on the main thread

ETA: 24 hours


Coordinate on Telegram

Metadata

Metadata

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third Campaign

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions