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

@indiebob/tracker

v0.2.0

Published

Privacy-first analytics SDK for web applications. <5KB gzipped. No cookies.

Downloads

329

Readme

@indiebob/tracker

Privacy-first analytics SDK for web applications. Under 5KB gzipped. No cookies. GDPR/CCPA/GPC compliant.

Installation

npm install @indiebob/tracker
# or
pnpm add @indiebob/tracker

Or via script tag (CDN):

<script src="https://assets.indiebob.com/tracker/latest/indiebob.min.js"></script>

Quick Start

Three lines to start tracking:

import { IndieBob } from '@indiebob/tracker'

const bob = IndieBob.init({ projectId: 'proj_your16charId' })
bob.page()

That's it. Page views are now tracked. UTM parameters are auto-captured.

Configuration

const bob = IndieBob.init({
  projectId: 'proj_your16charId',  // Required. From your IndieBob dashboard.
  endpoint: 'https://indiebob.com/api/collect',  // Optional. Default shown.
  debug: false,           // Optional. Logs events to console when true.
  respectDnt: true,       // Optional. Honor browser Do Not Track. Default: true.
  respectGpc: true,       // Optional. Honor Global Privacy Control. Default: true.
  autoDetect: true,       // Optional. Auto-detect signups, logins, checkouts, etc. Default: true.
})

| Option | Type | Default | Description | |---|---|---|---| | projectId | string | required | Your SDK project ID from the dashboard | | endpoint | string | 'https://indiebob.com/api/collect' | Override for self-hosted instances | | debug | boolean | false | Log all events to browser console | | respectDnt | boolean | true | Honor the browser's Do Not Track header | | respectGpc | boolean | true | Honor Global Privacy Control signal (legally binding in CA/GDPR) | | autoDetect | boolean \| object | true | Auto-detect user intent from URL patterns, form submissions, outbound clicks, and data attributes. See Auto-Detection. |

API Reference

IndieBob.init(config)

Creates and returns a tracker instance. Call once at app startup.

const bob = IndieBob.init({ projectId: 'proj_abc123def456ghij' })

bob.page(overrides?)

Track a page view. Call this on every page load and SPA navigation.

bob.page()                           // Auto-detects path, title, referrer
bob.page({ path: '/pricing' })       // Override path
bob.page({ title: 'Pricing Page' }) // Override title

Auto-detected: URL path, page title, referrer, UTM parameters, device type.


bob.track(event, properties?)

Track a custom event.

bob.track('signup_started')
bob.track('trial_activated', { plan: 'pro' })
bob.track('feature_used', { feature: 'csv_export', count: 3 })
bob.track('checkout_completed', { revenue: 29.00, currency: 'USD', plan: 'pro-monthly' })

event — string, required. Max 256 characters. Use snake_case by convention.

properties — plain object, optional. Values must be JSON-serializable.


bob.identify(userId, traits?)

Associate the current anonymous visitor with a known user ID. Call this after login or signup.

bob.identify('user_123', {
  email: '[email protected]',
  name: 'Jane Smith',
  plan: 'pro',
  createdAt: '2026-01-15',
})

userId — string, required. Your internal user ID. Never pass PII as the userId directly.

traits — plain object, optional. Can include email for attribution linking.

After identify(), all subsequent events are linked to this userId until the session ends.


bob.revenue(data)

Track a revenue event. Use alongside Stripe webhooks for complete attribution.

bob.revenue({
  amount: 29.00,           // Required. Positive number.
  currency: 'USD',         // Optional. Default: 'USD'. ISO 4217 code.
  plan: 'pro-monthly',     // Optional. Your plan name.
  type: 'new',             // Optional. 'new' | 'renewal' | 'upgrade' | 'downgrade'
})

Privacy Controls

bob.optOut()       // Stop all tracking. Clears all stored identifiers.
bob.optIn()        // Re-enable tracking.
bob.isOptedOut()   // Returns boolean.

Debug / Testing

bob.getAnonymousId()  // Returns the current anonymous ID
bob.getSessionId()    // Returns the current session ID
bob.getConfig()       // Returns a read-only copy of the config
bob.flush()           // Force-send all queued events immediately

Auto-Detection

The SDK automatically fires semantic events out of the box — no manual bob.track() calls needed for common user journeys. This powers IndieBob's attribution funnels (visit → signup → checkout → paid).

URL Intent Detection

When bob.page() is called, the SDK checks the URL path against known patterns (multilingual: EN, ES, FR, DE, PT, IT, NL) and fires the corresponding event:

| Event | Triggered By | |---|---| | signup_start | /signup, /register, /join, /registro, /inscription, /anmeldung, ... | | login_view | /login, /sign-in, /iniciar-sesion, /connexion, /anmelden, ... | | pricing_view | /pricing, /plans, /precios, /tarifs, /preise, ... | | checkout_view | /checkout, /cart, /basket, /panier, /warenkorb, /kasse, ... | | success_view | /success, /thank-you, /confirmation, /danke, /merci, ... | | onboarding_view | /onboarding, /welcome, /getting-started, /bienvenido, ... | | contact_view | /contact, /contacto, /kontakt, /nous-contacter, ... |

Signup vs Login Detection

When bob.identify() is called:

  • First call for this visitor fires signup (new user)
  • Subsequent calls fire login (returning user)

This uses a localStorage flag — no server round-trip needed.

Form Submit Detection

On signup, login, and checkout pages, form submissions are automatically tracked:

  • signup_submit — form submitted on a signup page
  • login_submit — form submitted on a login page
  • checkout_submit — form submitted on a checkout page

Outbound Click Tracking

Clicks on links pointing to external domains automatically fire outbound_click with { url, hostname } properties.

Data Attribute Tracking

Add data-ib-event to any element for zero-JS custom event tracking:

<button data-ib-event="add_to_cart" data-ib-plan="pro" data-ib-price="29">
  Add to Cart
</button>

This fires track('add_to_cart', { plan: 'pro', price: '29' }) on click. All data-ib-* attributes (except data-ib-event) become event properties.

Customizing Auto-Detection

Override the default URL patterns or disable specific features:

const bob = IndieBob.init({
  projectId: 'proj_abc123',
  autoDetect: {
    // Custom URL patterns (override defaults)
    signupPaths: [/\/my-signup-page/i],
    checkoutPaths: [/\/buy|\/purchase/i],

    // Disable specific features
    outboundClicks: false,
    formSubmit: false,
    dataAttributes: false,
  },
})

Set autoDetect: false to disable all auto-detection entirely.

| Option | Type | Default | Description | |---|---|---|---| | signupPaths | RegExp[] | built-in i18n | URL patterns for signup pages | | loginPaths | RegExp[] | built-in i18n | URL patterns for login pages | | pricingPaths | RegExp[] | built-in i18n | URL patterns for pricing pages | | checkoutPaths | RegExp[] | built-in i18n | URL patterns for checkout/cart pages | | successPaths | RegExp[] | built-in i18n | URL patterns for success/thank-you pages | | onboardingPaths | RegExp[] | built-in i18n | URL patterns for onboarding pages | | contactPaths | RegExp[] | built-in i18n | URL patterns for contact pages | | dataAttributes | boolean | true | Track clicks on data-ib-event elements | | formSubmit | boolean | true | Track form submissions on intent pages | | outboundClicks | boolean | true | Track clicks on external links |


Framework Integrations

Vue / Nuxt

// main.ts or plugins/tracker.client.ts
import { IndieBobPlugin } from '@indiebob/tracker/vue'

app.use(IndieBobPlugin, { projectId: 'proj_abc123def456ghij' })

Then in any component:

import { useTracker } from '@indiebob/tracker/vue'

const bob = useTracker()
bob.track('button_clicked', { button: 'cta_hero' })

Nuxt plugin (plugins/tracker.client.ts):

import { IndieBobPlugin } from '@indiebob/tracker/vue'

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(IndieBobPlugin, {
    projectId: useRuntimeConfig().public.trackerProjectId,
  })
})

Page views auto-track on vue-router navigation.

React

import { IndieBob } from '@indiebob/tracker'
import { useEffect } from 'react'

// Initialize once at app root
const bob = IndieBob.init({ projectId: 'proj_abc123def456ghij' })

// In a page component or layout
useEffect(() => {
  bob.page()
}, [pathname])

Vanilla JS (Script Tag)

<script src="https://assets.indiebob.com/tracker/latest/indiebob.min.js"></script>
<script>
  const bob = IndieBob.init({ projectId: 'proj_abc123def456ghij' })
  bob.page()
</script>

Server-Side (Node.js)

import { IndieBob } from '@indiebob/tracker/server'

const bob = IndieBob.initServer({ projectId: 'proj_abc123def456ghij' })
bob.track('server_event', { source: 'cron_job' })

How It Works

Anonymous ID

