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

p1-chaos-tracker

v0.9.0

Published

Intent-aware analytics tracker — behavioral signals, funnel classification, component visibility tracking, AI crawler tracking

Readme

p1-chaos-tracker

Intent-aware analytics tracker that classifies visitor behavior into funnel stages — Awareness, Interest, Consideration, Decision, Evaluation — with FAISS-ready signal vectors.

Install

npm install p1-chaos-tracker

Usage

Script tag (CDN)

<script defer data-site-id="YOUR_SITE_ID" src="https://unpkg.com/p1-chaos-tracker/dist/chaos-tracker.iife.js"></script>

The tracker auto-initializes when data-site-id is present. Access the API via window.ChaosTracker.

ES Module

import { initChaosTracker } from 'p1-chaos-tracker';

const tracker = initChaosTracker({ siteId: 'YOUR_SITE_ID' });

CommonJS

const { initChaosTracker } = require('p1-chaos-tracker');

const tracker = initChaosTracker({ siteId: 'YOUR_SITE_ID' });

Next.js / React

'use client';
import { useEffect, useRef } from 'react';
import { initChaosTracker } from 'p1-chaos-tracker';

export function ChaosProvider({ siteId }) {
  const tracker = useRef(null);

  useEffect(() => {
    tracker.current = initChaosTracker({ siteId, allowLocalhost: true });
    return () => tracker.current?.destroy();
  }, [siteId]);

  return null;
}

Config

| Option | Type | Default | Description | |--------|------|---------|-------------| | siteId | string | null | Your site identifier | | apiBase | string | "/api/v1/intent" | API base URL for event ingestion | | allowLocalhost | boolean | false | Enable tracking on localhost | | hasConsent | (signals) => boolean | null | Consent callback; receives { gpc, dnt, optedOut }. If omitted, consent falls back to GPC/DNT + the opt-out flag | | overrideBrowserSignals | boolean | false | Allow a true consent result to override an active GPC/DNT signal (e.g. Brave). Requires a lawful basis; optOut() still wins | | hashIdentifiers | boolean | false | SHA-256 hash hashFields traits client-side before sending. Fails closed (drops the field) if Web Crypto is unavailable | | hashFields | string[] | ["email","name","phone"] | Trait keys treated as PII for hashing | | captureUrlParams | boolean \| string[] | false | Query-string capture on url/referrer. false strips all; true keeps all; array = allowlist. Hash fragment always stripped | | samplingRate | number | 1 | Per-visitor fraction (0–1). A visitor is always or never tracked, so sessions stay complete. Sampled-out → init returns null | | localIntentOnly | boolean | false | Privacy mode: resolve intent on-device and send only compact intent_snapshot events — no raw click/scroll/URL stream. See INTENT_MODEL.md. Suppresses conversion events — conversions require full mode | | detectPaymentParams | boolean | true | Auto-detect payment success-page return params on init + SPA nav and emit a conversion once per id: Stripe session_id (cs_…), Polar checkout_id, LemonSqueezy order_id. Only the provider + opaque id are sent — never PII |

Bot assessment (report-only)

The tracker classifies likely bots on the client and emits the result on every session_startit does not block init or drop events. Crawlers, headless browsers, and automation tools are tracked like any other visitor so the backend can segment them in reports.

import { isBotUA, assessBotSignals } from 'p1-chaos-tracker';

assessBotSignals({
  user_agent: navigator.userAgent,
  webdriver: navigator.webdriver === true,
});
// → { flags, score, is_likely_bot, suppress_init }

session_start.data.device includes:

| Field | Description | |-------|-------------| | user_agent | Raw UA string | | bot_assessment.flags | Active signals, e.g. bot_ua, webdriver, no_plugins | | bot_assessment.score | 0–100 likelihood score | | bot_assessment.is_likely_bot | true when score ≥ 40 or a hard automation flag fired |

The Go ingest service (sublime-go) persists these fields on chaos.visitor_states and surfaces likely_bot_visitors in daily rollups / dashboard timeseries. See BACKEND_EVENT_SCHEMA.md.

AI / search crawler tracking (server-side)

