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

@callforge/tracking-client

v0.15.0

Published

Lightweight client library for the CallForge tracking API. Handles location-aware phone number assignment with aggressive caching and preload optimization.

Readme

@callforge/tracking-client

Lightweight client library for the CallForge tracking API. Handles location-aware phone number assignment with aggressive caching and preload optimization.

Installation

npm install @callforge/tracking-client
# or
pnpm add @callforge/tracking-client

Lease Hardening Migration (v0.8+)

This release adds bootstrap-token support for lease hardening and bot suppression.

Client integration requirements:

  • Add the generated preload snippet in <head>. This prefetches bootstrap tokens and keeps session lookup fast.
  • Keep handling phoneNumber and leaseId separately. A request can return a phone number with leaseId: null when lease assignment is intentionally suppressed.
  • For attribution/scale metrics, treat leaseId as the source of truth for lease-backed traffic.

Realtime Data Layer Upgrade (v0.10+)

This release adds explicit browser helpers for the new realtime layer:

  • client.linkPhoneCall({ phoneNumber, realtime }) for strict web-click to phone-call linkage plus optional realtime fields.
  • client.refreshPresenceLink({ phoneNumber, signal }) for short-lived active-visitor presence linking.
  • Automatic presence refreshes on load, debounced scroll, focus, visibility return (visibilitychange to visible), and SPA route changes (enabled by default).

Integration checklist:

  • Call linkPhoneCall immediately before opening a tel: link.
  • Pass the exact dialed number string (+1...) used by the link.
  • Continue dialing even if linkPhoneCall fails (best-effort attribution assist, not UX-blocking).
  • When ZIP is known, include it in linkPhoneCall(...realtime) as webZip and webZipSource.
  • Use realtime.params for additional key/value fields you want persisted with the linked session.
  • Keep default auto presence enabled unless you have a specific reason to disable it.

Service Variant Routing Upgrade (v0.14+)

This release adds sticky serviceVariant support for shared number pools.

Integration notes:

  • Keep categoryId fixed to the real tracking pool/category.
  • Optionally set serviceVariantQueryParam in preload to resolve and persist campaign intent from the URL.
  • The resolved serviceVariant is sent as routing metadata. It does not change the phone pool namespace.
  • If you briefly adopted the experimental categoryQueryParam behavior from 0.13.0, switch to serviceVariantQueryParam before upgrading.

Extended Location Upgrade (v0.15+)

This release adds opt-in extended location data for clients that need more than city/state.

Set includeExtendedLocation: true in both the preload snippet and client initialization to request:

  • coordinates: latitude/longitude of the requested loc_physical_ms location.
  • nearbyCities: cities within 25 miles, each with distanceMiles.
  • regionalName: a common regional label when available, such as Puget Sound or Middle Tennessee.

The default response is unchanged for backwards compatibility. Older clients continue receiving only { city, state, stateCode, zipOptions }.

Quick Start

1. Add preload snippet to <head> (required for deterministic leases)

For optimal performance and lease hardening, add this snippet to your HTML <head>:

import { getPreloadSnippet } from '@callforge/tracking-client';

const snippet = getPreloadSnippet({ categoryId: 'your-category-id' });
// Add snippet to your HTML <head>

Optional extended location preload:

const snippet = getPreloadSnippet({
  categoryId: 'your-category-id',
  includeExtendedLocation: true,
});

Optional sticky service-variant resolution:

const snippet = getPreloadSnippet({
  categoryId: 'your-category-id',
  serviceVariantQueryParam: 'serviceVariant',
});

With serviceVariantQueryParam enabled, preload resolves serviceVariant in this order:

  • query string (?serviceVariant=...)
  • stored category-scoped preference in localStorage
  • fallback to no variant/default behavior

The resolved value is cached in localStorage under cf_service_variant_v1_<siteKey>_<categoryId>. It stays sticky until a new query-string value overrides it. Resolution is synchronous and does not add an extra network request before the existing bootstrap/session fetches.

Example URLs:

/landing-page?serviceVariant=car-accident
/how-it-works?serviceVariant=medical-malpractice

Generated HTML:

<link rel="preconnect" href="https://tracking.callforge.io">
<script>/* preload script */</script>

2. Initialize and start session + location requests

import { CallForge } from '@callforge/tracking-client';

