brawsr TypeScript SDK
The official Node.js client for brawsr sessions, checkpoints, rewind, fork, and CDP
handoff. It turns asynchronous API operations into one bounded method call; it
does not bundle or proxy a browser library.
Published on npm as @brawsr/sdk. Requires Node.js 22+ and uses its built-in
fetch implementation.
This is a server-side SDK. Never expose a brawsr API key in browser code, a
public environment variable, or a client-side JavaScript bundle.
Install
npm install @brawsr/sdk
Install your browser client separately, for example:
npm install playwright-core
npm install puppeteer-core
Session and browser connection
import { chromium, type Browser } from "playwright-core";
import { BrawsrClient, BrawsrWaitTimeoutError } from "@brawsr/sdk";
const brawsr = new BrawsrClient();
const session = await brawsr.createSession({
ttlSeconds: 300,
displayLabel: "Checkout retry",
});
let browser: Browser | undefined;
try {
const connection = brawsr.connectCDP(session);
browser = await chromium.connectOverCDP(connection.endpointUrl, {
headers: connection.headers,
});
const context = browser.contexts()[0];
if (!context) throw new Error("browser has no default context");
const page = context.pages()[0] ?? (await context.newPage());
await page.goto("https://example.com");
console.log(await page.title());
} finally {
try {
await browser?.close();
} finally {
await brawsr.closeSession(session.id);
}
}
The production endpoint, https://api.brawsr.io, is built in. Pass an explicit
override only for another deployment:
const local = new BrawsrClient({
apiKey: "local-development-key",
baseUrl: "http://localhost:8080",
requestTimeoutMs: 15_000,
});
BRAWSR_BASE_URL is intentionally ignored so a process-level setting cannot
silently redirect credentials. The API key can always be passed explicitly.
connectCDP returns immutable connection data. The SDK does not retain the API
key in a browser object or speak CDP itself.
getSession(id) also returns the current stable cdpUrl, so a restarted
process can fetch an active session and pass it directly to connectCDP.
Checkpoint and rewind
const checkpoint = await brawsr.createCheckpoint(session.id, {
label: "before-submit",
timeoutMs: 30_000,
});
await page.fill("textarea", "draft that should be discarded");
const restored = await brawsr.rewind(session.id, checkpoint);
const connection = brawsr.connectCDP(restored);
const restoredBrowser = await chromium.connectOverCDP(connection.endpointUrl, {
headers: connection.headers,
});
const restoredContext = restoredBrowser.contexts()[0];
const restoredPages = restoredContext.pages();
await brawsr.waitRewind(restored.operationId);
createCheckpoint, rewind, fork, and deleteCheckpoint each send one mutation.
When the API returns 202 Accepted, the SDK polls only the operation resource
and returns the final result. Callers do not need to implement polling.
Checkpoint labels are case-sensitive and unique among live checkpoints in a
session. A label that has the checkpoint-ID shape is rejected; use the returned
ID when an immutable reference is required.
Resources and operations
createSession, getSession, listSessions, iterateSessions,
updateSessionLabel, closeSession
createCheckpoint, getCheckpoint, listCheckpoints,
iterateCheckpoints, listCheckpointAncestry,
iterateCheckpointAncestry, deleteCheckpoint
listActivity, iterateActivity, getLineage,
iterateLineageChildren
rewind
fork, closeSessions
getOperation, waitOperation, waitCheckpoint, waitRewind, waitFork
connectCDP
All iterators are lazy, fetch one page at a time, preserve opaque cursors, and
accept an AbortSignal. Session lists are newest-first. They support exact
status, displayLabel, and sessionId filters, case-insensitive label
search, and inclusive createdFrom / exclusive createdBefore creation-time
bounds. getSession and updateSessionLabel return SessionDetail, including
collection counts, an optional active lifecycle operation, and canonical
collection paths.
listCheckpoints is the session's capture history, including deletion
tombstones. It is useful for audit and dashboard history, but is not a list of
valid rewind targets. Use listCheckpointAncestry for the session's current
rewindable chain. The server still revalidates a checkpoint when rewind starts.
getLineage returns one hop: the selected session, its optional parent edge,
and one cursor-paged set of direct children. Fetch a child's lineage to expand
nested forks. listActivity exposes customer-visible lifecycle and operation
outcomes without internal worker or saga details.
Every waiter accepts a timeout and optional AbortSignal. Stopping a waiter
does not cancel work already admitted by the server. The operation-specific
waiters return the same typed result as the corresponding mutation, including
the restored CDP URL after rewind; callers never need to decode raw operation
results. getOperation and waitOperation remain available for generic
observability.
Fork and explicit cleanup
const forked = await brawsr.fork(session.id, checkpoint, {
n: 3,
ttlSeconds: 300,
timeoutMs: 30_000,
});
for (const child of forked.children) {
const connection = brawsr.connectCDP(child);
console.log(child.branchIndex, child.sessionId, connection.endpointUrl);
}
const outcomes = await brawsr.closeSessions(forked, { concurrency: 3 });
for (const outcome of outcomes) {
if (!outcome.ok)
console.error("close failed", outcome.sessionId, outcome.error);
}
The returned children are immutable and ordered by branchIndex. The source
session remains open. Children may checkpoint, rewind, or fork again; there is
no browser-state merge. closeSessions never selects a winner, closes the
source, or hides partial failures. It accepts a fork result, child objects, or
session IDs and preserves input order in its outcome list.
Errors and recovery
BrawsrError: base class for every public SDK error.
BrawsrResponseError: the API returned malformed JSON or a response outside
the public contract.
BrawsrApiError: safe API envelope with status, stable code,
requestId, optional operationId, retryable, and retryAfterMs.
BrawsrOperationError: the server operation reached failed.
BrawsrWaitTimeoutError / BrawsrWaitCancelledError: local waiting stopped;
server work may still complete. For an admitted mutation, operationId and
idempotencyKey remain available programmatically for recovery.
BrawsrTransportError: the transport outcome is ambiguous. Its
idempotencyKey is available programmatically for recovery but is omitted
from the message.
The client does not retry an ambiguous mutation by default. Advanced callers
may provide isPreResponseConnectionError; only a definitely pre-response
failure is retried, at most once, with the same idempotency key.
Every HTTP request has a 30-second transport timeout by default. Configure it
with requestTimeoutMs. A waiter also applies its own overall timeoutMs, so a
hung polling request cannot extend the waiter beyond that deadline. Cancelling
an admitted waiter produces BrawsrWaitCancelledError; aborting an initial
mutation request is transport-ambiguous and preserves its idempotency key in
BrawsrTransportError.
Resume a timed-out rewind without resending it:
try {
await brawsr.rewind(session.id, checkpoint, { timeoutMs: 1_000 });
} catch (error) {
if (!(error instanceof BrawsrWaitTimeoutError) || !error.operationId)
throw error;
const restored = await brawsr.waitRewind(error.operationId, {
timeoutMs: 30_000,
});
const connection = brawsr.connectCDP(restored);
await chromium.connectOverCDP(connection.endpointUrl, {
headers: connection.headers,
});
}
Rewind restores browser/client state, not side effects already committed by a
remote website. Long-lived WSS, SSE, or WebRTC connections may need
application-level reconnection after restore.
See examples/rewind-playwright.ts and examples/rewind-puppeteer.ts for
executable reconnect stories that reject stale page handles and rediscover the
restored pages. A classic Selenium WebDriver session cannot be rebound to the
replacement browser in v0.1; a later WebDriver/BiDi adapter owns that contract.
Development
npm ci
npm run typecheck
npm run typecheck:examples
npm run build
npm test
npm run verify:package
The package check uses the release-pinned Node 24 + npm 11.5.1 toolchain,
builds the archive twice, compares contents and SHA-256, and imports the SDK
from a clean temporary archive install. CI tests the installed SDK on Node 22
and Node 24.