Named AI agents (GPTBot, ChatGPT-User, ClaudeBot, PerplexityBot, …) usually fetch raw HTML and skip browser JavaScript, so they never hit the web tracker. Use the server package to report those requests separately from human intent.

npm install p1-chaos-tracker
// middleware.ts (Next.js) — do NOT await when you pass event
import { trackAICrawlerRequest } from "p1-chaos-tracker/server/ai-crawlers";

export function middleware(request, event) {
  trackAICrawlerRequest(request, event, {
    siteId: process.env.CHAOS_SITE_ID!,
    apiBase: process.env.CHAOS_API_BASE || "https://ap1.mathematica.ai/api/v1/intent",
    apiKey: process.env.CHAOS_API_KEY, // optional Bearer
  });
  return NextResponse.next();
}
// Express (Node 18+) — non-blocking via res.finish
import { createExpressAICrawlerMiddleware } from "p1-chaos-tracker/server/ai-crawlers";

app.set("trust proxy", 1);
app.use(
  createExpressAICrawlerMiddleware({
    siteId: process.env.CHAOS_SITE_ID!,
    apiBase: process.env.CHAOS_API_BASE!,
    publicOrigin: process.env.PUBLIC_ORIGIN, // Docker / Cloud Run
  })
);

| Concern | Browser bot_assessment | server/ai-crawlers | |---------|--------------------------|----------------------| | Runtime | JS sessions | Edge / Node middleware | | Taxonomy | Likelihood score | provider × agent × category | | Storage | visitor_states | POST …/ai-crawls (no FAISS) | | Discovery files | — | robots.txt, llms.txt, sitemaps |

Categories (client hints): answer_fetch | search_index | training | other.
Backend reclassifies from raw UA + IP.

Docs: AI_CRAWLER_INTEGRATION.md (setup) · docs/AI_CRAWLER_TRACKING_DESIGN.md (architecture)

Intent-only import is unchanged: import { createIntentClient } from "p1-chaos-tracker/server".

API

const tracker = initChaosTracker({ siteId: 'xxx' });

// Custom event
tracker.track('plan_toggle', { plan: 'pro' });

// Record a conversion (the outcome label the intent model learns from).
// value is coerced to a number; currency/order_id/provider are trimmed;
// any extra fields are preserved. Never pass raw PII (e.g. an email).
tracker.convert({ value: 49, currency: 'USD', order_id: 'ord_123', provider: 'stripe' });
// Stripe / Polar / LemonSqueezy success pages are also auto-detected from the
// return URL (see `detectPaymentParams`), so no explicit call is needed there.
// Note: conversions are suppressed in `localIntentOnly` mode.

// Identify user after login
tracker.identify('user_123', { email: '[email protected]', plan: 'pro' });

// Get current intent (for UI morphing or Stripe linkage)
const intent = tracker.getIntent();
// → {
//     stage: 'consideration', score: 3, confidence: 0.78,
//     distribution: { awareness: 0.05, interest: 0.12, consideration: 0.78, decision: 0.04, evaluation: 0.01 },
//     frustrated: false,
//     stage_history: [{ stage: 'awareness', at }, { stage: 'consideration', at }],
//     time_to_current_stage_ms: 42000,
//     signal_sequence: ['first_visit', 'pages_gte_2', 'pricing_over_30s'],
//     signals: [...], visitor_id: 'ct_...', session_duration, pages_viewed, scroll_depth
//   }
// See INTENT_MODEL.md for how the score, decay, and distribution work.

// Fire a signal manually
tracker.signal('roi_calc_use');

// Cart tracking
tracker.cartAdd({ product: 'Pro Plan', price: 49 });
tracker.cartComplete();

// Search tracking
tracker.searchQuery('enterprise pricing');

// Flush events immediately
tracker.flush();

// Cleanup (removes all listeners, restores history, flushes)
tracker.destroy();

Data Attributes

Declarative tracking without code:

<!-- Track clicks by intent signal -->
<button data-chaos-track="demo">Book a Demo</button>
<button data-chaos-track="trial">Start Free Trial</button>
<button data-chaos-track="checkout">Proceed to Checkout</button>
<button data-chaos-track="comparison">Compare Plans</button>
<button data-chaos-track="roi_calc">Calculate ROI</button>
<button data-chaos-track="download">Download Guide</button>

<!-- Track element visibility on scroll -->
<section data-chaos-scroll="testimonials">...</section>
<section data-chaos-scroll="pricing_table">...</section>

<!-- Rich component tracking with metadata -->
<div data-chaos-component="hero_cta" data-chaos-meta='{"variant":"A"}'>
  ...
</div>

<!-- Override page type detection -->
<body data-chaos-page-type="pricing">...</body>

<!-- Classify form type -->
<form data-chaos-form="demo">...</form>

<!-- Track media engagement (fires media_engaged @50%, media_completed @95%/end) -->
<video data-chaos-media="demo_video" src="..."></video>

Components with data-chaos-component or data-chaos-scroll added after page load are automatically observed via MutationObserver — no manual registerComponent() call needed.

Rich Metadata

Any element with data-chaos-meta will have its JSON parsed and included in all events for that element (visibility milestones, hover, click):

<section
  data-chaos-component="pricing_table"
  data-chaos-meta='{"productId":"pro","variant":"annual"}'
>
  ...
</section>

Attribution & UTM Tracking

UTM parameters (utm_source, utm_medium, utm_campaign, utm_term, utm_content) and ad click IDs (gclid, fbclid, msclkid, etc.) are captured on first page load of a session and persisted in sessionStorage.

They are included in every pageview and signal event, so the backend always has attribution context even if the initial beacon failed.

Session Persistence

Session ID, start time, and page count are persisted in sessionStorage. This means:

  • getSessionDuration() returns time since the session started, not just the current page
  • pages_viewed accumulates across full page loads (not just SPA navigations)
  • Bounce rate can be accurately computed (single-pageview sessions)

Sessions expire after 30 minutes of inactivity (standard analytics definition).

Privacy

Consent is evaluated in this priority order:

  1. tracker.optOut() — an absolute kill switch. Sets the chaos_optout flag, and also stops the live instance immediately (discarding queued and buffered events). Nothing — not even a hasConsent callback — overrides it. tracker.optIn() clears the flag.
  2. GPC / DNTnavigator.globalPrivacyControl === true or navigator.doNotTrack === "1" veto tracking by default, even when a hasConsent callback returns true. GPC is a legally recognized opt-out under CCPA/CPRA. To make a consent banner authoritative over a browser signal (e.g. Brave defaults GPC on), set overrideBrowserSignals: true and ensure you have a lawful basis — it is never the default, and logs a warning.
  3. hasConsent callback — receives { gpc, dnt, optedOut } so it can make an informed decision. Must return true for tracking to start.

EU / GDPR: with no hasConsent callback the tracker uses an opt-out model (it runs unless GPC/DNT or the opt-out flag is set). GDPR/ePrivacy require opt-in consent for analytics, so EU integrators must supply a hasConsent callback wired to their consent management platform.

Other protections:

  • PII hashinghashIdentifiers: true SHA-256 hashes the hashFields traits before sending. It fails closed: if Web Crypto is unavailable (e.g. a non-HTTPS origin) the fields are dropped, never sent in plaintext. Note this is pseudonymization, not anonymization — a hashed email is still personal data under GDPR.
  • URL redaction — query strings and hash fragments are stripped from the url / referrer fields by default (they routinely carry tokens and PII). Opt specific params back in with captureUrlParams. UTM / ad-click params are captured into attribution separately.
  • Durable visitor cookie — the tracker prefers a first-party chaos_vid cookie for identity (it survives localStorage eviction by Safari ITP / Brave / incognito, which otherwise fragments sessions). When only localStorage holds the id, the client writes it to a chaos_vid cookie (Path=/, SameSite=Lax, ~730-day Max-Age, Secure only over HTTPS) so it survives and is readable next load. The cookie is only written after the consent gate passes — an opted-out visitor or an active GPC/DNT signal means no cookie is set (and no events sent). See INTENT_MODEL.md.

Reliability

  • Retry buffer: failed sends are stored in localStorage (up to 100 events, FIFO eviction) and retried on next flush
  • Batch cap: events are sent in batches of 50 to avoid payload size limits
  • Event IDs: every event carries a unique event_id for server-side deduplication
  • Heartbeat pauses while the tab is hidden to save battery and network

Stripe Revenue Linkage

Pass the visitor_id to Stripe Checkout for direct revenue attribution:

const intent = tracker.getIntent();

// Server-side: create checkout session
const session = await stripe.checkout.sessions.create({
  client_reference_id: intent.visitor_id,
  // ...
});

Server-Side Intent (SSR / personalization)

Read a visitor's resolved intent on the server (Next.js, Express, edge) to morph UI, gate pricing, or branch email flows. Backed by a short-TTL cache, a hard timeout, and per-visitor single-flight — and it never throws, so it's safe in a render path.

import { createIntentClient } from 'p1-chaos-tracker/server';

const intent = createIntentClient({
  apiBase: 'https://ap1.example.com/api/v1/intent',
  siteId: 'YOUR_SITE_ID',
  apiKey: process.env.CHAOS_API_KEY, // optional Bearer token
});

// In a request handler / server component:
const snap = await intent.get(visitorId); // visitor_id cookie from the client
if (snap?.stage === 'decision') {
  // show the high-intent CTA
}

// Many at once (fans out cached, single-flighted /resolve calls):
const map = await intent.getBatch([idA, idB, idC]);

Reads GET /api/v1/intent/resolve (see BACKEND_ENDPOINTS.md §2). Returns null for unknown visitors, timeouts, or errors.

Intent Stages

| Stage | Weight | Example Signals | |-------|--------|----------------| | Awareness | 1 | first_visit, homepage_under_20s | | Interest | 2 | pages_gte_2, repeat_within_day | | Consideration | 3 | pricing_over_30s, demo_form_start, form_abandon | | Decision | 4 | checkout_entered, trial_signup_attempt | | Evaluation | 5 | case_study_read, feature_deepdive_over_1min |

Intent Model

Stages are scored with per-signal weights and recency decay, surfaced as a probability distribution + confidence (not just a single stage), with velocity (stage_history, time_to_current_stage_ms) and a frustrated flag from friction signals. Intent also persists across sessions with a decaying memory. High-intent behavioral signals captured automatically include hesitation (hovering a CTA without clicking), copy of a price/contact, reaching a payment field, media engagement, and rage/dead clicks.

See INTENT_MODEL.md for the full scoring model, the signal catalog, the privacy-preserving localIntentOnly mode, durable cookie identity, and the roadmap to learn weights from conversion outcomes.

Events Emitted

| Event | When | Key Data Fields | |-------|------|-----------------| | session_start | Tracker init | visit_count, device (incl. user_agent, bot_assessment), attribution, client_hints | | session_end | Previous session TTL expired | reason | | session_bounce | Unload with 1 page & <10s | time_on_page, scroll_depth | | pageview | Every page load + SPA nav | page_type, pages_viewed, attribution | | entry_page | First pageview of session | pathname, hostname, referrer, attribution | | exit_page | SPA nav away + unload | pathname, time_on_page, scroll_depth | | page_exit | Unload | full signal summary, components | | signal | Intent signal fired | signal name, intent_stage, attribution | | click | [data-chaos-track] click | track attr, text, meta | | outbound_click | External link click | url, hostname, pathname, page_type | | form_focus | Form field focus | form_type, field | | form_submit | Form submission | form_type | | form_abandon | Unload with active form | form_type, field, time_spent_ms | | component_visible | Scroll visibility milestone | component, visibility %, meta | | component_view_end | Component leaves viewport | view_duration_ms, max_visibility | | heartbeat | Every 10s (not when hidden) | intent, duration, scroll, components | | exit_intent | Mouse leaves viewport top | page_type, time_on_page | | identify | tracker.identify() call | user_id, traits (optionally hashed) | | custom | tracker.track() call | name, meta | | conversion | tracker.convert() call or auto-detected payment success page | value, currency, order_id, provider (+ auto: true when auto-detected) |

Development

yarn install
yarn test         # vitest
yarn build        # rollup → dist/

License

MIT