const client = CallForge.init({
  categoryId: 'your-default-category-id',
  serviceVariantQueryParam: 'serviceVariant', // Optional runtime fallback if preload is omitted
  includeExtendedLocation: true, // Optional: enables coordinates, nearbyCities, regionalName
  // endpoint: 'https://tracking-dev.callforge.io', // Optional: override for dev
});

const { session, location } = client.getSessionAsync();

// Location is delivered independently (often faster than phone number assignment)
console.log(await location);
// {
//   city: "Woodstock",
//   state: "Georgia",
//   stateCode: "GA",
//   zipOptions: ["30188", "30189", "30066", ...], // may be []
//   coordinates: { latitude: 34.1015, longitude: -84.5194 },
//   nearbyCities: [
//     { city: "Canton", state: "Georgia", stateCode: "GA", distanceMiles: 9.8 }
//   ],
//   regionalName: "Metro Atlanta"
// } or null

// Phone session data (deterministic token + phone number)
console.log(await session);  // { sessionToken, leaseId, phoneNumber }

3. Deterministic click/callback attribution (optional)

If you initiate calls programmatically (click-to-call / callback), you can request a short-lived callIntentToken. CallForge will consume this once to deterministically map the call back to the web session that requested it.

// In the browser:
const { callIntentToken } = await client.createCallIntent();

// Send to your backend and attach to the call you initiate
await fetch('/api/callback', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ callIntentToken }),
});

Notes:

  • callIntentToken is short-lived and single-use.
  • Treat it as an opaque secret (do not log it).

4. Realtime call-link with bundled realtime fields (recommended)

Use linkPhoneCall to create the strict call-link and optionally attach realtime fields in the same request.

const client = CallForge.init({ categoryId: 'your-category-id' });

async function dialTrackedNumber(phoneNumber: string, webZip?: string) {
  try {
    // Default TTL is 30s (clamped server-side to 5-120s).
    await client.linkPhoneCall({
      phoneNumber,
      realtime: webZip
        ? {
            webZip,
            webZipSource: 'manual', // or 'suggested'
            params: {
              zipChoiceMethod: 'picker',
            },
          }
        : undefined,
    });
  } finally {
    // Do not block dialing on telemetry failure.
    window.location.href = `tel:${phoneNumber}`;
  }
}

Notes:

  • linkPhoneCall is optimized for mobile tap-to-call timing.
  • realtime.webZip accepts manual (typed) and suggested (picked from options) via webZipSource.
  • realtime.webZip is validated client-side (12345 format).
  • realtime.params allows additional key/value payloads for future realtime enrichment without API changes.

5. GA4 Integration

To enable GA4 call event tracking, CallForge needs the GA4 client_id for the visitor (from the _ga cookie). Optionally provide your GA4 Measurement ID to improve client ID capture reliability when Google Analytics loads late.

const client = CallForge.init({
  categoryId: 'your-category-id',
  ga4MeasurementId: 'G-XXXXXXXXXX', // Recommended
});

Requirements:

  1. Google Analytics 4 must be installed on your site and allowed to set the _ga cookie (gtag.js or GTM).
  2. Configure GA4 credentials in CallForge dashboard (Measurement ID + API secret).

How it works:

  • Extracts the GA4 client_id from the _ga cookie and sends it to CallForge as ga4ClientId.
  • Polls briefly after init to capture the cookie if Google Analytics sets it slightly later.
  • If ga4MeasurementId is configured and gtag is available, also uses gtag('get', measurementId, 'client_id', ...) (with a short retry window).
  • If ga4ClientId becomes available after a session is created, the client will refresh once to sync it to CallForge.

Manual override:

client.setParams({
  ga4ClientId: '1234567890.1234567890',
});

6. Track conversion parameters (optional)

The client automatically captures ad platform click IDs from the URL:

  • gclid - Google Ads
  • gbraid - Google app-to-web (iOS 14+)
  • wbraid - Google web-to-app
  • msclkid - Microsoft/Bing Ads
  • fbclid - Facebook/Meta Ads

You can also add custom parameters:

client.setParams({
  customerId: '456',
  landingPage: 'pricing',
});

// Parameters are automatically sent with every API request
await client.getSession();

API Reference

CallForge.init(config)

