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

@reflect-sdk/web

v1.3.0

Published

Reflect MMP Web SDK — session tracking, UTM capture, web-to-app attribution, and cross-domain tracking.

Readme

@reflect-sdk/web

Reflect MMP Web SDK — session tracking, UTM capture, web-to-app attribution, and cross-domain tracking for web properties.

It is lightweight, dependency-free, and ships as ESM, CommonJS, and browser UMD bundles with TypeScript types. The package's conditional exports support both import { ReflectWeb } from "@reflect-sdk/web" and const { ReflectWeb } = require("@reflect-sdk/web").

Install

Install the package from npm:

npm install @reflect-sdk/web
# or: yarn add @reflect-sdk/web

Or drop the UMD bundle in with a script tag (served via jsDelivr from npm or GitHub):

<script src="https://cdn.jsdelivr.net/npm/@reflect-sdk/[email protected]/dist/reflect-web.umd.js"></script>

v1.3.0 is the first published tag carrying the privacy/outbox behavior documented below — do not install a mutable branch or an older tag (v1.1.0 and earlier do not prove it). If the v1.3.0 tag is not visible yet, the release is mid-rollout; wait for it rather than substituting an old tag.

Quick start

Initialize the SDK once, as early as possible in your page lifecycle, then track events.

import { ReflectWeb } from "@reflect-sdk/web";

// Initialize once with your app key.
ReflectWeb.initialize({
  appKey: "YOUR_APP_KEY",
  autoPageView: true,
  debug: true,
});

// Track a custom event with optional properties.
ReflectWeb.trackEvent("signup", { plan: "pro" });

initialize automatically captures the session, coarse campaign attribution, and external referrer when consent permits it. Durable URLs retain only their origin/path: query strings and fragments are removed before an event is queued. Automatic campaign capture is limited to sanitized utm_source, utm_medium, and utm_campaign; utm_content, utm_term, click-ID parameters, URL/query syntax, UUID-like values, and opaque token-shaped values are not persisted. For a Reflect tracking-link landing URL, the SDK reads the nested referrer parameter only after consent and keeps only a validated 32-hex Reflect click ID. That minimal value is attached only to an explicit app_install event so the server can perform deterministic attribution; other nested referral fields and the landing query never enter the durable outbox. Call initialize only once — subsequent calls are ignored.

Configuration

| Option | Type | Description | | ------------------- | ---------- | ------------------------------------------------------------ | | appKey | string | Required. Your Reflect app key. | | companyKey | string | Optional company key, attached to every event. | | baseUrl | string | Override the ingestion endpoint. Defaults to the Reflect API. | | debug | boolean | Log SDK activity to the console. | | cookieDomain | string | Scope the first-party cookie (e.g. ".example.com"). | | crossDomainDomains| string[] | Domains used for cross-domain tracking link decoration. | | autoPageView | boolean | Auto-track a page view on init. Default false. | | requireConsent | boolean | Start fail-closed: no identifiers, cookies, collection, or transmission until setConsent(true). | | privacyDeleteHandler | function | Preferred erasure hook. Sends the pending request through your authenticated first-party backend. | | signingSecret | string | Optional direct HMAC credential used only by deleteUserData(). Avoid embedding privileged production secrets in public pages. |

Consent and local durability

With requireConsent: true, Reflect resolves persisted consent before doing any tracking work. Until consent is granted, it creates no install/session cookies, collects no fingerprint or URL attribution data, and queues or sends no events. Calls that identify a user are ignored while consent is denied.

Consent is an origin-wide durable authority mirrored in localStorage and IndexedDB. A durable denial always outranks a stale grant, including when one storage API is blocked, and each cross-tab update carries a privacy generation so an old grant cannot resurrect identity after revocation. A grant is not observable and cannot mint cookies until every available authority accepts it; if no durable authority is available, tracking remains denied.

Calls made while the authority is loading or a grant is committing are replayed in API order. This includes profile setters and destructive operations such as clearEmail(), unsetGlobalProperty(), and clearGlobalProperties(), so a next-line call cannot overtake consent readiness or restore older profile data. An immediate denial is durably ordered ahead of a destroy/reinitialize, even if the old lifecycle's authority load was still pending. When localStorage is blocked, an active sibling rechecks the IndexedDB authority at collection and send boundaries so a missed notification cannot leak an event after denial.

