@newtalaria/browser
Browser SDK for Talaria — error capture and session replay (rrweb event streams, not video).
Install
npm install @newtalaria/browser
From this monorepo:
cd new_talaria_js/packages/browser
npm install
npm run build
Quick start
import { Talaria } from '@newtalaria/browser';
Talaria.init({
// Serverpod base URL (no trailing path)
dsn: 'http://localhost:8080',
apiKey: 'tal_live_…', // full raw key from createApiKey
environment: 'development',
release: '1.0.0',
// Continuous session upload (0–1). Default 0 = buffer only until an error.
replaysSessionSampleRate: 0,
// On error, promote the ~60s ring buffer (0–1). Default 1.
replaysOnErrorSampleRate: 1,
// Post-error upload window. Default 15000 (cheap clip). Use 0 to continue
// until the 5-minute max duration (Sentry-like, more expensive).
replaysErrorAfterMs: 15_000,
maskAllInputs: true, // default
// Embed same-origin CSS into the snapshot (needed for auth-gated UIs like CMS).
// Default false — player re-fetches public stylesheet hrefs instead.
// inlineStylesheet: true,
});
try {
throw new Error('Something broke');
} catch (error) {
await Talaria.captureException(error);
}
await Talaria.captureMessage('Checkout opened', 'info');
console.log('replay', Talaria.getReplayId());
await Talaria.flush();
await Talaria.close();
Talaria.init also installs window.onerror / unhandledrejection handlers unless you pass disableDefaultIntegrations: true. Opaque cross-origin "Script error." events (no usable Error object) are ignored by default.
Optional init tags are merged into every captured event (per-call tags win on key conflict).
Script tag (IIFE)
For hosts without a bundler (e.g. Silverstripe Requirements):
<script src="/path/to/talaria.browser.iife.js"></script>
<script>
Talaria.init({
dsn: 'https://api.example.com',
apiKey: 'tal_live_…',
environment: 'production',
replaysSessionSampleRate: 0,
replaysOnErrorSampleRate: 0,
});
</script>
Build output: dist/talaria.browser.iife.js (also produced by npm run build / npm run build:iife).
Recommended sampling (cost)
You pay for uploaded + retained bytes, not for local buffering. Prefer error clips in production; keep full-session sampling low.
| Traffic | replaysSessionSampleRate |
replaysOnErrorSampleRate |
replaysErrorAfterMs |
|---|---|---|---|
| High (100k+/day) | 0.01 |
1.0 |
15000 (default clip) |
| Medium (10k–100k/day) | 0.1 |
1.0 |
15000 |
| Low (under 10k/day) | 0.25 |
1.0 |
15000 |
| Marketing / docs site | 0 |
1.0 |
15000 |
| Rich post-error context | 0 |
1.0 |
0 (continue to 5 min cap) |
Defaults (session=0, onError=1, errorAfterMs=15000) are the cheapest useful profile: quiet traffic costs nothing; each sampled error keeps ~60s before + ~15s after.
Replay sampling behavior
| Mode | Behavior |
|---|---|
| Session sample hit | replays/start immediately; segments upload every ~5s or ~100KB until unload or 5 min max |
| Session sample miss | Record into a ~60s ring buffer; nothing uploaded until an error sample hits |
Error sample + replaysErrorAfterMs > 0 |
Upload buffer (≤~960KiB gzip pack target / 1MiB server cap) + trailing window, hard caps 12 segments or 2MiB compressed, attach replayId only if segments landed, finish, return to buffer mode |
Error sample + replaysErrorAfterMs = 0 |
Upload buffer then continue like session mode until 5 min / unload / size caps |
| Server limit (replay segments / total size / duration) | Stop uploading that replay; no retries |
| Oversized FullSnapshot on segment 0 | Abort the clip (no blank orphan upload); error event gets replay.capture=failed + reason. Meta+FullSnapshot are taken/fitted atomically so a soft estimated take window cannot orphan the snapshot. |
| Oversized single non-FS rrweb event | Dropped with a console warning (cannot fit under segment cap) |
pagehide / close |
Flush pending segments with fetch keepalive, then replays/finish; close fully resets so init() works again (React Strict Mode) |
Replay capture outcome tags
When an error/fatal event attempts an on-error clip (replaysSessionSampleRate miss), the SDK may attach:
| Tag | Values |
|---|---|
replay.capture |
ok | failed | skipped |
replay.capture_reason |
oversized_full_snapshot | no_full_snapshot | upload_failed | not_sampled | buffer_empty |
Failed captures do not set replayId (avoids linking a blank player). Details are also under extra.replayCapture.
When the paint base cannot ship, extra.replayCapture also includes size diagnostics where available:
| Field | Meaning |
|---|---|
fullSnapshotEstimatedBytes |
JSON size estimate of the FullSnapshot |
fullSnapshotCompressedBytes |
Gzip size of the FullSnapshot alone (when measured) |
metaEstimatedBytes |
JSON size estimate of the Meta event |
maxUncompressedSegmentBytes / maxCompressedSegmentBytes |
Current pack caps |
Use these to see how far over budget a CMS/admin snapshot was when a clip was aborted.
Serverpod URL pattern
Talaria uses Serverpod RPC, not REST resource URLs:
| Call | Method | URL |
|---|---|---|
| Start replay | POST |
{baseUrl}/replays/start |
| Upload segment | POST |
{baseUrl}/replays/ingestSegment |
| Finish replay | POST |
{baseUrl}/replays/finish |
| Ingest event | POST |
{baseUrl}/events/ingest |
Local default: http://localhost:8080.
Bodies are JSON with named parameters and __className__ on typed inputs:
{
"input": {
"__className__": "StartReplayInput",
"replayId": "…",
"environment": "development",
"sessionId": "…",
"url": "http://localhost:5173/"
}
}
Auth (ingest):
X-API-Key: tal_live_…(preferred)- or
Authorization: Bearer tal_live_…
ByteData (gzipBytes)
Serverpod serializes ByteData as a wrapped base64 string:
decode('<base64>', 'base64')
Segment payloads are a gzip-compressed JSON array of rrweb events.
Custom rrweb events:
talaria-console— console level + args (truncated)talaria-network— method, redacted URL, status, duration (no bodies / no auth headers)
Privacy defaults: maskAllInputs: true, password fields masked, [data-talaria-mask] blocked.
Auth-gated CSS (CMS / admin)
By default inlineStylesheet is false: linked stylesheets stay as hrefs and are re-fetched when you watch the replay. That works for public CSS, but fails for login-protected admin CSS (player has no session cookies).
Set inlineStylesheet: true so same-origin stylesheet rules are embedded while the user is logged in. Cross-origin sheets without CORS still cannot be inlined (browser cssRules restriction).
Failed HTTP requests → events
By default, completed fetch / XHR responses with status 500–599 are also sent as Talaria events (message like HTTP 500: GET /api/...). Replay still gets talaria-network breadcrumbs for all requests.
Talaria.init({
dsn: 'https://api.example.com',
apiKey: 'tal_live_…',
environment: 'production',
// Default true
captureFailedRequests: true,
// Default [[500, 599]]. CMS admin often wants 4xx too:
failedRequestStatusCodes: [[400, 599]],
// Extra URL substrings to skip (Talaria /events and /replays are always skipped)
failedRequestIgnoreUrls: ['/health'],
});
Transport errors (network down, no status) are not promoted as HTTP events; uncaught exceptions from failed fetches still go through normal error / unhandledrejection handlers when the app rethrows.
Public API
| API | Description |
|---|---|
Talaria.init(options) |
Configure + start recording |
Talaria.captureException(error) |
Ingest error (+ replay link when sampled) |
Talaria.captureMessage(message, level?) |
Ingest message |
Talaria.getReplayId() |
Active upload replay id, or null |
Talaria.flush() |
Upload buffered segments |
Talaria.close() |
Stop recording, flush, finish |
Example
See ../../examples/sdk-spa for a minimal page wired to localhost:8080.
Local file: install (marketing / monorepo)
dist/ is gitignored. After editing SDK src/, rebuild before the consumer picks up changes:
cd new_talaria_js/packages/browser
npm run build
# or: npm install in the consumer — `prepare` runs build for file: installs
Then restart the Next app with a clean cache:
cd ../new_talaria_marketing # sibling repo
rm -rf .next
npm run dev
Serverpod must be restarted after changing ReplayLimits (compressed cap is 512KiB). A live process still on 256KiB will 400 every near-max segment.
Verify error-clip ingest
- Restart Serverpod (512KiB live).
- Rebuild the browser package; restart marketing with clean
.next. - Browse
/docs/**~30s, throw one test exception. - Expect: a few
ingestSegment200s, onefinish, zero compressed-size 400 spam, dashboard replay plays. - Throw again later: a new bounded clip is allowed (buffer mode reset).