@replayio-app-building/session-recorder
v0.7.14
Published
Capture browser sessions and create Replay recordings via simulation
Readme
@replayio-app-building/session-recorder
Capture browser sessions and create Replay recordings via simulation. This package provides a client-side API for capturing DOM events in the browser and a server-side API for storing session data, triggering recording creation, and managing recordings.
Installation
npm install @replayio-app-building/session-recorderClient API
Import from @replayio-app-building/session-recorder.
Early initialization
The session recorder intercepts localStorage, indexedDB, fetch, and WebSocket via proxies. If your app reads from localStorage at module scope (e.g. to restore an auth token), those reads happen during JS module evaluation — before any function call in your entry file can run. To ensure the recorder captures these reads, import the init entry point before any application modules:
// main.ts — init MUST be the first import
import "@replayio-app-building/session-recorder/init";
import { store } from "./store"; // module-scope localStorage reads are now captured
import App from "./App";The init import installs all capture proxies as a side effect. You still call startSession() later to get a session handle.
startSession(): SessionHandle
Begins capturing DOM events (mouse, keyboard, scroll, resize, mutations) in the current browser window. Returns a handle for retrieving the captured data. Calls initializeCapture() internally if it hasn't been called yet (e.g. via the init import).
import "@replayio-app-building/session-recorder/init";
import { startSession } from "@replayio-app-building/session-recorder";
const session = startSession();
// ... user interacts with the page ...
const data = session.getSessionData();Returns: SessionHandle with:
sessionId— A UUID identifying this capture sessiongetSessionData()— Returns the capturedSimulationData(array ofSimulationPacketevents)
markEndpoint(): void
Marks the end of the session recording. When getSessionData() is called, only events captured before the endpoint are included. This is useful when the interesting part of a session ends before the page is closed — for example, after a form submission completes or a checkout flow finishes.
import { startSession, markEndpoint, uploadSession } from "@replayio-app-building/session-recorder";
const session = startSession();
// ... user completes the checkout flow ...
markEndpoint();
// ... page may continue with confirmation animations, etc. ...
const data = session.getSessionData(); // only includes events before markEndpoint()
await uploadSession(session.sessionId, data);If markEndpoint() is never called, getSessionData() returns all captured events as before. Calling markEndpoint() again moves the endpoint to the current position, so the last call wins.
uploadSession(sessionId, sessionData, endpoint?): Promise<{ session_id: string }>
Compresses and uploads captured session data to the server. The data is gzip-compressed before sending.
import "@replayio-app-building/session-recorder/init";
import { startSession, uploadSession } from "@replayio-app-building/session-recorder";
const session = startSession();
// ... capture interactions ...
const data = session.getSessionData();
const result = await uploadSession(session.sessionId, data);
console.log(result.session_id); // UUID of the stored sessionParameters:
sessionId— The session UUID (fromstartSession().sessionId)sessionData— The capturedSimulationDataarrayendpoint— Upload URL (default:"/api/upload-session")
Types
SessionHandle—{ sessionId: string; getSessionData: () => SimulationData }SimulationData— Array ofSimulationPacketeventsSimulationPacket— Individual captured DOM event
Server API
Import from @replayio-app-building/session-recorder/server. All handler factories return (req: Request) => Promise<Response> functions suitable for use in Netlify Functions v2, Cloudflare Workers, or any platform with Web API Request/Response.
Each factory accepts a sql parameter — a Neon-style tagged-template SQL function:
type SqlFunction = (
strings: TemplateStringsArray,
...values: unknown[]
) => Promise<unknown[]>;createUploadSessionHandler(options)
Handles POST requests to store captured session data. Accepts JSON or gzip-compressed binary payloads. Compresses data with gzip, uploads it to UploadThing, and stores the resulting URL in the session_recorder_data table. Requires UPLOADTHING_TOKEN environment variable.
import { createUploadSessionHandler } from "@replayio-app-building/session-recorder/server";
const handler = createUploadSessionHandler({ sql });Options:
sql— SQL functionauthenticate?—(req: Request) => Promise<boolean>— optional auth check
Request body (JSON): { session_id?: string, sessionData: unknown[] }
Request body (binary): gzip-compressed JSON with the same shape
Response: { session_id: string } (201)
createSessionRecordingHandler(options)
Handles POST requests to create a Replay recording from a stored session. Updates the session_recorder_data row status and dispatches the work to a background recording process.
When sessionRecorderUrl is provided, delegates recording to an external session recorder service (e.g. this app's deployed instance) via its /api/create-recording and /api/get-session endpoints.
import { createSessionRecordingHandler } from "@replayio-app-building/session-recorder/server";
const handler = createSessionRecordingHandler({
sql,
baseUrl: "https://my-app.netlify.app",
sessionRecorderUrl: "https://session-recorder-si6nol.netlify.app",
pollTimeout: 60000,
});Options:
sql— SQL functionbaseUrl— Base URL of the calling app (used for webhook URLs)authenticate?— Optional auth checkpollTimeout?— If set, the handler polls for completion up to this many milliseconds before returning. Without this, returns immediately with status201.sessionRecorderUrl?— Base URL of an external session recorder service. When set, recording is delegated to that service.
Request body: { session_id: string, check_description?: string }
session_id— The session UUID to create a recording fromcheck_description— Optional description of something that should be visible in both the original rrweb session data and the resulting simulation recording (e.g. a bug the user encountered). When provided, the recording pipeline verifies that the described behavior appears in the rrweb DOM snapshots and in the simulation recording. If the behavior is present in the rrweb data but missing from the simulation, a bug report is filed automatically.
Response: The session_recorder_data row as JSON (201 if returned immediately, 200 if poll completed)
External service protocol
When sessionRecorderUrl is provided, the handler:
- POSTs to
{sessionRecorderUrl}/api/create-recordingwith:{ "sessionId": "<uuid>", "sessionBlobUrl": "{baseUrl}/api/session-data/{sessionId}", "webhookUrl": "{baseUrl}/api/recording-complete?sessionId={sessionId}", "checkDescription": "<optional check_description from the request>" } - If
pollTimeoutis set, polls{sessionRecorderUrl}/api/get-session?sessionId={sessionId}every 2 seconds until the session reaches a terminal status or the timeout expires. - When the recording completes, the service calls the
webhookUrlwith the result.
createRecordingCompleteHandler(options)
Handles POST webhook callbacks when a recording finishes. Updates the session_recorder_data and recording_tasks tables.
import { createRecordingCompleteHandler } from "@replayio-app-building/session-recorder/server";
const handler = createRecordingCompleteHandler({ sql });Options:
sql— SQL functionauthenticate?— Optional auth check
Query parameter: sessionId (required) — the session ID
Request body:
{ status: "recorded", recordingId: "<replay-recording-id>" }— on success{ status: "failed", error: "message" }— on failure
If the session has a caller_webhook_url, the handler forwards the completion payload to that URL via POST (best-effort).
createRecordingsHandler(options)
Handles GET requests to list or retrieve recordings.
import { createRecordingsHandler } from "@replayio-app-building/session-recorder/server";
const handler = createRecordingsHandler({ sql });Options:
sql— SQL functionauthenticate?— Optional auth check
Routes:
GET /api/recordings— Lists all recordings (newest first) with data size infoGET /api/recordings/{sessionId}— Returns a single recording by session ID
createSessionDataHandler(options)
Handles GET requests to retrieve stored session data for a given session ID. Decompresses gzip data URLs before returning.
import { createSessionDataHandler } from "@replayio-app-building/session-recorder/server";
const handler = createSessionDataHandler({ sql });Options:
sql— SQL functionauthenticate?— Optional auth check
Route: GET /api/session-data/{sessionId}
Response: { session_id, data_url } — where data_url is a base64-encoded JSON data URL
Database Schema
The server handlers expect two tables: session_recorder_data and recording_tasks. Both
are operated on through the sql function you pass in, so both must exist in the consumer's
database — not just in the recording service.
session_recorder_data
| Column | Type | Description |
|--------|------|-------------|
| session_id | TEXT (PK) | UUID identifying the captured session |
| data_url | TEXT | UploadThing URL pointing to gzip-compressed session data |
| status | TEXT | 'pending', 'queued', 'processing', 'complete', or 'failed' |
| recording_id | TEXT | Replay recording ID (set when complete) |
| error_message | TEXT | Error details (set when failed) |
| check_description | TEXT | Optional description of expected behavior to verify in the recording |
| caller_webhook_url | TEXT | Webhook URL to notify when recording completes (set by external callers) |
| created_at | TIMESTAMPTZ | Row creation time |
| updated_at | TIMESTAMPTZ | Last update time |
recording_tasks
createSessionRecordingHandler inserts a row into this table when a recording is requested —
on both the local-pipeline path and the external-service path (sessionRecorderUrl) — and
createRecordingCompleteHandler updates it from the webhook. It must exist in the consumer's
database; without it, createSessionRecordingHandler fails with
relation "recording_tasks" does not exist.
| Column | Type | Description |
|--------|------|-------------|
| session_id | TEXT (PK) | UUID identifying the captured session |
| data_url | TEXT | UploadThing URL pointing to gzip-compressed session data |
| webhook_url | TEXT | Recording-complete webhook URL for this task |
| status | TEXT | 'pending', 'assigned', or 'failed' |
| created_at | TIMESTAMPTZ | Row creation time |
| updated_at | TIMESTAMPTZ | Last update time |
CREATE TABLE IF NOT EXISTS recording_tasks (
session_id TEXT PRIMARY KEY,
data_url TEXT,
webhook_url TEXT,
status TEXT NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);