A random 21-character ID is generated on first visit and stored in localStorage. This persists across sessions so you can track a visitor's journey before they sign up.

  • Stored as _ib_anon in localStorage
  • Falls back to sessionStorage if localStorage is blocked
  • Never uses cookies — no consent banner required
  • Cleared when optOut() is called

Session ID

A session ID is generated per browser tab/window using crypto.randomUUID(). Sessions expire after 30 minutes of inactivity or when the tab closes.

UTM Attribution

UTM parameters (utm_source, utm_medium, utm_campaign, utm_term, utm_content) are captured from the URL automatically on init().

  • First-touch attribution stored in _ib_first_utm — never overwritten
  • Last-touch attribution in _ib_last_utm — updated each time UTMs are present in the URL
  • Enables "which campaign drove this paying customer?" — the core attribution question

Event Batching

Events are batched for efficiency:

  • Sent when 10 events accumulate
  • Sent every 5 seconds regardless
  • Sent immediately when the page becomes hidden (tab switch, close)
  • Queued in localStorage when offline; flushed when back online

Privacy by Default

The SDK respects:

  • Do Not Track (navigator.doNotTrack === '1'): no events sent, respectDnt: true by default
  • Global Privacy Control (navigator.globalPrivacyControl): legally binding under CCPA in California, respectGpc: true by default
  • Neither cookies nor fingerprinting is used at any point
  • IP addresses are never stored by the server — only the country code derived at ingestion time

What Is and Isn't Collected

Automatically collected (on page())

| Data | What | Stored As | |---|---|---| | Page path | /pricing, /blog/post-1 | String | | Page title | document.title | String | | Referrer | document.referrer | String | | UTM params | From URL query string | Object | | Screen size | window.innerWidth/Height | Numbers | | Device type | Derived from screen size | 'desktop' | 'mobile' | 'tablet' | | Browser locale | navigator.language | String | | Timezone | Intl.DateTimeFormat().resolvedOptions().timeZone | String | | Country | Derived server-side from IP | String (e.g. 'US') |

Auto-detected events (when autoDetect is enabled)

| Event | Trigger | |---|---| | signup_start | User visits a signup/register page | | signup_submit | User submits a form on a signup page | | signup | First identify() call (new user) | | login_view | User visits a login page | | login_submit | User submits a form on a login page | | login | Subsequent identify() call (returning user) | | pricing_view | User visits a pricing page | | checkout_view | User visits a checkout/cart page | | checkout_submit | User submits a form on a checkout page | | success_view | User visits a success/thank-you page | | onboarding_view | User visits an onboarding page | | contact_view | User visits a contact page | | outbound_click | User clicks an external link | | Custom via data-ib-event | User clicks an element with the attribute |

NOT collected

  • IP addresses (never stored, used only for country lookup then discarded)
  • Browser fingerprint
  • Cookies
  • Personal data (name, email, etc.) unless you pass it to identify()
  • Cross-site tracking data

Privacy & Compliance

GDPR (EU)

The SDK does not use cookies and does not collect personal data by default. The anonymousId is a random identifier with no link to personal identity. Under GDPR, anonymous analytics may not require a consent banner — but consult your legal team.

When a user calls optOut(), all stored keys are cleared: _ib_anon, _ib_session, _ib_first_utm, _ib_last_utm, _ib_referrer, _ib_queue.

CCPA (California)

Global Privacy Control (GPC) is automatically honored. If a user has GPC enabled in their browser, the SDK sends no events. This is the legally correct behavior under CCPA as amended by CPRA.

Cookie Law (EU ePrivacy Directive)

No cookies are set. The localStorage identifiers are not cookies and fall outside the scope of the Cookie Law.


Self-Hosting

Point the SDK at your own IndieBob instance:

const bob = IndieBob.init({
  projectId: 'proj_abc123def456ghij',
  endpoint: 'https://your-indiebob.example.com/api/collect',
})

Versioning & Backwards Compatibility

This SDK follows Semantic Versioning.

1.x guarantee: All public methods listed in this README are stable. We will never:

  • Remove a method in a 1.x release
  • Change the behavior of an existing method without a deprecation period
  • Break the wire format for events that include v: 1

Upgrade path: npm update @indiebob/tracker — no code changes required within 1.x.

Deprecation process: Methods marked @deprecated in JSDoc will log a warning in debug: true mode. They remain functional until the next major version.


Bundle Size

| Bundle | Size (gzip) | |---|---| | Browser IIFE (tracker.js) | < 5KB | | ESM tree-shakeable | < 4KB | | Server (Node.js) | No limit |

Zero runtime dependencies.


License

MIT