Calling setConsent(false) is an immediate privacy boundary: active event requests are aborted, _reflect_uid and _reflect_sid are expired, user and attribution state is cleared, and the event outbox is purged. A later grant starts with fresh identifiers. decorateUrl() also becomes a no-op while denied.

Configured cookie domains are recorded in a versioned scope registry. Privacy clears expire host-only cookies, the current scope, historical configured scopes, and valid hostname suffix candidates, including duplicate host/domain cookies left by older configurations.

Accepted events are written to an IndexedDB outbox before dispatch (with a localStorage fallback where IndexedDB is unavailable). A retryable batch keeps the same batch_id across network failures, HTTP 408/429/5xx responses, malformed acknowledgements, reloads, and browser restarts. It is removed after a structurally valid 2xx acknowledgement. Other 4xx responses are terminal and are removed so one invalid client batch cannot poison the queue head. Server retry_in and continue_in pacing directives are honored.

The 1,000-event ceiling is reserved atomically with each write, including across tabs. If IndexedDB later recovers after a page used localStorage, the SDK merges the fallback records back into the primary outbox before claiming a batch, so a backend transition cannot strand events. Migration assigns stable batch IDs, uses a crash tombstone to stop the fallback copy from sending concurrently, and reapplies the hard cap without duplicating records. Rows from an obsolete privacy generation are purged in the same transaction that reserves space for the first post-grant event. Server continue_in pacing is durable across SDK instances and reloads rather than being only an in-memory timer.

Queued events have a hard 90-day client retention boundary measured from the earlier of their event source time and outbox enqueue time, so a delayed write or backend migration cannot restart the source-age clock. A row exactly 90 days old is expired. IndexedDB is swept when the namespace opens and whenever it is counted, claimed, or written; the localStorage fallback is swept on the same operations and before any recovery migration. Legacy rows without a privacy generation follow the same age rule, and malformed rows without a finite creation/source time are discarded. Expired rows do not consume the 1,000-event capacity and cannot be claimed for transmission.

Same-origin tabs using the same app coordinate through an expiring, renewable batch-owner lease. Only the current owner may post, acknowledge, or retry a batch; a crashed owner can be recovered after expiry, while a stale owner is fenced from deleting the recovered data. Ownership is revalidated immediately before transport and again before acknowledgement/retry; a resumed tab whose lease expired cannot act on a late response. The localStorage fallback also serializes read-modify-write operations so concurrent tabs cannot overwrite one another's events. Storage critical sections use renewable origin-lock leases; heartbeat failure or an owner-token change fences the stale operation before it can report success.

The Web SDK requires an app configured for unsigned browser ingestion. Any terminal ingestion 401 (for example signature_required, unknown_app_key, or company_suspended) is treated as a configuration error, not retried as an outage: the rejected batch is terminalized, new collection stops for the current lifecycle, and the console explains how to enable browser/legacy mode. After the app policy is corrected, call destroy() and initialize() to begin a fresh lifecycle; newly collected events can then be delivered normally.

Identify: setEmail / clearEmail

The consent-gated identify API associates the current visitor with an email address for email-based attribution and improved Conversions API (CAPI) match quality.

// Associate the visitor with an email.
ReflectWeb.setEmail("[email protected]");

// Stop sending the email with subsequent events.
ReflectWeb.clearEmail();

Key behaviour:

  • Raw email, server-side hashing. setEmail sends the raw email on the wire — the Reflect server hashes it. Do not pre-hash the value yourself.
  • Consent-gated and erased on withdrawal. The email is accepted only while consent is granted. ReflectWeb.setConsent(false) erases email, user/global properties, identifiers, and queued analytics; it does not retain them for a later grant.
  • Purpose. Email attribution plus a stronger match signal for the Conversions API (CAPI).

Pair it with the consent API to respect the visitor's choice:

ReflectWeb.setConsent(true); // "granted" — starts a fresh identity
ReflectWeb.setEmail("[email protected]");

ReflectWeb.setConsent(false); // "denied" — email/state is erased and events stop

setEmail should be called after you obtain the address (for example, on sign-up or login). Call clearEmail on logout. After a later consent grant, re-supply any identity/property values the user still wants associated; denied-period values are never replayed.

