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

peekaboo-events

v1.0.0

Published

Non-blocking React analytics hook powered by sendBeacon

Readme

Peekaboo Events

Peekaboo Events is a lightweight, non-blocking analytics library for React. It batches events, enriches them automatically with device/browser/session context, and flushes them via sendBeacon (with a fetch fallback) — so tracking never blocks the UI or affects user experience.

📖 Documentation Guide

Start here:

| Your Goal | Read This | |-----------|-----------| | I'm using this package | CONSUMER_GUIDE.md ⭐ — Setup, auth, patterns, production checklist | | Try it out quickly | DEMO_SETUP.md — Run the demo in 2 minutes | | Understand how it works | ARCHITECTURE.md — Data flow, merge order, lifecycle | | Debug CORS errors | CORS_TROUBLESHOOTING.md — DevTools debugging guide | | API reference | This README (scroll down) |


Installation

npm install peekaboo-events

or using Yarn:

yarn add peekaboo-events

ua-parser-js is installed automatically as a dependency (used for browser/OS/device detection). react is a peer dependency — your app must already have React ≥16.8 installed.

Setup

Call initAnalytics once, at your app's entry point, before any component tries to track events.

// main.tsx / App.tsx
import { initAnalytics } from 'peekaboo-events';

initAnalytics({
  trackingUrl: 'https://my-backend.com/api/analytics',
  instanceId: 'your-tenant-id',       // identifies this instance to your backend
  sessionProperties: {                // optional, session-level properties added to every event
    sessionKey: 'abc-123',
    userId: '[email protected]',
  },
  defaultProperties: {                // optional, default properties for every event
    appVersion: '2.4.1',
    environment: 'production',
  },
  flushIntervalMs: 5000,              // optional, default 5000
  maxQueueSize: 20,                   // optional, default 20
});

Events tracked before initAnalytics runs are dropped — make sure this call happens early (e.g. before your router mounts).

Usage

import { useAnalytics } from 'peekaboo-events';

const MyComponent = () => {
  const { trackEvent } = useAnalytics('MyComponent');

  return (
    <button onClick={() => trackEvent({ event: 'button_clicked', properties: { buttonId: 'submit' } })}>
      Submit
    </button>
  );
};

export default MyComponent;

Automatic lifecycle tracking

The hook automatically fires a component_unmounted event when the component unmounts — no need to call trackEvent manually for this.

Custom events and extra data

properties accepts any additional fields you want attached to a specific event — including nested objects for arbitrary metadata:

trackEvent({
  event: 'form_submitted',
  properties: {
    formId: 'signup',
    plan: 'pro',
    meta: { experimentVariant: 'B', referrer: 'email' },
  },
});

Sending data with every event

If you have data that should go out with every tracked event (app version, environment, build number, feature flags, etc.), set it once via defaultProperties in initAnalytics rather than passing it on every trackEvent call:

initAnalytics({
  trackingUrl: 'https://my-backend.com/api/analytics',
  instanceId: 'tenant-abc-123',
  key: 'encrypted-identifier-string',
  defaultProperties: {
    appVersion: '2.4.1',
    environment: 'production',
  },
});

Merge order per event is: auto-collected properties → defaultProperties → per-call properties. A per-call value always wins if the same key is used at more than one level.

Overriding the screen name

By default, onScreen is set to the component name passed into useAnalytics(componentName). You can override it per-call:

trackEvent({ event: 'modal_opened', onScreen: 'UpgradeModal' });

Identifying users

By default, events are attributed to an auto-generated, persisted distinctId (stored in localStorage, stable across sessions until cleared). Once you know who the user is, call identify:

import { identify, reset } from 'peekaboo-events';

// after login
identify(user.id);

// after logout
reset();

Once identified, distinctId reflects the known user ID instead of the anonymous one.

Middleware

Register middlewares to inspect, transform, or veto events before they're queued. Middlewares run in registration order — the output of one feeds into the next.

import { addMiddleware } from 'peekaboo-events';

// consent gate — drop events until the user accepts tracking
addMiddleware((event) => (hasUserConsented() ? event : null));

// scrub sensitive fields before they ever leave the browser
addMiddleware((event) => {
  delete event.properties.email;
  return event;
});

