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

@heycatch/sdk

v0.8.3

Published

HeyCatch SDK

Readme

@heycatch/sdk

The official HeyCatch package for your app.

(AI Agents: the setup guide is https://heycatch.ai/agents.md - you also need the project key from the user's HeyCatch dashboard Install page.)

Privacy & consent options

init() accepts optional privacy/consent options alongside projectKey. Every one is unset by default, and each takes effect only when you set it - so init({ projectKey }) on its own captures exactly as it always has:

analytics.init({
  projectKey: 'hck_pk_...',
  autocapture: false, // turn off automatic click/form-submit capture (pageviews and rage-clicks are captured separately, unaffected)
  maskAllText: true, // strip captured elements' text content
  maskAllElementAttributes: true, // strip captured elements' attributes
  propertyDenylist: ['some_property'], // drop specific properties from every event (HeyCatch's own attribution keys can't be denylisted)
  beforeSend: event => event, // runs on every event right before it's queued; return null to drop it (the SDK's own delivery and attribution keys are put back after it)
  persistence: 'localStorage', // where the distinct id + super properties are stored
  disablePersistence: false, // disable persistence entirely
  cookieExpirationDays: 365, // days before the persistence cookie expires
  optOutCapturingByDefault: true, // start with capturing off - see below
  respectDnt: true, // honor the browser's Do Not Track signal
});

In the browser, setting autocapture: false, maskAllText: true, or maskAllElementAttributes: true logs a one-line console notice naming what it costs you in the dashboard. (The other entries ignore these options, so they have nothing to warn about - see the platform notes below.)

URL tokens are redacted

Some sign-in flows put the session in the page URL - #access_token=… and refresh_token=… after an OAuth or magic-link callback, ?token=… or ?token_hash=… in the link itself. The SDK replaces those values with [redacted] in every event before it leaves the app: every string in an event's properties is checked (the page URL, the referrer, the first URL stored on a person, your own properties), for the parameter names access_token, refresh_token, token_hash and token, and for anything shaped like a JWT. The parameter name stays, so you can still see which page it was. This is always on, has no option, and runs after your beforeSend, so a hook cannot put a token back. It applies in the browser, in React Native, and on the server alike.

Consent gate

Set optOutCapturingByDefault: true to capture nothing until your visitor agrees (behind a cookie banner, for example), then:

analytics.optInCapturing(); // consent granted - start capturing
analytics.optOutCapturing(); // consent withdrawn - stop again

While the gate is closed nothing is written to browser storage either, so no identifier lands before consent - not just "no events sent".

Call init() before either one. Neither is remembered if it runs first - there is no transport yet to carry the decision - and both warn on the console when that happens rather than failing quietly. init()'s own config always wins, so a call made too early can never silently flip optOutCapturingByDefault back on.

optInCapturing / optOutCapturing exist on every entry, so shared code compiles everywhere. They are a real gate in the browser and in React Native. On the server they are no-ops: a server event has no ambient visitor to consent for - each one already names its userId and only happens because your code called it.

The remaining options above are browser behaviour. React Native honours optOutCapturingByDefault; the rest describe a DOM and a browser store it does not have. The server entry accepts the same HeyCatchConfig so one object works under every import condition, and ignores them.

Delivery options

By default the SDK batches captured events for a few seconds before sending them, which is right for a single-page app. On a multi-page site - one where each click is a full page load - a visitor can navigate away inside that window, and iOS Safari in particular does not reliably let the batch out as the page is torn down. The symptom is mobile pageviews missing from your dashboard while desktop looks fine. Two options, both browser-only and both unset by default:

analytics.init({
  projectKey: 'hck_pk_...',
  requestBatching: false, // send every event as it happens - the fix for multi-page sites
  flushIntervalMs: 250, // or keep batching but shrink the window (250-5000 ms; default 3000)
});

Set one or the other - with batching off the interval has nothing to act on. The cost is one request per captured event, and autocapture is on by default: the count follows how much a visitor clicks, not how many pages they open, so a busy page sends more requests than a quiet one. flushIntervalMs: 250 is the middle ground - still batched, but a window short enough that a navigation rarely lands inside it.

Volume limits

A tracking call inside a render loop or an effect can send thousands of events from one browser tab, and nothing about the app looks broken while it does. So the SDK bounds what one session can send. None of this needs configuring. The first three limits are the SDK's own, and each logs one [HeyCatch] … console warning the first time it applies on a page, so a runaway loop is visible where you are already looking:

  • 5,000 events per session. Past that, the rest of the session is dropped until a new one starts. Real sessions do not get near this - it is a backstop for a loop, not a quota for a busy user.
  • 60 person-property updates a minute. Call setPersonProperties when a property changes, not on every render. Updates past the budget are dropped; pageviews and other events in the same session still flow.
  • localhost is throttled, not filtered out. A session on localhost, 127.0.0.1 or a private-network host sends its first 1,000 events like any other, then 60 a minute. A dev server hot-reloading for an hour cannot flood your dashboard, and your first local test still shows up on the Install page. Only this throttle is limited to local hosts: a production session meets the other three limits like any other, and a local session that reaches the cap is cut off like any other.
  • 5 events a second, sustained (browser only). Bursts of up to 50 pass; a steady stream faster than that is dropped. This one applies from the first event, on every host, and it belongs to the transport rather than the SDK, so there is no [HeyCatch] warning for it: the transport logs its own console error, under its own prefix, for each call it drops.

The session cap and the person-property budget apply in React Native too. The localhost limit does not: a native event has no page host, so there is nothing to tell a simulator from a phone. Server events are not counted at all - each one is a call your own code made.

React Native screen views

On Expo Router, <HeyCatchProvider> records a screen view each time the route changes, named by the path (/settings, /recipe/42). The route's search params ride along as properties, minus anything that could identify a person or unlock an account: the # fragment, keys such as email, code, token, password, passcode, secret, session, otp, pin, auth… and anything containing key, plus any value that looks like an email address. Attribution params such as utm_* and ids are kept.

If your app already records screens itself with analytics.trackScreen, turn the automatic ones off so each screen is not counted twice:

<HeyCatchProvider trackScreens={false}>{children}</HeyCatchProvider>

License

MIT