clintplay-sdk
v0.9.1
Published
Clintplay - Session replay, error tracking, and automatic Playwright test generation
Maintainers
Readme
clintplay-sdk
Browser SDK for Clintplay — session replay, error tracking, network and console capture, rage-click detection, and Core Web Vitals — wired into a single small script that runs in your users' browsers.
npm install clintplay-sdkQuick start
Bundled (Vite, webpack, Next.js, etc.)
import Clintplay from 'clintplay-sdk';
Clintplay.init({
projectId: 'gp_xxxxxxxxxxxxxxxxxxxxxxxx', // from your project's API key page
apiUrl: 'https://your-clintplay-domain.com/v1',
});Drop-in script tag
<script src="https://your-clintplay-domain.com/sdk.js"></script>
<script>
ClintplaySDK.Clintplay.init({
projectId: 'gp_xxxxxxxxxxxxxxxxxxxxxxxx',
apiUrl: 'https://your-clintplay-domain.com/v1',
});
</script>The IIFE bundle (dist/index.global.js) exposes the global ClintplaySDK, and
the SDK is ClintplaySDK.Clintplay — not a bare Clintplay, which is what this
section said until 0.8.0 and what would have thrown a ReferenceError for anyone
who copied it. The dashboard's Setup page has always shown the correct form.
API
Clintplay.init(config); // start recording
Clintplay.identify(userId, traits?); // attach a user to the current session
Clintplay.track(name, properties?); // emit a custom product event
Clintplay.tag(tagName); // tag the session (e.g. "checkout-failed")
Clintplay.setMetadata(metadata); // attach arbitrary metadata to the session
Clintplay.stop(); // stop recording + flush (see Delivery)
Clintplay.getSessionId(); // get active session ID or null
Clintplay.sentryIntegration(options?); // Sentry / GlitchTip integrationSession ID & Sentry / GlitchTip Integration
Clintplay.getSessionId(): string | null
Returns the ID of the currently active recording session (e.g., "gs_..."), or null if the SDK is not initialized, has been stopped, or no session is active. Never throws.
const sessionId = Clintplay.getSessionId();Clintplay.sentryIntegration(options?): ClintplaySentryIntegration
Creates a client integration for Sentry (@sentry/browser, @sentry/react, etc.) or GlitchTip that enriches Sentry error and message events with the active Clintplay session ID and deep-link replay URLs.
Options:
dashboardUrl?: string— Optional base URL of the Clintplay dashboard (e.g.,'https://clintplay.clint.digital'). When configured, generates areplay_urldeep link pointing to the session replay with the playback offset (?t=<offset_ms>).
Event Enrichment:
- Tag: Sets tag
[CLINTPLAY_SENTRY_TAG]('clintplay_session') to the active session ID. - Context: Adds
contexts.clintplaywith:session_id: the active Clintplay session ID.offset_ms: milliseconds elapsed between session start and the error event.replay_url: direct replay link in the Clintplay dashboard (whendashboardUrlis provided).
Invariants:
- Non-error events (e.g.,
transaction) are passed through untouched to prevent tag table bloat. - When no session is active, events pass through unmodified.
- Safe execution: the integration never throws; any internal error returns the original event intact.
CLINTPLAY_SENTRY_TAG
Constant string ('clintplay_session') exported at the package root (import { CLINTPLAY_SENTRY_TAG } from 'clintplay-sdk') and on Clintplay.CLINTPLAY_SENTRY_TAG. This is the tag key indexed by GlitchTip and Sentry (e.g. has:clintplay_session or clintplay_session:<sessionId>).
Sentry Integration Example
import * as Sentry from '@sentry/browser'; // or @sentry/react
import Clintplay, { CLINTPLAY_SENTRY_TAG } from 'clintplay-sdk';
// 1. Initialize Sentry
Sentry.init({
dsn: 'https://[email protected]/0',
});
// 2. Initialize Clintplay
Clintplay.init({
projectId: 'gp_xxxxxxxxxxxxxxxxxxxxxxxx',
apiUrl: 'https://your-clintplay-domain.com/v1',
});
// 3. Register the integration
Sentry.addIntegration(
Clintplay.sentryIntegration({
dashboardUrl: 'https://clintplay.clint.digital',
})
);On-Demand / Dynamic Loading
If your application loads clintplay-sdk dynamically to keep recording dependencies out of the initial bundle:
import * as Sentry from '@sentry/react';
// Initialize Sentry at application boot
Sentry.init({ dsn: '...' });
// Load Clintplay on idle / after page load
async function initSessionReplay() {
const { default: Clintplay } = await import('clintplay-sdk');
Clintplay.init({
projectId: 'gp_xxxxxxxxxxxxxxxxxxxxxxxx',
apiUrl: 'https://your-clintplay-domain.com/v1',
});
try {
if (typeof Clintplay.sentryIntegration === 'function') {
Sentry.addIntegration(
Clintplay.sentryIntegration({
dashboardUrl: 'https://clintplay.clint.digital',
})
);
}
} catch (err) {
console.warn('Failed to register Clintplay Sentry integration:', err);
}
}Configuration
All fields beyond projectId are optional.
| Field | Default | Purpose |
|---|---|---|
| projectId | — | Required. Must start with cp_proj_. |
| apiUrl | — | Your Clintplay API base URL (https://…/v1). |
| maskAllInputs | true | Mask every <input> value before sending (privacy-first default). |
| maskInputSelector | — | CSS selector for inputs to mask. |
| blockSelector | — | CSS selector for elements to fully exclude from the replay. |
| ignoreSelector | — | CSS selector for elements to ignore in interaction events. |
| recordNetwork | true | Capture fetch / XHR request + response metadata. |
| recordConsole | true | Capture console.log/warn/error calls. |
| trackPerformance | true | Capture Core Web Vitals (LCP, CLS, INP, FCP, TTFB). |
| trackRageClicks | true | Detect repeated clicks on the same element. |
| trackNavigation | true | Track pushState / popstate URL changes. |
| networkSanitize | — | { denyHeaders, denyBodyUrls } — strip sensitive fields from captured network calls. |
| networkIgnoreUrls | — | URL globs (* = anything) that are not recorded at all — no network event, no error when they fail. Meant for other telemetry SDKs' export calls. |
| flushIntervalMs | 15000 | How often to flush buffered events (15s). Clamped to a minimum of 1000. |
| flushMaxBytes | 256000 | Max payload size before forcing a flush (256 KB). |
| compressEvents | true | gzip the payload before sending. |
| persistence | 'localStorage' | Session persistence backend ('localStorage', 'sessionStorage', or 'none'). |
Delivery
Events are buffered and sent in batches (see flushIntervalMs / flushMaxBytes), and also when the tab is hidden.
- A batch answered
408,429or5xx, lost to a network error, or left unanswered past its timeout is kept and re-sent, oldest first. The timeout is 30 s plus the body's upload time at 20 KB/s, and it doubles after each attempt that timed out. The backoff runs 1, 2, 4, 8, 16, 30 and 30 s, jittered, for up to 8 attempts. Other4xxanswers are not re-sent. Up to 4 MiB of batches wait to be re-sent. While they wait, newer batches line up behind them, except while the tab is hidden. - When the tab is hidden, or on
Clintplay.stop(), every waiting batch and the buffer go out at once, concurrently. Batches still failing after that keep being re-sent on their timers. - Requests use
keepalive(so they survive the page closing) while this SDK's own keepalive bodies in flight stay under 60,000 bytes. The browser allows 64 KiB per page, shared with every other script on it. Past that budget, a request goes as a plain request. On hide, the oldest batches get the budget first. - A batch the SDK gives up on is logged with
console.warnand counted in theundeliveredfield of the next batch built after the loss, so the loss is visible server-side once that batch arrives. A loss at the very end of the page has no next batch and is not reported.
Privacy controls
- Mask sensitive inputs:
maskAllInputs: true, or scope it withmaskInputSelector. Password fields and credit card / SSN / phone / email input types are masked unconditionally. - Block whole elements from the replay:
blockSelector: '.do-not-record'. The element is excluded from DOM snapshots and mutation events. - Strip request bodies for specific endpoints:
networkSanitize.denyBodyUrls: ['/api/auth/']. Headers are stripped viadenyHeaders. - Skip other telemetry SDKs entirely:
networkIgnoreUrls: ['https://otel.example.com/*']. UnlikedenyBodyUrls, the request is not recorded at all, so its failures don't count as errors either. - Self-ingest endpoints are auto-skipped — calls to your own
apiUrlaren't captured (otherwise the recorder would record itself sending events).
License
MIT — see LICENSE.
