remote-errors.ts
Bridges engine-side errors to the metagame analytics pipeline. Sole module that forwards render crashes, uncaught exceptions, unhandled promise rejections, and perf warnings to the player_events Supabase table via the injected trackEvent() batcher. Avoids importing metagame deps directly — receives trackEvent at boot to break the circular import.
Event types
| Event name | Source |
|---|---|
render_error | Sticker/canvas context loss, render pass crash (reportRenderError) |
global_error | Uncaught exceptions via window.onerror |
unhandled_rejection | Unhandled promise rejections via window.onunhandledrejection |
perf_warning | Sustained low FPS or canvas memory spikes (reportPerfWarning) |
Exports
| Symbol | Signature | Role |
|---|---|---|
initRemoteErrors | (trackFn) => void | Boot — install global listeners, capture device context, store the callback |
reportRenderError | (source, detail, extra?) => void | Push a render_error event |
reportPerfWarning | (source, detail, extra?) => void | Push a perf_warning event |
Boot — initRemoteErrors
Called once from App.tsx after analytics is ready. Stores the trackEvent callback in module-local _track. Captures device context once: ua, screen (WxH), dpr, mem (navigator.deviceMemory or 'unknown'), cores (navigator.hardwareConcurrency or 'unknown'). Registers window listeners for error and unhandledrejection.
Promise rejection events serialize reason.message || String(reason) and slice reason.stack to 500 chars.
Dedup
| Constant | Value | Effect |
|---|---|---|
DEDUP_INTERVAL_MS | 30_000 | Same key is dropped if last send was within 30s |
Dedup key: ${eventType}:${source||''}:${message||detail||''}. Stored in _sent: Map<string, number> (last send timestamp).
Map is capped at 200 entries; on overflow, entries older than now - DEDUP_INTERVAL_MS are pruned. No hard cap beyond pruning sweep.
Send path — _send
- Short-circuit if
_trackis null (not booted yet — safe to call from hot paths pre-boot). - Build dedup key, drop if recent.
- Record timestamp, prune map if oversized.
- Call
_track(eventType, { ..._deviceCtx, ...props, ts: ISOString }).
Device context is spread first, so per-event props can override (e.g. an event-level ua would win).
Hot-path safety
reportRenderError and reportPerfWarning are designed for render-loop and per-frame use — they no-op when not booted, dedupe identical messages, and never throw. Caller passes a stable source string for effective dedup.
EXTRACT-CANDIDATE
- Event-type list (
render_error,global_error,unhandled_rejection,perf_warning) is duplicated in the JSDoc header and again in event-type mapping in Supabase ingestion; a sharedRemoteErrorEventunion type would lock both ends. DEDUP_INTERVAL_MS = 30_000and the200-entry cap are unnamed magic numbers in the prune branch — extractDEDUP_MAP_MAX = 200.- Stack trace slice length (
500) is inline magic in theunhandledrejectionhandler — extractSTACK_TRACE_MAX_CHARS = 500. - Device-context capture (
ua,screen,dpr,mem,cores) duplicates similar logic likely present in analytics boot; consider a sharedcaptureDeviceContext()helper if a second caller appears. - The dedup key builder is implicit string concat — a
keyFor(eventType, props)helper would centralize the rule.