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.16.2

Published

Lightweight client library for the CallForge tracking API. Handles per-visitor phone number assignment (with deterministic session tokens) plus aggressive caching and preload optimization.

Downloads

773

Readme

@callforge/tracking-client

Lightweight client library for the CallForge tracking API. Handles per-visitor phone number assignment (with deterministic session tokens) plus aggressive caching and preload optimization.

This package is phone-number tracking only. Display location (city/state/ZIP rendering) is handled by the separate CallForge geo edge worker, which exposes window.__cfGeo — see the geo-edge worker documentation.

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.

Display Location Removal (v0.16, breaking)

The legacy display-location surface has been removed from this package. Marketing sites now get display location from the CallForge geo edge worker (window.__cfGeo) instead of the npm client.

Removed in 0.16.0:

  • client.getLocation(), client.getSessionAsync(), client.onLocationReady()
  • includeExtendedLocation config option (client and preload)
  • TrackingLocation, TrackingLocationSource, LocationReadyCallback types
  • The window.__cfTrackingLocation preload promise and the cf_location_v1_* localStorage cache
  • The location half of the preload snippet (it no longer calls /v1/tracking/location)

Migration:

  • Replace client.getSessionAsync().session / getSessionAsync().location with client.getSession() and the geo edge worker's window.__cfGeo respectively.
  • Phone tracking is unaffected: sessions, call intents, linkPhoneCall (including realtime.webZip), and presence links all work as before.
  • The server-side /v1/tracking/location endpoint still exists for sites pinned to ^0.15.0, but new integrations must use the geo edge worker.

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 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 request the phone session

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

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

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

Display location (city/state/ZIP) is not part of this client. Read it from the geo edge worker's window.__cfGeo instead.

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.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.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>
  • Bootstrap cache key: cf_bootstrap_v1_<siteKey>_<categoryId>
  • Session TTL: controlled by the server expiresAt response (currently 30 minutes)
  • 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,
  TrackingParams,
  ReadyCallback,
  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 |