npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

clintplay-sdk

v0.9.1

Published

Clintplay - Session replay, error tracking, and automatic Playwright test generation

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-sdk

Quick 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 integration

Session 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 a replay_url deep 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.clintplay with:
    • 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 (when dashboardUrl is 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, 429 or 5xx, 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. Other 4xx answers 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.warn and counted in the undelivered field 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 with maskInputSelector. 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 via denyHeaders.
  • Skip other telemetry SDKs entirely: networkIgnoreUrls: ['https://otel.example.com/*']. Unlike denyBodyUrls, 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 apiUrl aren't captured (otherwise the recorder would record itself sending events).

License

MIT — see LICENSE.