Skip to content

Repository files navigation

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
# or
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(); // reads BRAWSR_API_KEY

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,
});

// Continue browser work. This state is intentionally newer than the checkpoint.
await page.fill("textarea", "draft that should be discarded");

// A label, checkpoint ID, Checkpoint, or CheckpointResult is accepted.
const restored = await brawsr.rewind(session.id, checkpoint);

// Rewind replaces the browser process. The old Browser/Page handles are stale.
// Connect again and rediscover the default context and its pages.
const connection = brawsr.connectCDP(restored);
const restoredBrowser = await chromium.connectOverCDP(connection.endpointUrl, {
  headers: connection.headers,
});
const restoredContext = restoredBrowser.contexts()[0];
const restoredPages = restoredContext.pages();

// The early rewind result is already CDP-usable. Wait only before another
// lifecycle mutation on this same session (for example checkpoint or close).
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);
  // Each child is an independent session. Attach with your browser library.
  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.

About

TypeScript and JavaScript SDK for brawsr

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages