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 🙏

© 2025 – Pkg Stats / Ryan Hefner

autocap

v1.5.0

Published

Privacy-compliant event tracking library for exit point analysis

Readme

AutoCap

Privacy-compliant auto-capture event tracking library focused on exit point analysis. Framework-agnostic and built for global browser compatibility.

Features

  • Exit Point Detection - Track where users leave
  • High-Granularity Input Tracking - Track input events without storing values (PII-safe)
  • Click/Tap Tracking - Comprehensive click event capture with link URL and text content
  • Session Management - Unique session IDs, timing, duration
  • SPA Navigation - Track page loads in single-page applications
  • Dynamic Elements - Works with dynamically created DOM elements via event delegation
  • Privacy-First - No PII collection, complies with global privacy regulations (GDPR, PIPL, CCPA)
  • Analytics-Optimized - Flattened event structure with normalized URLs for efficient dashboard queries
  • Global Browser Compatible - Works across modern browsers including Chrome, Safari, Firefox, Edge, Opera, Samsung Internet, WeChat, UC, QQ, 360, Baidu
  • Lean - < 5KB gzipped bundle size

Installation

npm install autocap
# or
yarn add autocap

Quick Start

Next.js (App Router)

// app/layout.tsx or app/tracking-provider.tsx
'use client'

import { useEffect } from 'react'
import { Tracker } from 'autocap'

export function TrackingProvider({ children }) {
  useEffect(() => {
    const tracker = new Tracker({
      endpoint: '/api/track', // Your API endpoint
      trackClicks: true,
      trackInputs: true,
      sanitizeUrls: true
    })
    
    tracker.start()
    
    return () => {
      tracker.stop()
    }
  }, [])
  
  return <>{children}</>
}

Next.js (Pages Router)

// pages/_app.tsx
import { useEffect } from 'react'
import { Tracker } from 'autocap'

export default function App({ Component, pageProps }) {
  useEffect(() => {
    const tracker = new Tracker({
      endpoint: '/api/track',
      trackClicks: true,
      trackInputs: true
    })
    tracker.start()
    return () => tracker.stop()
  }, [])
  
  return <Component {...pageProps} />
}

Vanilla JavaScript

<script src="/autocap.umd.js"></script>
<script>
  const tracker = new AutoCap.Tracker({
    endpoint: 'https://your-api.com/track',
    trackClicks: true,
    trackInputs: true
  })
  tracker.start()
</script>

Configuration

interface TrackerConfig {
  // Session
  sessionStorage?: 'sessionStorage' | 'memory' | 'none'
  sessionTimeout?: number // milliseconds
  
  // Events
  trackClicks?: boolean
  trackInputs?: boolean
  
  // Throttling
  inputThrottle?: number // milliseconds
  
  // Privacy
  sanitizeUrls?: boolean
  excludeQueryParams?: boolean
  
  // Data
  maxEventsInMemory?: number
  endpoint?: string // Server endpoint
  
  // Compatibility
  enablePolyfills?: boolean
  
  // Analytics metadata (optional, added to all events)
  environment?: string // e.g., 'dev' | 'staging' | 'prod'
  appVersion?: string // semver string
  traceId?: string // for backend correlation
  requestId?: string // for backend correlation
  samplingRate?: number // if sampling is implemented
}

Test Harness

A test harness (index.html) is included in the npm package to help you test and debug AutoCap. The test harness provides:

  • Interactive testing of click, input, and page navigation events
  • Real-time event log display
  • Configuration options
  • Event export functionality
  • SPA navigation simulation

Development Usage

For local development in this repository:

  1. Build the library:

    npm run build
  2. Start a local server in the root directory:

    npx serve
  3. Open index.html in your browser (the server will typically show the URL, usually http://localhost:3000)

Note: Using a local server (like npx serve) is recommended over opening the file directly (file://) because some features like pushState work better with HTTP.

Serving with Docker

The project includes a Dockerfile that uses nginx to serve the test harness. Here's how to use it:

Build the Docker image:

docker build -t autocap .

Run the container:

docker run -d -p 8080:80 --name autocap-server autocap

Then access it at http://localhost:8080/.

Server-Side Implementation

AutoCap sends events to your configured endpoint. Here's how to handle them:

Request Format

  • Method: POST
  • Content-Type: application/json (for all events, including exit events via sendBeacon)
  • Body: Single EventPayload object as JSON string

Events are sent individually and immediately as they occur.

Next.js Route Handler Example

// app/api/track/route.ts (App Router)
import { NextRequest, NextResponse } from 'next/server'

export async function POST(request: NextRequest) {
  try {
    // All events are sent as application/json
    const event = await request.json()
    
    // Log to stdout (will appear in your server logs)
    console.log(JSON.stringify({
      timestamp: new Date().toISOString(),
      event: event
    }))
    
    // Optional: Add your own processing here
    // - Store in database
    // - Send to analytics service
    // - Filter/transform data
    
    return NextResponse.json({ success: true }, { status: 200 })
  } catch (error) {
    console.error('Error processing tracking event:', error)
    return NextResponse.json(
      { error: 'Invalid request' },
      { status: 400 }
    )
  }
}
// pages/api/track.ts (Pages Router)
import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' })
  }
  
  try {
    // All events are sent as application/json
    const event = req.body
    
    // Log to stdout
    console.log(JSON.stringify({
      timestamp: new Date().toISOString(),
      event: event
    }))
    
    res.status(200).json({ success: true })
  } catch (error) {
    console.error('Error processing tracking event:', error)
    res.status(400).json({ error: 'Invalid request' })
  }
}

Important Notes

  • Exit Events: Use navigator.sendBeacon() with application/json content-type for reliable delivery during page unload.
  • Individual Events: Each event is sent in a separate request (not batched).
  • Silent Failures: The client silently fails if requests fail to avoid breaking user experience.
  • Keepalive: Regular events use fetch with keepalive: true for reliability.
  • Session Start Events: A session_start event is automatically emitted on the first page load of each session with full browser metadata. Subsequent events include minimal browser fields to reduce payload size.

Event Payload Structure

Events are structured for analytics dashboards with flattened dimensions and normalized URLs for efficient querying.

interface EventPayload {
  // Event identification
  event_id: string // UUID, auto-generated
  event_type: 'click' | 'input' | 'page_load' | 'page_exit' | 'session_start'
  
  // Session
  session_id: string
  session_start_ts: number // milliseconds
  ts: number // event timestamp in milliseconds
  ts_iso: string // ISO 8601 string for human readability
  elapsed_ms: number // milliseconds since session start
  
  // Location (normalized for low cardinality)
  url_host: string // e.g., "localhost:3000"
  url_path: string // e.g., "/products"
  url?: string // optional raw URL (not for grouping)
  referrer_host: string // empty if no referrer
  referrer_path?: string // optional
  prev_url_path?: string // for SPA navigation
  page_title: string
  page_id?: string // stable page identity derived from path (e.g., "products" from "/products")
  
  // Element dimensions (promoted from data for click/input events)
  element_tag?: string // tagName for click/input elements
  element_id?: string // id for click/input elements
  element_class?: string // className for click/input elements
  element_name?: string // name attribute for click/input elements
  element_text_len?: number // textLength for click elements
  
  // Input-specific dimensions
  input_type?: string // for input events
  field_name?: string // for input events
  input_length?: number // for input events
  
  // Click event fields (flattened)
  click_x?: number // click X coordinate
  click_y?: number // click Y coordinate
  link_url?: string // URL of clicked link (sanitized)
  link_text?: string // Text content of clicked link (limited to 200 chars)
  button_text?: string // Text content of clicked button (limited to 200 chars)
  
  // Page event fields (flattened)
  load_time?: number // page load time in milliseconds
  
  // Exit event fields (flattened)
  last_interaction?: string // type of last interaction before exit
  time_on_page?: number // milliseconds spent on page
  exit_element_tag?: string // tagName of exit element
  exit_element_id?: string // id of exit element
  exit_element_class?: string // className of exit element
  
  // Browser metadata (minimal on regular events, full on session_start)
  ua_hash?: string // hash of userAgent for joins
  ua_full?: string // full userAgent string (only on session_start event)
  viewport_w?: number
  viewport_h?: number
  timezone?: string
  language?: string // browser language (only on session_start event)
  ua_browser?: string // parsed browser name (optional)
  ua_os?: string // parsed OS (optional)
  ua_device?: string // parsed device (optional)
  
  // Analytics metadata from config (optional)
  environment?: string
  app_version?: string
  trace_id?: string
  request_id?: string
  sampling_rate?: number
}

Key Design Decisions

Fully Flattened Structure: All event fields are at the top level (no nested data object). This includes element dimensions, click coordinates, page load times, and exit metadata. This structure enables easier GROUP BY queries in SQL/SLS-style aggregations without nested field access.

Normalized URLs: URLs are split into url_host and url_path to reduce cardinality. Full URLs are high-cardinality and expensive to group by in dashboards.

Session-Level Browser Metadata: Full browser metadata (ua_full, language, viewport_w/h, timezone) is only sent once per session in a session_start event. Regular events include minimal fields (ua_hash, viewport_w/h, timezone) to reduce payload size and storage costs.

Stable Page Identity: page_id is derived from URL path (e.g., /products/123products) to provide a stable identifier that doesn't change with A/B tests or dynamic content.

Example Click Event

{
  "event_id": "3f2c8a1b-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
  "event_type": "click",
  "session_id": "789d453b-9e91-4628-98d1-6663fccadbb3",
  "session_start_ts": 1766084562000,
  "ts": 1766084562976,
  "ts_iso": "2025-12-18T10:29:22.976Z",
  "elapsed_ms": 976,
  "url_host": "localhost:3000",
  "url_path": "/products",
  "referrer_host": "",
  "page_title": "AutoCap Test - /products",
  "page_id": "products",
  "element_tag": "button",
  "element_id": "add-to-cart",
  "element_class": "btn btn-primary",
  "element_text_len": 12,
  "click_x": 450,
  "click_y": 320,
  "ua_hash": "xxh3:a1b2c3d4",
  "viewport_w": 1707,
  "viewport_h": 620,
  "timezone": "America/Los_Angeles"
}

Example Input Event

{
  "event_id": "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
  "event_type": "input",
  "session_id": "789d453b-9e91-4628-98d1-6663fccadbb3",
  "session_start_ts": 1766084562000,
  "ts": 1766084563450,
  "ts_iso": "2025-12-18T10:29:23.450Z",
  "elapsed_ms": 1450,
  "url_host": "localhost:3000",
  "url_path": "/contact",
  "referrer_host": "",
  "page_title": "Contact Us",
  "page_id": "contact",
  "element_tag": "input",
  "element_id": "email",
  "element_class": "form-control",
  "element_name": "email",
  "input_type": "email",
  "field_name": "email",
  "input_length": 24,
  "ua_hash": "xxh3:a1b2c3d4",
  "viewport_w": 1707,
  "viewport_h": 620,
  "timezone": "America/Los_Angeles"
}

Example Session Start Event

{
  "event_id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "event_type": "session_start",
  "session_id": "789d453b-9e91-4628-98d1-6663fccadbb3",
  "session_start_ts": 1766084562000,
  "ts": 1766084562000,
  "ts_iso": "2025-12-18T10:29:22.000Z",
  "elapsed_ms": 0,
  "url_host": "localhost:3000",
  "url_path": "/",
  "referrer_host": "",
  "page_title": "AutoCap Test",
  "page_id": "home",
  "ua_full": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...",
  "viewport_w": 1707,
  "viewport_h": 620,
  "language": "en-US",
  "timezone": "America/Los_Angeles"
}

Privacy & Compliance

AutoCap is designed to comply with global privacy regulations (GDPR, PIPL, CCPA):

  • No Input Values - Never stores input field values, only character count
  • No PII in Values - Only collects metadata (length, type, field names) - never actual input values
  • Metadata Only - Only collects non-identifying metadata
  • Data Localization - Configure endpoint to store data in your preferred region

Server-Side Logging Utilities

AutoCap exports utility functions that you can use in your server-side code to maintain consistent event formatting between client and server logs. This is useful for:

  • Augmenting client events with server-side data (e.g., IP hashing)
  • Creating server-side events (e.g., API calls, errors)
  • Maintaining consistent URL normalization and hashing across your logging pipeline

Available Utilities

import { 
  // URL utilities
  parseUrl,
  parseReferrer, 
  derivePageId,
  sanitizeUrl,
  // Hashing utilities
  hashString,        // Fast non-cryptographic hash (djb2)
  hashStringSecure,  // Secure SHA-256 hash (async)
  // Other
  generateUUID
} from 'autocap';

Usage Examples

URL Normalization

import { parseUrl, parseReferrer, derivePageId } from 'autocap';

// Parse URL into host and path
const url = parseUrl('https://example.com/products/123?foo=bar');
// → { host: 'example.com', path: '/products/123', raw: 'https://...' }

// Parse referrer
const ref = parseReferrer('https://google.com/search');
// → { host: 'google.com', path: '/search' }

// Derive stable page ID
const pageId = derivePageId('/products/123/details');
// → 'products'

Hashing PII

import { hashString, hashStringSecure } from 'autocap';

// Fast non-cryptographic hash (synchronous) - for user agents, non-sensitive data
const uaHash = hashString(req.headers['user-agent']);
// → 'xxh3:1a2b3c4d'

// Secure SHA-256 hash for PII (async) - for IPs, emails, etc.
const emailHash = await hashStringSecure('[email protected]');
// → 'sha256:a1b2c3d4e5f6...'

// Hash IP address (extract first IP from x-forwarded-for)
const ipHeader = req.headers['x-forwarded-for'];
const firstIp = ipHeader?.split(',')[0]?.trim();
const ipHash = await hashStringSecure(firstIp || 'unknown');
// → 'sha256:7f8e9d0c1b2a3f4e'

Complete Server Log Entry

import { 
  parseUrl, 
  parseReferrer, 
  derivePageId, 
  hashString,
  hashStringSecure,
  generateUUID 
} from 'autocap';

export async function createServerLogEntry(req: Request) {
  const url = new URL(req.url);
  const urlData = parseUrl(req.url);
  const referrerData = parseReferrer(req.headers.get('referer'));
  
  // Extract first IP from x-forwarded-for
  const ipHeader = req.headers.get('x-forwarded-for');
  const firstIp = ipHeader?.split(',')[0]?.trim();
  
  return {
    event_id: generateUUID(),
    event_type: 'server_request',
    ts: Date.now(),
    ts_iso: new Date().toISOString(),
    url_host: urlData.host,
    url_path: urlData.path,
    referrer_host: referrerData.host,
    referrer_path: referrerData.path,
    page_id: derivePageId(urlData.path),
    ua_hash: hashString(req.headers.get('user-agent') || ''),
    ip_hash: await hashStringSecure(firstIp || 'unknown'),
  };
}

Hash Function Details

hashString(input: string): string

  • Algorithm: djb2 variant (non-cryptographic)
  • Synchronous: Yes
  • Use Case: User agents, non-sensitive identifiers
  • Output: 'xxh3:1a2b3c4d' or empty string

hashStringSecure(input: string): Promise<string>

  • Algorithm: SHA-256 (cryptographic)
  • Synchronous: No (uses Web Crypto API)
  • Use Case: PII like IPs, emails, phone numbers
  • Output: 'sha256:7f8e9d0c...' (first 16 hex chars) or 'unknown'
  • Fallback: Uses hashString() if crypto unavailable

Browser Compatibility

Written to be compatible with:

  • ✅ Chrome 90+
  • ✅ Safari 14+
  • ✅ Firefox 88+
  • ✅ Edge 90+
  • ✅ Opera 76+
  • ✅ Samsung Internet 14+
  • ✅ WeChat In-App Browser
  • ✅ UC Browser 12+
  • ✅ QQ Browser
  • ✅ 360 Secure Browser
  • ✅ Baidu Browser

Note: The library uses feature detection and graceful degradation. If fetch and/or navigator.sendBeacon are unavailable, events will not be sent to your endpoint. All operations are wrapped in try-catch blocks to prevent errors from breaking your application.

License

MIT