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

@reprokitapp/browser-sdk

v1.3.0

Published

Framework-agnostic TypeScript browser SDK for ReproKit. Captures safe runtime context (errors, unhandled rejections, failed requests, rage clicks, console errors, resource errors) and posts batched events to the ReproKit API. Privacy-first defaults; zero

Readme

ReproKit Browser SDK

Framework-agnostic TypeScript browser SDK for ReproKit.

ReproKit turns vague user bug reports into reproducible, evidence-backed issues. The browser SDK captures safe runtime signals from your web app — JavaScript errors, failed network requests, action breadcrumbs, optional user reports — and sends compact batched events to the ReproKit API.

  • Privacy-first defaults: no input values, no cookies, no storage, no request/response bodies.
  • Zero runtime dependencies.
  • ESM, CJS, and script-tag (IIFE) builds with strict TypeScript declarations.
  • Works with any framework, or none.

Install

npm install @reprokitapp/browser-sdk

Quick start

import { ReproKit } from '@reprokitapp/browser-sdk';

const rk = new ReproKit({
  projectKey: 'pk_live_...',
  apiUrl: 'https://api.reprokit.app',
  environment: 'production',
  release: '[email protected]'
});

rk.start();

Or via the convenience function, which constructs and starts in one call:

import { initReproKit } from '@reprokitapp/browser-sdk';

const rk = initReproKit({
  projectKey: 'pk_live_...',
  apiUrl: 'https://api.reprokit.app'
});

Script-tag (IIFE) usage via a public CDN:

<script src="https://unpkg.com/@reprokitapp/[email protected]/dist/reprokit.global.js"></script>
<script>
  ReproKit.init({
    projectKey: 'pk_live_...',
    apiUrl: 'https://api.reprokit.app'
  });
</script>

The same path works on jsDelivr: https://cdn.jsdelivr.net/npm/@reprokitapp/[email protected]/dist/reprokit.global.js. Always pin a version on CDN URLs.

The browser project key is a public identifier, not a secret. Access control happens server-side: per-project allowed origins, rate limits, and payload limits.

What it captures

Defaults are conservative. Anything riskier is opt-in.

| Signal | Default | Config key | |---|---|---| | JS errors (window.error) | on | capture.errors | | Unhandled promise rejections | on | capture.unhandledRejections | | Failed network requests (5xx + network errors; 4xx opt-in) | on | capture.failedRequests | | console.error calls | off | capture.consoleErrors | | console.warn calls | off | capture.consoleWarnings | | Resource load failures (img, script, link, …) | off | capture.resourceErrors | | Rage clicks | off | capture.rageClicks | | Action breadcrumbs (clicks/submits/nav keys/route changes) | on | breadcrumbs.enabled |

Every event includes a small context: RuntimeContext snapshot: sanitized URL, path, sanitized referrer, user agent, language(s), timezone, viewport, screen size, devicePixelRatio, online status, visibility state, navigation timing summary, SDK name + version.

By default the snapshot also carries browser setup context to help diagnose which browsers and devices an issue affects: touch/pointer capability (maxTouchPoints, coarse/fine pointer, hover) and — on browsers that expose them — raw User-Agent Client Hints from navigator.userAgentData (brands, mobile flag, platform, plus high-entropy hints like platformVersion, architecture, and model). The SDK sends these raw facts as-is and never derives browser/OS/device labels itself. Exact device model is best-effort: browsers only expose it through Client Hints (in practice Chromium on Android); on Safari, Firefox, iOS, and most desktops it is simply unavailable. Disable with privacy: { captureDeviceContext: false }, or keep low-entropy hints but skip the high-entropy request with privacy: { captureHighEntropyHints: false }.

Every event also carries a snapshot of the most recent user-action breadcrumbs (see Action breadcrumbs).

What it never captures

The SDK does not collect, and has no configuration that enables collecting:

  • request bodies
  • response bodies
  • cookies
  • localStorage / sessionStorage
  • form input values (inputs and textareas are treated as sensitive)
  • full DOM snapshots
  • session replay
  • screen recording
  • IP addresses, geolocation, or ISP/network-provider information