// dev-only logging, doesn't affect what's sent
addMiddleware((event) => {
  if (process.env.NODE_ENV === 'development') console.log('[peekaboo]', event);
  return event;
});
  • Return the event (modified or not) to let it continue to the next middleware and eventually the queue.
  • Return null to drop the event entirely — no further middlewares run, and nothing is queued.
  • If a middleware throws, only that event is dropped; tracking continues normally for subsequent events.
  • Middlewares can be registered any time — via addMiddleware() directly, or up front through initAnalytics({ middlewares: [...] }).

How it works

  1. Batching: Events are queued in memory and flushed together, not sent one-by-one.
  2. Flush triggers: Automatic on an interval (flushIntervalMs), when the queue hits maxQueueSize, when the tab is hidden, and on beforeunload.
  3. Non-blocking delivery: Flushes use navigator.sendBeacon where available; falls back to fetch with keepalive: true if the beacon fails or isn't supported.
  4. Automatic enrichment: Every event is stamped with a uuid, timestamp, and a properties object containing device, browser, OS, session, and identity context — collected automatically from the browser.
  5. Lifecycle tracking: component_unmounted fires automatically per hook instance.

Event shape

Each tracked event looks like this:

{
  "uuid": "019f4b6e-d6f7-7c10-9237-d21ade04159a",
  "event": "button_clicked",
  "timestamp": "2026-07-10T09:49:43.591Z",
  "properties": {
    "deviceType": "Desktop",
    "browser": "Chrome",
    "browserVersion": "148.0.0.0",
    "browserLanguagePrefix": "en",
    "rawUserAgent": "Mozilla/5.0 ...",
    "os": "Mac OS X",
    "osVersion": "10.15.7",
    "screenWidth": 3440,
    "sessionId": "019f4b54-7f96-7419-a82a-0987938a936c",
    "distinctId": "019f3bf9-0f7e-7149-80f8-0d9df0163053",
    "timestamp": "2026-07-10T09:49:43.030Z",
    "key": "encrypted-identifier-string",
    "onScreen": "MyComponent",
    "buttonId": "submit"
  }
}

Fields collected automatically: uuid, timestamp, deviceType, browser, browserVersion, browserLanguagePrefix, rawUserAgent, os, osVersion, screenWidth, sessionId, distinctId, key, onScreen (defaulted). Anything in config.defaultProperties or passed in properties on trackEvent is merged in alongside these.

createdAt is intentionally not set client-side — your backend should stamp this on ingest.

API reference

| Export | Description | |---|---| | initAnalytics(config) | Configures tracking URL, instance ID, identifier key, default properties, and flush behavior. Call once at bootstrap. | | useAnalytics(componentName) | React hook. Returns { trackEvent } scoped to the given component name. | | identify(userId) | Associates future events with a known user's distinctId. | | reset() | Clears identity; future events fall back to the anonymous distinctId. | | track(input) | Low-level, framework-agnostic event tracking — use outside React if needed. | | flush() | Manually force an immediate flush of the queue. | | addMiddleware(fn) | Register a middleware to inspect, transform, or veto events before they're queued. |

initAnalytics config

| Option | Type | Required | Default | Purpose | |---|---|---|---|---| | trackingUrl | string | Yes | — | Endpoint that receives event batches | | instanceId | string | Yes | — | Identifies this app instance (sent separately, not in events) | | sessionProperties | Record<string, unknown> | No | undefined | Session-level data added to every event (e.g., sessionKey, userId) | | defaultProperties | Record<string, unknown> | No | undefined | Default data merged into every event (e.g., appVersion) | | flushIntervalMs | number | No | 5000 | How often to send queued events (milliseconds) | | maxQueueSize | number | No | 20 | Send immediately when queue reaches this size | | middlewares | Middleware[] | No | undefined | Event processors/filters (run before queueing) |

Backend integration

Each flush POSTs a single JSON body to trackingUrl:

{
  "instanceId": "your-tenant-id",
  "events": [ /* array of event objects, shape above */ ]
}

Your backend can use instanceId to route each batch to the correct downstream provider (e.g. CleverTap, Adobe) with the correct per-tenant credentials, and should stamp createdAt on receipt.

Files included

  • src/: Source code — core.ts (framework-agnostic queueing, enrichment, and flush logic) and useAnalytics.ts (the React hook).
  • dist/: Compiled output for distribution.
  • package.json: Package metadata and build scripts.

📚 Documentation


Contributing

Feel free to open issues or submit pull requests. Contributions are welcome!

License

MIT License. See LICENSE for details.