Privacy deletion

deleteUserData() closes identity getters and collection synchronously. Before cookies are erased or denial is broadcast, it writes a same-origin crash-barrier journal. The normal journal uses localStorage and IndexedDB; if both APIs are unavailable, a versioned restrictive cookie tombstone records the minimum UUID and immutable dispatch binding needed for recovery. The active identity cookie is still erased and that UUID is never reused. The full journal and event-outbox purge complete behind the barrier. A destroy/reinitialize or consent grant cannot reuse identity while this work is pending. The deletion journal merges both localStorage and IndexedDB records after storage recovery. The method resolves:

  • true only when there was no server-side identifier to erase, or the configured deletion transport durably accepted the request;
  • false when authentication is missing or delivery/acknowledgement failed.

A false result is not silent loss: the pending erasure survives reload/restart and is retried on the next initialize(), without restoring cookies, identifiers, consent, or events. Calling deleteUserData() again also retries it.

Each deletion journal is immutably bound to its originating appKey, optional companyKey, normalized baseUrl, and transport class (first-party handler or direct Reflect API). A later initialization with different tenant/environment routing will not claim or retarget that UUID. Keep the original routing configuration—and, for a handler, routing-compatible handler behavior—available until the journal drains. Restoring the matching configuration resumes delivery.

Deletion handlers, signing, fetch, and response parsing share a 30-second attempt deadline. An attempt has a 60-second durable owner lease across tabs; timeouts and other unknown outcomes retain that lease so a second page cannot immediately issue a duplicate deletion. If neither IndexedDB nor localStorage can durably journal an existing UUID, the call returns false and keeps collection closed behind the cookie tombstone rather than preserving or reusing the tracking identity cookie. A matching future configuration can retry the record, and only a validated remote acknowledgement removes the tombstone. On startup, any crash-incomplete deletion finishes denial, cookie/outbox purge, and journal completion before remote delivery is attempted.

The recommended browser integration is an authenticated first-party backend:

ReflectWeb.initialize({
  appKey: "YOUR_APP_KEY",
  privacyDeleteHandler: async ({ appKey, companyKey, installUuid }) => {
    const response = await fetch("/api/reflect/privacy-delete", {
      method: "POST",
      credentials: "same-origin",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ appKey, companyKey, installUuid }),
    });
    return response.ok;
  },
});

Your backend should authenticate the user, HMAC-sign the exact {"install_uuid":"…"} body, forward it to Reflect with X-Reflect-App-Key, X-Reflect-Signature, and optional X-Reflect-Company-Key, and return success only for Reflect's validated 202 { ok: true, queued: true } acknowledgement.

Direct HMAC delivery is available with signingSecret, but browser-delivered secrets are discoverable and should not be treated as strong authentication on a public page.

Other APIs

  • trackPageView(url?) — track a page view.
  • trackRevenue({ amount, currency, productId?, transactionId? }) — track revenue.
  • setUserId(userId) / clearUserId() — set or clear the user identifier.
  • setUserProperties(props) — attach user-level properties.
  • setGlobalProperty(key, value) / unsetGlobalProperty(key) / clearGlobalProperties() — manage properties merged into every event.
  • setConsent(granted) / getConsent() — manage data-collection consent through the origin-wide localStorage + IndexedDB authority; denial purges tracking data.
  • decorateUrl(url) — append the cross-domain tracking parameter to a link.
  • getWebSessionId() / getInstallUuid() — read the current session and install identifiers.
  • setEnabled(enabled) — enable or disable event tracking.
  • deleteUserData() — GDPR deletion: clears locally, durably records erasure intent, and requests server-side deletion.
  • flush() / destroy() — dispatch the acknowledged durable outbox or tear down the SDK. flush() returns a promise for the current attempt. destroy() invalidates deferred work and clears app-scoped in-memory identity/profile/attribution state before the object may be initialized for another app; durable consent and unsent app-key-namespaced outbox data remain authoritative.

Typed standard-event helpers (e.g. signUpWith, checkoutBegan, addedToCart) are also exported via ReflectWeb; see StandardEventNames for the full taxonomy. (For purchases/revenue use trackRevenue({ amount, currency }).)

License

MIT