type ReplayPlatform = "web" | "react" | "nextjs" | "react_native" | "android" | "ios"; type ReplayEventType = "session_start" | "session_end" | "full_snapshot" | "incremental_snapshot" | "input" | "pointer" | "scroll" | "viewport" | "navigation" | "console" | "network" | "error" | "performance" | "custom" | "tap" | "native_snapshot"; interface ReplayBatchEnvelope { projectId?: string; sessionId: string; segmentId: string; sequence: number; sentAt: number; sdk: ReplaySdkDescriptor; page: ReplayPageContext; events: ReplayEvent[]; } interface ReplaySdkDescriptor { name: string; version: string; platform: ReplayPlatform; /** Web build / revision id (ReplayConfig.revId) — scopes funnels + * session search to a specific deployed build. */ revId?: string; } interface ReplayPageContext { url: string; title?: string; referrer?: string; userAgent: string; viewport: ViewportDimensions; timezone?: string; language?: string; screen?: ViewportDimensions; } interface ViewportDimensions { width: number; height: number; } interface ReplayEvent { id: string; ts: number; offsetMs: number; type: ReplayEventType; source: ReplayPlatform; data: TData; } interface SessionStartEventData { href: string; path: string; referrer: string; /** document.title of the entry page — the first screen's human name. */ title?: string; } interface SessionEndEventData { reason: "manual" | "unload" | "visibility_hidden" | "inactivity"; } interface SnapshotEventData { recorder: "rrweb"; rrwebEvent: unknown; } interface ConsoleEventData { level: "log" | "info" | "warn" | "error" | "debug"; message: string; args: unknown[]; stack?: string; } interface NetworkEventData { requestId: string; transport: "fetch" | "xhr" | "beacon"; method: string; url: string; statusCode?: number; startedAt: number; endedAt?: number; durationMs?: number; ok?: boolean; requestHeaders?: Record; responseHeaders?: Record; requestBody?: string; responseBody?: string; error?: string; /** navigator.connection.rtt at the time of the request. */ connectionRtt?: number; /** navigator.connection.effectiveType ("4g", "3g", "slow-2g"…). */ connectionEffectiveType?: string; } /** * One parsed stack frame. Produced by `error-stack-parser-es` from the * raw `Error.stack` string, normalised to a platform-neutral shape (the * native SDKs emit the same fields from their own symbolicated traces). * * `frames[0]` (the throw site) is the primary input to structured * Issue fingerprinting — grouping on structured frames is far more * stable than hashing the raw stack string, which drifts with source * maps, minified names, and line-number churn. */ interface StackFrame { functionName?: string; fileName?: string; lineNumber?: number; columnNumber?: number; } interface ErrorEventData { /** Error class/type — "TypeError", "ReferenceError", or the value's * constructor name. Primary Issue-grouping key. */ name?: string; message: string; /** Raw stack string — kept for replay display and as a fallback when * structured parsing fails. */ stack?: string; /** Structured stack frames parsed from `stack`. Top frame drives the * fingerprint. Empty when the throw carried no usable stack. */ frames?: StackFrame[]; kind: "error" | "unhandledrejection"; /** True when reported via the public captureException (a developer-CAUGHT * error) rather than an uncaught window error/rejection. Undefined/false on * the auto paths. The backend classifies a handled error as an "exception" * (vs an uncaught "crash"), mirroring the mobile `fatal` discriminator. */ handled?: boolean; /** Optional contextual tags a caller attaches to a captureException — e.g. a * React error boundary's componentStack, the active route, or a feature flag. * Small key/value map; sharpens dashboard grouping + triage. */ metadata?: Record; } interface NavigationEventData { from?: string; to: string; /** document.title at the time of the view — a human screen name for the * dashboard's Screens list, not just an opaque URL. */ title?: string; trigger: "pushState" | "replaceState" | "popstate" | "hashchange" | "load" | "screen_appeared" | "screen_dismissed" | "deep_link"; } /** * One captured tap/gesture on a native (iOS/Android/Flutter/RN) screen. * * Carries the metadata the player needs to render a tap marker AND * the widget identity for heatmaps / funnels. `uiId` is a stable hash * of `route + class + value` (djb2) so taps on the same logical button * across millions of sessions collapse to one bucket. * * Sensitive views (any ancestor marked occluded) ship with blanked * `uiValue`/`uiId`/`uiClass` and `isSensitive: true` — the dashboard * still shows that a tap happened, just not what was on the button. */ interface TapEventData { bounds: { x: number; y: number; w: number; h: number; }; point: { x: number; y: number; }; route: string; uiClass: string; uiType: "button" | "field" | "compound" | "text" | "image" | "container" | "unknown"; uiValue: string; uiId: string; isSensitive: boolean; } /** * View-tree snapshot emitted on screen transition or after a 500ms * idle. NOT every frame — the dashboard renders interpolated playback * from `tap` events plus these sparse snapshots — the standard native * session-replay approach (orders of magnitude smaller than rrweb's * frame-diff stream). * * `nodes` is the rendered view tree, depth-first. Image-heavy nodes * carry an `imageRef` (hash); the actual bytes are uploaded * out-of-band via the snapshot-asset endpoint and the player resolves * the ref at playback time. */ interface NativeSnapshotEventData { recorder: "native"; width: number; height: number; pixelRatio: number; trigger: "screen_appeared" | "idle" | "tap" | "manual"; root: NativeViewNode; } /** * Recursive view-tree node. Identical shape across iOS UIView, * Android View, Flutter Widget — the SDK serializes its native tree * into this shape so the player has ONE renderer. * * Keep this lean — at 10k views per screen, every extra field * multiplies payload size. */ interface NativeViewNode { id: string; type: "button" | "field" | "compound" | "text" | "image" | "container" | "unknown"; className?: string; bounds: { x: number; y: number; w: number; h: number; }; text?: string; imageRef?: string; backgroundColor?: string; opacity?: number; occluded?: boolean; ariaLabel?: string; children?: NativeViewNode[]; } /** * Performance metrics — web and native both ship as `performance` * events. Metric NAMES differ by platform; the envelope is identical. * * Web metrics: lcp, cls, inp, fcp, ttfb, long_task, * long_animation_frame, page_load, resource, memory * Native metrics: cold_start_ms, time_to_first_meaningful_render_ms, * tap_response_ms, first_network_ttfb_ms, frozen_frame_count, * memory_rss_mb, anr_count, frame_drop_pct, thermal_state, * battery_drain_pct_per_min * * Rating thresholds and capture cadence are documented in * docs/mobile-vitals-matrix.md. */ interface PerformanceEventData { metric: string; value: number; unit: string; rating?: "good" | "needs-improvement" | "poor"; kind?: string; detail?: Record; } interface ViewportEventData { width: number; height: number; } interface ReplayPrivacyConfig { maskAllInputs?: boolean; maskTextSelector?: string; blockSelector?: string; redactUrls?: Array; captureRequestHeaders?: boolean; captureResponseHeaders?: boolean; } interface ReplayIngestResponse { accepted: boolean; acceptedSequence: number; sessionId: string; } interface ReplaySessionSummary { sessionId: string; projectId?: string; platform: ReplayPlatform; sdkName: string; sdkVersion: string; startedAt: number; endedAt: number; eventCount: number; pageUrl: string; distinctId?: string; status?: "LIVE" | "COMPLETED"; durationMs?: number; } interface ReplaySessionDetail extends ReplaySessionSummary { segments: ReplaySegmentSummary[]; } interface ReplayProjectSummary { id: string; slug: string; name: string; retentionDays: number; samplingRate: number; createdAt: string; sessionsLast24h: number; apiKeys: ReplayApiKeySummary[]; } interface ReplayApiKeySummary { id: string; label: string; prefix: string; createdAt: string; revokedAt?: string; lastUsed?: string; } interface ReplayProjectionLogRow { sessionId: string; projectId: string; sequence: number; eventId: string; eventType: ReplayEventType; timestamp: number; offsetMs: number; kind: "console" | "network" | "error"; level?: string; message?: string; method?: string; url?: string; statusCode?: number; durationMs?: number; error?: string; stack?: string; } interface ReplaySegmentSummary { sessionId: string; segmentId: string; sequence: number; eventCount: number; storageKey: string; startedAt: number; endedAt: number; } export type { ConsoleEventData, ErrorEventData, NativeSnapshotEventData, NativeViewNode, NavigationEventData, NetworkEventData, PerformanceEventData, ReplayApiKeySummary, ReplayBatchEnvelope, ReplayEvent, ReplayEventType, ReplayIngestResponse, ReplayPageContext, ReplayPlatform, ReplayPrivacyConfig, ReplayProjectSummary, ReplayProjectionLogRow, ReplaySdkDescriptor, ReplaySegmentSummary, ReplaySessionDetail, ReplaySessionSummary, SessionEndEventData, SessionStartEventData, SnapshotEventData, StackFrame, TapEventData, ViewportDimensions, ViewportEventData };