Sensitive query params (token, access_token, api_key, password, …) and sensitive headers (Authorization, Cookie, X-Api-Key, …) are redacted from anything that does get captured. See Privacy guarantees.

Production checklist

  • Pin the version. Follow your release policy: ^1.3.0 to take compatible updates, or an exact version for fully reproducible builds. From 1.0.0 onward, the public API and configuration follow semantic versioning. CDN users should always pin (@1.3.0).
  • Keep debug: false. Debug-mode logger output is visible to anyone with DevTools open.
  • Configure CSP. The SDK posts to ${apiUrl}/api/sdk/events. Your Content-Security-Policy must include the ingest origin under connect-src, e.g. connect-src https://api.reprokit.app. Without it, the browser blocks ingest silently apart from a console warning.
  • Add your site origin to the project's allowed origins in the ReproKit admin. CORS alone is not enough — the API validates the origin allow-list per request.
  • Enable optional capture deliberately. Console capture, resource errors, rage clicks, 4xx capture, and the report widget are all off by default. Turn each on intentionally and review what it adds to your payloads; the widget injects a floating "Report a problem" button on every page.

Configuration

const rk = new ReproKit({
  projectKey: 'pk_live_...',
  apiUrl: 'https://api.reprokit.app',
  environment: 'production',
  release: '[email protected]',

  capture: {
    consoleErrors: true,
    consoleWarnings: false,
    resourceErrors: true,
    rageClicks: true,
    rageClickThreshold: 5,
    rageClickWindowMs: 1500
  },

  filters: {
    ignoreBrowserExtensions: true,
    ignoreIframes: true,
    ignoreThirdPartyScripts: true,
    allowedScriptOrigins: ['https://cdn.example.com']
  },

  network: {
    captureServerErrors: true,   // 5xx (default)
    captureNetworkErrors: true,  // status 0 / aborts / timeouts (default)
    captureClientErrors: false,  // 4xx — off by default to keep noise low
    captureXhr: true,            // also instrument XMLHttpRequest (default)
    ignoreUrls: ['/health', /\/_next\//],
    ignoreMethods: ['OPTIONS']
  },

  privacy: {
    maskInputs: true,
    maskTextareas: true,
    redactUrlQueryParams: true,
    captureAccessibleNames: true,  // safe action labels (aria-label/label/title/short text)
    captureDeviceContext: true,    // touch/pointer signals + raw userAgentData Client Hints
    captureHighEntropyHints: true  // platformVersion/architecture/model via getHighEntropyValues
  },

  sampleRate: 1,
  flushIntervalMs: 5000,
  maxQueueSize: 50,
  debug: false
});

rk.start();

Legacy flat config

The following flat options are accepted for backward compatibility. The nested form is preferred.

| Legacy flat | Replacement | |---|---| | enableErrorCapture | capture.errors | | enableUnhandledRejectionCapture | capture.unhandledRejections | | enableNetworkCapture | capture.failedRequests | | enableConsoleCapture | capture.consoleErrors | | enableResourceErrorCapture | capture.resourceErrors | | enableRageClickCapture | capture.rageClicks |

When both are present the nested value wins.

Runtime API

After start(), the SDK exposes:

rk.captureError(error: unknown, context?: { metadata?: Record<string, unknown> }): void;
rk.captureMessage(message: string, context?: { metadata?: Record<string, unknown> }): void;
rk.addBreadcrumb(breadcrumb: BreadcrumbInput): void;
rk.captureReport(input: CaptureReportInput): Promise<{ id: string; accepted: true }>;
rk.flush(): Promise<void>;
rk.stop(): void;

rk.setUser(userId: string | undefined): void;
rk.clearUser(): void;
rk.setContext(key: string, value: unknown): void;
rk.removeContext(key: string): void;
rk.clearContext(): void;

Identifying users and adding context

After a login flow:

rk.setUser(currentUser.id);
rk.setContext('plan', currentUser.plan);
rk.setContext('feature_flags', { newCheckout: true });

setContext values are sanitized with the same redaction rules as event payloads (sensitive keys become [REDACTED]), and the SDK caps the number of custom keys (currently 20) to avoid unbounded payloads.

stop() removes all listeners, restores patched browser APIs (fetch, XMLHttpRequest, console.*, history.*), and cancels in-flight sends. start() can be called again afterwards.

Action breadcrumbs

The SDK keeps a small ring buffer of the last user actions (default 15) and attaches a snapshot to every captured event under event.breadcrumbs, so ReproKit can reconstruct what the user did before a failure.

Captured automatically:

| Type | What is recorded | What is never recorded | |---|---|---| | click | safe selector of the nearest interactive ancestor (tag + id/classes only, depth-limited), a short privacy-filtered accessible label, current path/url | input/textarea/select values, placeholder, text of non-interactive containers | | submit | safe form selector, current path/url | field names, field values, form data | | navigation | from/to path, sanitized url (history.pushState/replaceState/popstate) | hash fragments verbatim; sensitive query params are redacted | | key | only navigation keys (Enter, Escape, Tab, ArrowUp/Down/Left/Right); Tab/arrow keys are suppressed when the target is an input/textarea/contenteditable | typed characters, key codes for letters/digits, input value |

Each breadcrumb is:

  • sanitized via the redaction helpers (sensitive metadata keys like token, password, apiKey become [REDACTED])
  • length-capped per field
  • bounded by maxItems — oldest is dropped first

Configuration:

const rk = new ReproKit({
  projectKey: 'pk_live_...',
  apiUrl: 'https://api.reprokit.app',

  breadcrumbs: {
    enabled: true,
    maxItems: 15,        // ring buffer size, clamped to 0–50
    captureClicks: true,
    captureNavigation: true,
    captureSubmits: true,
    captureKeys: true
  }
});

To disable entirely:

new ReproKit({ projectKey, apiUrl, breadcrumbs: { enabled: false } });

Adding custom breadcrumbs

Apps can record domain breadcrumbs through the public API. The call is safe to make any time after construction and never throws into the host app. Metadata is redacted with the same rules as event payloads.

rk.addBreadcrumb({
  type: 'custom',
  message: 'feature flag evaluated',
  category: 'flags',
  metadata: { flag: 'newCheckout', variant: 'B' }
});

addBreadcrumb is a no-op when breadcrumbs.enabled is false.

Action labels

Click breadcrumbs (and rage-click events via targetLabel) resolve the raw click target to the nearest interactive ancestor — clicking the <i> icon inside <button aria-label="Like"> records the button, not the icon — and attach a short accessible label derived in priority order from:

  1. aria-label
  2. the associated <label> for form controls (never the control's value)
  3. title
  4. short visible text — interactive elements only, never form controls

Candidates are whitespace-normalized and rejected outright (never truncated) when they exceed 64 characters, look like emails, phone numbers, long numeric identifiers, UUIDs/hashes/tokens, or URLs. When nothing safe remains, the label is omitted and the sanitized selector stands alone. Disable with privacy: { captureAccessibleNames: false }.

Privacy guarantees (breadcrumbs)

  • Input/textarea/select/contenteditable values are never read; placeholder is never used as a label.
  • Accessible labels come only from interactive elements, are length-capped, and are dropped when they look identifying (emails, ids, tokens, URLs).
  • textContent of non-interactive containers is not captured.
  • The safe selector helper only emits tag name, id, and up to two class names per ancestor (depth-limited to 4).
  • Navigation URLs are sanitized — sensitive query parameters are redacted before being attached.
  • Form submits record the safe form selector only — no field names or values.
  • Custom breadcrumb metadata is filtered through the same depth/key redaction rules as event payloads.

Breadcrumbs give ReproKit enough action context to help generate reproduction steps. They are not a session-replay stream and they never carry user-typed content.

User reports

Optional, user-initiated bug reports — distinct from the automatic signals above. Two ways in:

  1. Programmatic API. Call rk.captureReport({ message, … }) from your own feedback button or shortcut handler.
  2. Built-in floating widget. A "Report a problem" button that opens a 2-step modal + confirmation. Off by default; enable with reporting.widget: true.

captureReport

captureReport is an instance method, not a static one. The same call shape works in both npm/bundler mode and IIFE/script-tag mode — capture the instance returned by init() and call .captureReport(...) on it.

npm / bundler:

import { initReproKit } from '@reprokitapp/browser-sdk';

const rk = initReproKit({ projectKey, apiUrl });
await rk.captureReport({ message: 'Pay button keeps loading' });

Script-tag / IIFE:

<script src="https://unpkg.com/@reprokitapp/[email protected]/dist/reprokit.global.js"></script>
<script>
  const rk = ReproKit.init({ projectKey: 'pk_live_...', apiUrl: 'https://api.reprokit.app' });
  rk.captureReport({ message: 'Pay button keeps loading' });
</script>

Full payload — building your own feedback form:

const files = Array.from(fileInput.files ?? []).slice(0, 2);

await rk.captureReport({
  message: 'Pay button keeps loading',
  expectedBehavior: 'The payment should complete and show the receipt page.',
  blockingLevel: 'Blocking',                // 'NotBlocking' | 'Annoying' | 'Blocking' (camelCase accepted, normalized server-side)
  reporterEmail: currentUser.email,
  screenshot: screenshotBlob,               // Blob | File from your own input/capture flow; see Screenshots
  attachments: files,                       // File[] | FileList, max 2
  user: {                                   // visible to admins; redacted server-side; not sent to AI
    id: currentUser.id,
    email: currentUser.email,
    plan: currentUser.plan,
    role: currentUser.role
  },
  metadata: {                               // same redaction/visibility/AI guarantees as `user`
    checkoutId,
    cartSize: cart.items.length,
    experiment: 'new-checkout-flow'
  }
});
// → { id: 'rk-…', accepted: true }

Screenshot input options:

  • screenshot: Blob | File — used as-is. Drop in any image your app already produced (host-rendered preview, your own masked <canvas>, etc.).
  • screenshot: true — asks the SDK to capture the page via html-to-image. Requires the optional library to be supplied — reporting.screenshotLibrary for npm/bundler users, or a UMD <script> exposing window.htmlToImage for script-tag users. See Screenshots. If capture fails, the whole captureReport call rejects with ReproKitReportError.
  • omitted / false — no screenshot is attached.

Attachments: pass File[] or a FileList (e.g., directly from fileInput.files). The SDK enforces the API's max 2 files, 10 MB each cap and the content-type allow-list (image/png, image/jpeg, image/webp, video/mp4, video/webm, video/quicktime, text/plain); over-cap or wrong-type files are silently dropped and a debug warning is logged.

user and metadata: stored alongside the report and visible to admins in Issue Central. Sensitive-looking values (token, password, apiKey, …) are redacted by the SDK before send and again server-side before persistence. They are not included in the AI enrichment prompt — only message, expectedBehavior, blockingLevel, the browser context allow-list, and breadcrumbs are sent to AI.

Auto-filled by the SDK so callers don't have to:

  • projectKey, environment, release — from init
  • url, path — from location.href / location.pathname (sanitized)
  • context.userAgent, context.viewport, context.locale, context.theme — from the browser
  • breadcrumbs — the current breadcrumb snapshot

Validation / sanitization:

  • message is required (non-empty after trim).
  • All strings are capped at the API limits (message 8000, title 200, expectedBehavior 2000, reporterEmail 320, url/path 2048, …).
  • blockingLevel is normalized to the API's wire values: NotBlocking, Annoying, Blocking. notBlocking, annoying, blocking, not-blocking, not_blocking, blockingMe all map to the canonical value.
  • metadata passes through the same redaction used for events (token, password, apiKey, … become [REDACTED]).
  • Returns { id, accepted: true }. Failures throw ReproKitReportValidationError (client-side) or ReproKitReportError (network/HTTP).

Endpoints used

| When | Endpoint | Body | |---|---|---| | No screenshot, no attachments | POST {apiUrl}/api/sdk/reports | JSON | | Otherwise | POST {apiUrl}/api/sdk/reports/multipart | multipart/form-data |

The multipart body sends the JSON in a report form field, the optional screenshot under screenshot, and each attachment under the same attachments field name repeated. Headers on both requests: X-ReproKit-Project: <projectKey>, X-ReproKit-Sdk, X-ReproKit-Sdk-Version. Reports are sent immediately, not queued; the call returns the server's id on success.

Limits

| Field | Client cap | Server cap | |---|---|---| | message | 2000 in the widget UI, 8000 via the API | 8000 | | title | 200 | 200 | | expectedBehavior | 2000 | 2000 | | reporterEmail | 320 | 320 | | url / path | 2048 | 2048 | | Attachments | up to 2 files, 10 MB each | 2 × 10 MB | | Screenshot | 10 MB | 10 MB | | Allowed attachment types | image/png, image/jpeg, image/webp, video/mp4, video/webm, video/quicktime, text/plain | same | | Allowed screenshot types | image/png, image/jpeg, image/webp | same |

Over-cap or wrong-type files are dropped client-side; the report is still sent without them and the SDK logs a warning (debug mode only).

Built-in widget

new ReproKit({
  projectKey,
  apiUrl,
  reporting: {
    widget: true,                  // mount the floating button. Default: false
    position: 'bottom-right',      // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
    label: 'Report a problem',     // button text
    buttonVariant: 'label',        // 'label' | 'icon' | 'auto' (icon uses `label` as aria-label)
    screenshot: true,              // show "Attach screenshot" in Step 2 (default true). Capture is always user-triggered.
    attachments: true,             // show the attachments row
    maxAttachments: 2,             // clamped to 2
    maxFileSizeMB: 10,             // clamped to 10
    allowedAttachmentTypes: ['image/png', 'image/jpeg'], // optional narrowing
    screenshotTarget: '#app-shell',                       // optional capture target
    screenshotLibrary: () => import('html-to-image'),     // supplies the optional dependency (npm users; see Screenshots)
    user: { id: currentUser.id, plan: currentUser.plan }, // static user context
    metadata: { app: 'web' }       // or () => Record<string, unknown> for dynamic
  }
});

Widget behavior:

  • Floating button in the chosen corner. The modal renders in a top-level <div id="__reprokit-report-widget"> inside <body>; CSS is namespaced under .rk- and injected once. No Shadow DOM — the namespace plus single root element keep styles isolated from the host app.
  • Step 1: required "What happened?" textarea (2000-char UI limit), optional "What were you trying to do?", a NotBlocking / Annoying / Blocking me segmented control, optional email for follow-up.
  • Step 2: shows the runtime context that will be sent (browser, current page, breadcrumb count), an attachments row (up to 2 files, with remove buttons), and a privacy callout right above the Send button.
  • Confirmation: copy-to-clipboard button for the server-assigned report id, Close.
  • Keyboard: Escape closes; Tab/Shift+Tab is trapped inside the modal; focus returns to the previously-focused element on close.
  • ARIA: role="dialog", aria-modal="true", labelled buttons for Close/Back/Send/Copy.
  • SSR-safe: returns an inert handle when document / window are missing.

Screenshots

Screenshot capture is user-triggered, lazy-loaded, and privacy-masked:

  • Manual. In the widget, Step 2 shows an "Attach screenshot" button. The SDK does not capture on modal open; the user must click the button. A thumbnail preview appears before send; "Remove" and "Retake" are both one click away.
  • Lazy-loaded. The screenshot library (html-to-image, ~50 KB raw / ~13 KB gzip) is resolved only when capture is actually requested. Bundler users supply it as a lazy provider (reporting: { screenshotLibrary: () => import('html-to-image') }) so their bundler emits it as a separate chunk. The base SDK bundle is unaffected for callers who never trigger screenshots.
  • Privacy masking. Before capture, the SDK adds inline color: transparent !important (plus -webkit-text-fill-color, text-shadow, caret-color) to every <input> (except checkboxes/radios/buttons/file/etc.), <textarea>, <select>, contenteditable element, password field, and any element with data-reprokit-mask. Original styles are restored after capture. Input values are never read.
  • Widget exclusion. The widget root, anything with data-reprokit-ignore or .reprokit-ignore, and <script>/<style>/<link>/<meta>/<noscript> tags are filtered out of the capture. The widget root + modal are also visibility-hidden during capture so they never appear in the output.
  • Graceful failure. Cross-origin images, fonts, video elements, and <iframe> content may not render; in the widget, capture failure surfaces a non-blocking warning and the report can still be sent without a screenshot. Programmatic captureReport({ screenshot: true }) failures reject with ReproKitReportError — the caller asked for a screenshot explicitly, so the SDK doesn't silently send without it.

Installation

The screenshot feature requires the optional html-to-image peer dependency. It is marked optional, so installs that don't use screenshots never pull it in. The SDK does not import the package itself — you supply it through one of the mechanisms below, checked in this order:

  1. reporting.screenshotLibrary from config
  2. window.htmlToImage (script-tag/UMD users)
  3. a native dynamic import('html-to-image') — works only when the page has an import map entry for html-to-image

Bundler users (Vite, Webpack, Rollup, esbuild, Angular, …) — install the package and pass it in config:

npm install html-to-image
new ReproKit({
  projectKey, apiUrl,
  reporting: {
    widget: true,
    // Lazy provider (recommended): your bundler code-splits it, and it loads
    // only when a screenshot is actually captured.
    screenshotLibrary: () => import('html-to-image')
  }
});

If you prefer (at the cost of putting it in your main bundle), import it eagerly and pass the module object: import * as htmlToImage from 'html-to-image'screenshotLibrary: htmlToImage. A configured screenshotLibrary takes precedence over window.htmlToImage, and if it fails the error is surfaced rather than silently falling back.

Script-tag / IIFE users — load the UMD bundle before your ReproKit script tag. It exposes the window.htmlToImage global, which the SDK picks up automatically:

<script src="https://unpkg.com/[email protected]/dist/html-to-image.js"></script>
<script src="https://unpkg.com/@reprokitapp/[email protected]/dist/reprokit.global.js"></script>

If the library cannot be resolved through any mechanism, the SDK throws a ReproKitScreenshotError whose message includes the exact instructions for both modes — surfaced to the developer via the SDK logger, not to the end user.

Excluding nodes from capture

Add data-reprokit-ignore (or the class reprokit-ignore) to any element you don't want in the screenshot — for example, a sidebar containing account details:

<aside data-reprokit-ignore>…account details…</aside>

You can also configure a specific capture target:

new ReproKit({
  projectKey, apiUrl,
  reporting: {
    widget: true,
    screenshotTarget: '#app-shell'        // CSS selector, Element, or () => Element | null
  }
});

Advanced capture behavior

Failed network requests

Failed-request capture comes from the SDK's fetch wrapper and (when enabled) XMLHttpRequest wrapper — it is independent of console capture.

Default policy (status buckets):

| Bucket | What it covers | Default | |---|---|---| | network.captureNetworkErrors | status === 0: DNS, offline, CORS preflight failures, aborts, timeouts | on | | network.captureServerErrors | 5xx (500, 502, 503, 504, …) | on | | network.captureClientErrors | 4xx (400, 401, 404, 409, 422, …) | off (opt-in) | | network.captureXhr | also instrument XMLHttpRequest in addition to fetch | on |

2xx and 3xx are never captured. 4xx is off by default because client-error noise (auth refreshes, validation errors, abandoned 404s) tends to dwarf actionable signal at product scale; set network.captureClientErrors: true if you want it. URL sanitization, ignoreUrls, and ignoreMethods apply to everything captured.

XHR instrumentation

Enabled by default. Wraps XMLHttpRequest.prototype.open and .send to:

  • record method, requestUrl (sanitized), status, durationMs, and reason on loadend / error / timeout / abort events
  • apply the same bucket policy and captureStatuses override as fetch
  • skip the SDK self-ingest URL, ignoreUrls, and ignoreMethods
  • never read xhr.responseText, xhr.response, the request body, headers, or cookies
  • restore the original prototype methods on stop()

Disable with network: { captureXhr: false } if you only want fetch capture.

network.captureStatuses (advanced override)

When set to a non-empty array it wins — only those exact statuses are captured and the three bucket flags are ignored. Empty or undefined lets the buckets apply.

// Capture exactly these statuses, ignoring buckets:
network: { captureStatuses: [0, 408, 429, 500, 502, 503, 504] }

// Capture everything >= 400 via buckets:
network: { captureClientErrors: true, captureServerErrors: true, captureNetworkErrors: true }

Console capture

console.error and console.warn are independent toggles:

| Toggle | Patches | Default | |---|---|---| | capture.consoleErrors | console.error only | off | | capture.consoleWarnings | console.warn only | off |

new ReproKit({
  projectKey,
  apiUrl,
  capture: {
    consoleErrors: true,    // only console.error
    consoleWarnings: false  // console.warn untouched
  }
});

When both toggles are off the SDK does not patch console.* at all. Captured console arguments pass through the same redaction and truncation rules as other events.

Noise filters

To reduce browser-extension, iframe, and third-party-script noise from window.error and resource-error capture, the SDK applies safe defaults:

| Option | Effect | Default | |---|---|---| | filters.ignoreBrowserExtensions | Drops events whose source URL is chrome-extension://, moz-extension://, safari-web-extension://, etc. | on | | filters.ignoreIframes | Skips installing capture in non-top-level browsing contexts. | on | | filters.ignoreThirdPartyScripts | Applies ReproKit's bundled deny-list of known third-party telemetry noise (Amplitude, Google Analytics / Tag Manager, DoubleClick, …). | on | | filters.ignoredSourcePatterns | Customer-supplied deny-list. Wins over everything else — including allowedScriptOrigins and same-origin. Use it to suppress noisy first-party endpoints too. See pattern shapes below. | [] | | filters.allowedScriptOrigins | Allow-list of origins (in addition to location.origin) that pass — overrides the bundled list and extension drop, but not an explicit ignoredSourcePatterns match. | [] |

Precedence (highest first):

  1. ignoredSourcePatterns matches → drop (even same-origin, even if also on the allow-list)
  2. origin is on allowedScriptOrigins or equals location.originkeep
  3. ignoreBrowserExtensions and URL is an extension scheme → drop
  4. ignoreThirdPartyScripts and bundled list matches → drop
  5. otherwise → keep

Filtering applies to: failed network requests (fetch + XHR), resource errors, window.error/unhandled-rejection source URLs, and (when console capture is enabled) console events whose body or stack contain a matching URL. Same-origin app errors that lack a source URL are never dropped — the filter only acts on URLs it can positively classify.

Pattern shapes for ignoredSourcePatterns (the API enforces the same semantics server-side as defense-in-depth):

  • api2.amplitude.com — exact host match (case-insensitive)
  • *.amplitude.com — wildcard for subdomains only; does not match the bare amplitude.com root
  • chrome-extension:// — scheme prefix; matches any URL with that protocol
  • www.google.com/g/collecthost + path prefix; matches the exact path or any deeper segment under it (e.g. …/g/collect, …/g/collect/x, …/g/collect?v=2) but not …/g/collect2. Full-URL inputs like https://www.google.com/g/collect?v=2 are accepted and normalized down to host + path.

Paths are prefix-matched with a / boundary so a pattern like /g/collect does not absorb unrelated paths like /g/collect2. Query strings and fragments are ignored during matching. There is no regex support, no path wildcards, and no naive substring matching. Malformed patterns are silently dropped at config-normalization time so a typo doesn't accidentally suppress real signal.

new ReproKit({
  projectKey,
  apiUrl,
  filters: {
    ignoreBrowserExtensions: true,
    ignoreIframes: true,
    ignoreThirdPartyScripts: true,                 // apply the bundled noise list
    ignoredSourcePatterns: [                        // your additional deny-list
      'segment.io',
      '*.segment.com',
      '*.intercom-messenger.com'
    ],
    allowedScriptOrigins: ['https://cdn.example.com'] // exceptions that always pass
  }
});

To capture everything, including extension and third-party noise (useful while debugging an integration):

filters: {
  ignoreBrowserExtensions: false,
  ignoreIframes: false,
  ignoreThirdPartyScripts: false
}

Local debugging with onEvent

For local development or QA, pass an onEvent callback. It fires once per event after sampling and sanitization, before the event is queued. The SDK swallows any error your callback throws.

const rk = new ReproKit({
  projectKey: 'pk_test_local',
  apiUrl: 'https://api.example.com',
  debug: true,
  onEvent: event => {
    console.log('[reprokit]', event.type, event);
  }
});

Ingestion contract

The SDK POSTs batched events to ${apiUrl}/api/sdk/events. apiUrl is the API base only (e.g. https://api.reprokit.app); the SDK adds the path internally — never include /api/sdk/events in apiUrl yourself.

| Aspect | Behavior | |---|---| | Endpoint | POST {apiUrl}/api/sdk/events | | Auth | Anonymous. The browser project key is a public identifier, not a secret. | | Origin policy | The site's origin must be on the project's allowed-origins list, configured in the ReproKit admin. CORS allows the origin at the protocol layer; the API validates the allow-list per request. | | Body | JSON EventBatchPayload: projectKey, environment, release?, sdk: { name, version }, events[], sentAt (ISO 8601 string). | | Per-event timestamp | Epoch milliseconds. | | Header (fetch path) | X-ReproKit-Project: <projectKey> (also present in the body — the two must match). X-ReproKit-Sdk and X-ReproKit-Sdk-Version are sent for telemetry. | | Header (sendBeacon path) | sendBeacon cannot set custom headers. The SDK sends an application/json Blob with the body unchanged (projectKey still present in the body). | | Credentials | credentials: 'omit'. No cookies. | | Success | Any 2xx is treated as success. The SDK does not depend on the response body. | | Response (debug only) | When debug: true, the SDK opportunistically parses the response as SdkIngestResponse ({ accepted, rejected, stored, serverTime }) and logs it. | | Retry | 5xx, 408, and 429 are retried a limited number of times with exponential backoff. Other 4xx are terminal. | | Self-capture | The fetch-network capture short-circuits requests whose origin+path match the ingest URL, so SDK retries never become events. |

Limits enforced by the API

The API may reject individual events or whole batches that exceed these limits — counts appear in the SdkIngestResponse.rejected field:

  • Max batch size: 50 events.
  • Max individual event payload: 64 KB of serialized JSON.
  • Max request size: 4 MB.

The SDK applies its own conservative caps (strings 2,000 characters, stacks 4,000, depth-capped metadata redaction) so single events normally stay well under 64 KB.

Privacy guarantees

| Guarantee | Implementation | |---|---| | Input values never collected | No DOM serialization; inputs/textareas treated as sensitive | | Cookies never collected | The SDK does not read document.cookie | | Storage never collected | The SDK does not read localStorage / sessionStorage | | IP/location/ISP never collected | No geolocation API, no network lookups; device context is limited to browser-exposed facts (userAgentData, touch/pointer, viewport/screen, language, timezone) and is disabled via privacy.captureDeviceContext | | Request/response bodies never collected | The fetch wrapper never reads .body / .text() / .json() | | Sensitive query params redacted | ?token, ?access_token, ?api_key, ?password, … become [REDACTED] | | Sensitive headers redacted | Authorization, Cookie, X-Api-Key, … become [REDACTED] in any captured headers | | URL credentials redacted | https://user:pw@host/ has its password redacted | | SDK ingest requests never self-captured | The fetch wrapper short-circuits requests to the configured ingest URL | | SDK debug logs do not appear in console capture | The logger keeps its own references to the original console methods | | All capture callbacks are wrapped | No SDK error escapes into the host app after construction | | stop() restores patched APIs | window.fetch, XMLHttpRequest, console.*, and history.* are returned to their prior references |

Builds and browser support

| Output | Format | Purpose | |---|---|---| | dist/index.js | ESM | Modern bundlers, native ESM | | dist/index.cjs | CJS | Legacy Node-friendly callers | | dist/reprokit.global.js | IIFE | <script src="…"> usage; assigns window.ReproKit | | dist/index.d.ts / .d.cts | Types | Strict TS declarations |

  • Targets ES2020 and modern evergreen browsers. No old-browser polyfills are bundled.
  • SSR-safe: importing or constructing the SDK never touches browser globals; browser work begins at start(). In SSR frameworks, call start() from a client-only code path.
  • Zero runtime dependencies. html-to-image is an optional peer dependency used only for screenshot capture.

Not included

The SDK intentionally does not provide:

  • session replay, DOM snapshots, or screen recording
  • request/response body capture
  • cookie or localStorage/sessionStorage capture
  • OpenTelemetry, Sentry, Datadog, or Hotjar integrations
  • source map upload or release-tracking automation
  • a plugin system

License

MIT © ReproKit