Initialize the tracking client.

interface CallForgeConfig {
  categoryId: string;        // Required - which number pool to use
  endpoint?: string;         // Optional - defaults to 'https://tracking.callforge.io'
  siteKey?: string;          // Optional - cache partition key (defaults to window.location.hostname)
  serviceVariantQueryParam?: string; // Optional - sticky query/localStorage serviceVariant resolution
  ga4MeasurementId?: string; // Optional - GA4 Measurement ID (e.g., 'G-XXXXXXXXXX')
  presenceAutoLink?: {       // Optional - defaults shown below
    enabled?: boolean;             // default true
    ttlSeconds?: number;           // optional override for auto refreshes
    scrollDebounceMs?: number;     // default 1200
    minRefreshIntervalMs?: number; // default 15000
    fireOnLoad?: boolean;          // default true
    fireOnScroll?: boolean;        // default true
    fireOnRouteChange?: boolean;   // default true (pushState/replaceState/popstate/hashchange)
  };
}

When presenceAutoLink.enabled is true, the client also sends auto presence refreshes on window focus and when document.visibilityState returns to visible.

If preload resolved a serviceVariant via serviceVariantQueryParam, CallForge.init(...) will automatically use that same resolved variant for the initial session request and future refreshes. You still pass the real categoryId in CallForge.init(...); the sticky variant only affects routing params, not the pool namespace. If some pages do not include preload, you can also pass serviceVariantQueryParam to CallForge.init(...) so the runtime resolves the same sticky variant directly.

client.getSession()

Get tracking session data (phone number + deterministic session token). Returns cached data if valid, otherwise fetches from the API.

interface TrackingSession {
  sessionToken: string;      // Signed, opaque token used to refresh the session
  leaseId: string | null;    // Deterministic assignment lease ID (when available)
  phoneNumber: string | null;
}

Behavior:

  • Returns cached data if valid.
  • Fetches fresh data when cache is missing/expired.
  • If loc_physical_ms is present in the URL, cached sessions are only reused when it matches the cached locId.
  • If lease assignment is suppressed (for example bot traffic or missing/invalid bootstrap), phoneNumber may be present while leaseId is null.
  • Throws on network errors or API errors.

client.getLocation()

Get location data only. Returns cached data if valid, otherwise fetches from the API.

const location = await client.getLocation();
// { city, state, stateCode, zipOptions } or null

Set includeExtendedLocation: true in CallForge.init() or getPreloadSnippet() to request precomputed extended fields.

interface TrackingLocation {
  city: string;
  state: string;
  stateCode: string;
  zipOptions?: string[]; // ordered by proximity, may be []
  coordinates?: { latitude: number; longitude: number } | null;
  nearbyCities?: Array<{ city: string; state: string; stateCode: string; distanceMiles: number }>;
  regionalName?: string | null;
}

Example:

const client = CallForge.init({
  categoryId: 'your-category-id',
  includeExtendedLocation: true,
});

const location = await client.getLocation();

if (location) {
  console.log(location.coordinates);
  console.log(location.regionalName);
  for (const nearbyCity of location.nearbyCities ?? []) {
    console.log(`${nearbyCity.city}: ${nearbyCity.distanceMiles} miles`);
  }
}

When using preload, pass the same flag there too so the first location fetch includes the extended payload:

const snippet = getPreloadSnippet({
  categoryId: 'your-category-id',
  includeExtendedLocation: true,
});

client.getSessionAsync()

Kick off both requests and use each as soon as it resolves.

const { session, location } = client.getSessionAsync();

location.then((loc) => {
  // show city/state ASAP
  // optionally render loc?.zipOptions in a ZIP picker
});

session.then((sess) => {
  // show phone number when ready
});

client.createCallIntent()

Create a short-lived call intent token for click/callback deterministic attribution.

const intent = await client.createCallIntent();
console.log(intent.callIntentToken);

client.linkPhoneCall(input)

Create a short-lived realtime call-link intent keyed by dialed number, with optional realtime payload fields persisted against the linked session.

const result = await client.linkPhoneCall({
  phoneNumber: '+13105551234',
  ttlSeconds: 30, // Optional, default 30s
  realtime: {
    webZip: '30309',          // Optional
    webZipSource: 'manual',   // Optional, defaults to 'manual' when webZip is provided
    params: {                 // Optional
      zipChoiceMethod: 'picker',
    },
  },
});

console.log(result.status); // 'ready'

Use this right before tel: navigation so inbound call handling can perform strict 1:1 consume.

Behavior:

  • Rejects invalid realtime.webZip values unless they are 5 digits.
  • Persists bundled realtime values (webZip + params) during call-link intent creation.
  • Mirrors bundled realtime values into custom params and performs a best-effort session sync backup.

client.refreshPresenceLink(input)

Refresh a short-lived presence link for active web visitors on a dialed number.

await client.refreshPresenceLink({
  phoneNumber: '+13105551234',
  signal: 'scroll', // Optional: 'load' | 'scroll' | 'focus' | 'visibility'
  ttlSeconds: 45,   // Optional, server-clamped
});

Note:

  • You usually do not need to call this directly because the client auto-refreshes presence on load, debounced scroll, and SPA route changes.
  • Use this method for explicit/manual refresh points only (for example custom engagement events).

client.onReady(callback)

Subscribe to session ready event. Callback is called once session data is available.

client.onReady((session) => {
  // session is the same TrackingSession object
});

client.onLocationReady(callback)

Subscribe to location ready event. Callback is called once location data is available.

client.onLocationReady((location) => {
  // location is { city, state, stateCode, zipOptions } or null
});

client.setParams(params)

Set custom tracking parameters for conversion attribution.

client.setParams({
  customerId: '456',
  landingPage: 'pricing',
  campaign: 'summer-sale',
});

Behavior:

  • Merges with existing params (later calls override earlier values).
  • If a session already exists (cached sessionToken), the client will refresh once to sync updated params server-side (best-effort).
  • Parameters are sent with every getSession() API request.
  • Persisted in localStorage alongside session data.

getPreloadSnippet(config)

Generate HTML snippet for preloading tracking data.

import { getPreloadSnippet } from '@callforge/tracking-client';

const html = getPreloadSnippet({
  categoryId: 'your-category-id',
  endpoint: 'https://tracking.callforge.io', // Optional
  siteKey: 'example.com', // Optional
  serviceVariantQueryParam: 'serviceVariant', // Optional - opt-in sticky query/localStorage service-variant resolution
});

If you want the preload config type explicitly:

import type { CallForgePreloadConfig } from '@callforge/tracking-client';

Tracking Parameters

Auto-Capture

The client automatically extracts these parameters from the URL:

| Parameter | Source | |-----------|--------| | gclid | Google Ads Click ID | | gbraid | Google app-to-web (iOS 14+) | | wbraid | Google web-to-app | | msclkid | Microsoft/Bing Ads Click ID | | fbclid | Facebook/Meta Ads Click ID |

API Request Format

Parameters are sent as a sorted query string for cache consistency:

/v1/tracking/session?categoryId=cat-123&fbclid=456&gclid=abc&loc_physical_ms=1014221&sessionToken=...

Caching Behavior

  • Session cache key: cf_tracking_v1_<siteKey>_<categoryId>
  • Location cache key: cf_location_v1_<siteKey>
  • Bootstrap cache key: cf_bootstrap_v1_<siteKey>_<categoryId>
  • Session TTL: controlled by the server expiresAt response (currently 30 minutes)
  • Location cache reset: when loc_physical_ms changes (no time-based expiry)
  • Storage: localStorage (falls back to memory if unavailable)

Error Handling

try {
  const session = await client.getSession();
  if (session.phoneNumber) {
    // Use phone number
  } else {
    // No phone number available
  }
} catch (err) {
  // Network error or API error
  console.error('Failed to get tracking session:', err);
}

TypeScript

Full type definitions are included:

import type {
  CallForgeConfig,
  TrackingSession,
  TrackingLocation,
  TrackingParams,
  ReadyCallback,
  LocationReadyCallback,
  CallIntentResponse,
  CallLinkIntentResponse,
  LinkPhoneCallInput,
  RealtimeLinkPayload,
  RealtimeProfileSource,
} from '@callforge/tracking-client';

Environment URLs

| Environment | Endpoint | |-------------|----------| | Production | https://tracking.callforge.io (default) | | Staging | https://tracking-staging.callforge.io | | Dev | https://tracking-dev.callforge.io |