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

easy-analytics-tracker

v0.1.0

Published

Lightweight, dependency-free browser client for first-party behavioural analytics. Batches events in memory and delivers them to your own HTTP endpoint via sendBeacon or fetch.

Readme

easy-analytics-tracker

A lightweight, dependency-free browser client for first-party behavioural analytics. It batches track() calls in memory and delivers them to an HTTP endpoint that you operate. No data is sent to third-party services.

The package provides a framework-agnostic core and an optional React adapter.

Installation

npm install easy-analytics-tracker

react and react-dom are optional peer dependencies, required only when importing from easy-analytics-tracker/react.

Quick start

import { createTracker } from "easy-analytics-tracker";

export const tracker = createTracker({
  endpoint: "https://api.example.com/analytics/events",
  source: "web-app",
  getToken: () => sessionStorage.getItem("access_token"),
});

tracker.track("menu_item_clicked", { item: "agenda" });

Omit getToken on surfaces where users are not authenticated. Events are then delivered without an Authorization header and treated as anonymous by the receiving service.

React

import { TrackerProvider, useTracker } from "easy-analytics-tracker/react";

function Root() {
  return (
    <TrackerProvider
      config={{
        endpoint: "https://api.example.com/analytics/events",
        source: "web-app",
        getToken: () => sessionStorage.getItem("access_token"),
      }}
    >
      <App />
    </TrackerProvider>
  );
}

function SaveButton() {
  const { track } = useTracker();
  return (
    <button onClick={() => track("document_saved", { format: "pdf" })}>
      Save
    </button>
  );
}

TrackerProvider reads config once, when it mounts. To apply a different endpoint or source at runtime, remount the provider with a new key.

Behaviour

Non-blocking. track() never throws and never awaits network activity on the calling path. Each event is appended to an in-memory ring buffer and delivered later as part of a batch.

Batching. A flush is triggered by whichever of the following occurs first: the flush interval elapses (10 seconds by default), the buffer reaches the flushAt threshold (20 events by default), the document becomes hidden, or the page begins to unload. Each request carries at most 50 events and stays under 64 KB; larger backlogs are split across consecutive requests.

Transport. Anonymous batches under 64 KB are sent with navigator.sendBeacon, which is guaranteed to complete during page unload. Authenticated batches, and any batch that exceeds the beacon size limit, are sent with fetch using keepalive: true, because the Beacon API cannot set an Authorization header.

Retries. A network failure, a 429, or a 5xx response causes the batch to be returned to the buffer and retried with exponential backoff (10 s, 20 s, 40 s, 80 s, then 120 s between attempts). Any other 4xx response is treated as a permanent rejection and the batch is discarded. The buffer holds 1,000 events; when full, the oldest events are dropped. Nothing is persisted across page reloads.

Identity. The client never transmits a user identifier. When getToken returns a value, it is sent as a bearer token and the receiving service is expected to resolve the user from it. Anonymous sessions are keyed by a random UUID stored in localStorage under eds_anon_id, which persists until reset() is called.

API

createTracker(config): Tracker

| Option | Default | Description | | -------------------- | ---------- | ------------------------------------------------------------------ | | endpoint | required | Absolute URL that batches are posted to. | | source | required | Identifier for the emitting application, included in every batch. | | getToken | — | Returns the current bearer token, or null/undefined if none. | | flushIntervalMs | 10000 | Interval between timer-driven flushes. | | flushAt | 20 | Buffer size that triggers an immediate flush. | | bufferCap | 1000 | Maximum buffered events; the oldest are dropped beyond this. | | maxPropertiesBytes | 12288 | Events whose serialised properties exceed this size are dropped. | | debug | false | Log dropped events and transport failures to the console. |

getToken is invoked on every flush and its result is never cached. If it throws, the batch is sent as anonymous.

Tracker

| Method | Description | | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | track(name, properties?) | Queue an event. name must match ^[a-z0-9]+(_[a-z0-9]+)*$ and be at most 100 characters. properties is an optional JSON object and should not contain personal data. | | identify(userId) | Call after authentication. Emits a user_identified event and flushes immediately so that events queued while anonymous are delivered under the same session. Repeat calls with the same id are ignored; the id itself is not transmitted. | | reset() | Call on sign-out. Clears the identified user and rotates the anonymous id. | | flush() | Send buffered events now, bypassing any active backoff. Resolves when the attempt has settled. | | shutdown() | Remove timers and event listeners. The tracker is inert afterwards. |

Server contract

Each batch is delivered as a single POST request with Content-Type: application/json. When getToken returns a value, the request also carries Authorization: Bearer <token>.

{
  "source": "web-app",
  "events": [
    {
      "event_id": "d94f9c8e-4a2b-4c1d-9e3f-1a2b3c4d5e6f",
      "event_name": "menu_item_clicked",
      "occurred_at": "2026-09-01T10:32:11.204Z",
      "anonymous_id": "b1c2d3e4-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
      "properties": { "item": "agenda" },
      "url": "https://app.example.com/agenda"
    }
  ]
}

| Field | Description | | -------------- | ------------------------------------------------------------------------------------------------------------ | | event_id | UUID generated per event. A retry following a partial failure may resend an event, so deduplicate on this key if exactly-once semantics are required. | | event_name | The name passed to track(). | | occurred_at | ISO 8601 UTC timestamp of the track() call. | | anonymous_id | The per-browser anonymous id described above. | | properties | The object passed to track(), if any. | | url | The page URL at the time of the call, with the query string removed. |

The receiving service should respond with any 2xx status; the client does not read the response body. Respond with 429 or 5xx to request a retry, or with any other 4xx to have the batch discarded.

Development

npm install
npm run typecheck
npm test
npm run build

npm run build uses tsup to emit ESM, CommonJS, and type declarations to dist/.

Releases are published to npm from CI when a v* tag is pushed.

License

MIT