Browser SDK
syncline-browser records the session with rrweb and mints the trace context that makes the stitch possible. It is written on the assumption that it is a guest in someone else’s page.
Setup
import { startRecording } from 'syncline-browser';
const recording = startRecording({
key: 'pk_live_...',
endpoint: 'https://syncline.example.com',
traceOrigins: ['https://app.acme.com', 'https://api.acme.com'],
release: 'web@2.4.1',
user: { id: currentUser.id },
});Options
| Option | Default | What it does |
|---|---|---|
key | — | Public project key. Safe to ship in a bundle; gated by the origin allowlist |
endpoint | — | Base URL of your Syncline API |
traceOrigins | page origin | The only origins that receive a traceparent |
release | — | Ties a recording to a deploy |
user | — | { id }, so you can find one person’s session |
maskAllInputs | true | Masks every input, textarea and select |
captureErrors | true | Uncaught errors and unhandled promise rejections |
captureConsole | false | true for error and warn, or a list of levels |
debug | false | SDK diagnostics to the console |
Rules the SDK will not break
It will not break your page
Every patched path is wrapped, and any internal failure falls through to the original fetch or XMLHttpRequest. There is a test asserting that when both instrumentation hooks throw, the request still completes normally. A recording tool that takes down checkout is worse than no recording tool.
It will not inject cross-origin
traceparent goes only to origins on traceOrigins. Sending it to a third party would leak internal trace ids and — worse — add a header their CORS policy does not allow, turning a working request into a failed preflight. Subdomains do not match either: a third-party widget can be parked on one.
It will not trace itself
window.fetch is captured before the patch is installed, so the SDK’s own uploads and clock probes carry no header and never appear in their own recording.
What you have to do
Access-Control-Allow-Headers: traceparentThis is the single most common integration failure. Without it every traced request fails preflight, and it looks like the SDK broke your site rather than like a CORS setting.How it behaves
- Flush cadence. Every 5 seconds or 64 KB, whichever comes first. The final flush uses
fetchwithkeepaliveonpagehide, which survives the page closing and still sets headers. - Session identity. A ULID in
sessionStoragewith a 30-minute idle timeout. It survives navigation within a tab but not a new tab — two tabs are two recordings, and merging them would produce a replay whose DOM jumps between windows. - Requests in flight at a flush boundary roll into a later chunk rather than being reported with a guessed duration.
- Compression.
CompressionStreamwhere available; an uncompressed body is a supported outcome, not a failure.
Sampling, inverted
A recorded session always sets sampled=1 on the traceparent, and standard OTel parent-based sampling honours it. You can never open the replay of a slow request whose spans were sampled away — which would be exactly the request you wanted.