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

@livechat/store-metrics

v3.1.22

Published

Browser-side analytics utilities used across the LiveChat / Text family of products (livechat.com, text.com, helpdesk.com, chatbot.com, knowledgebase.ai, openwidget.com, …).

Readme

@livechat/store-metrics

Browser-side analytics utilities used across the LiveChat / Text family of products (livechat.com, text.com, helpdesk.com, chatbot.com, knowledgebase.ai, openwidget.com, …).

The package solves four related cross-domain attribution problems:

  1. Onsite marketing attribution — captures UTMs, referrer, landing page, partner program ids, internal campaigns, etc. into sessionStorage / localStorage and forwards them to internal links and forms.
  2. Ad platform click IDs — reads gclid, wbraid, gbraid, _fbp, rdt_cid, qclid, li_fat_id, fbclid (via Meta _fbc) from the URL and cookies (persisting _fbp itself; the rest rely on their vendor script's own cookie) and decorates outbound internal links with them.
  3. HubSpot tracking cookies — copies hubspotutk / __hstc across domains via the __hutk / __hstc URL parameters.
  4. Google Tag _gl linker — generates / parses the same _gl cross-domain parameter that GA4 / GTM / gtag use so that GA cookies (_ga, _ga_*, _gcl_*, FPLC, FPAU, …) can be reconstructed on the destination domain.

The package is browser only. It reads and writes document.cookie, window.location, localStorage, sessionStorage, and the DOM. It does not run during SSR — see docs/usage-astro.md for the recommended client-only pattern.


Installation

npm install --save @livechat/store-metrics

The package ships as ESM + CJS with .d.ts typings and also has a UMD/IIFE build for script-tag usage (see dist/ after npm run build).


Modules

| Module / export | Purpose | Docs | | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | | storeMetrics, extractMetrics, getStoredMetrics | Capture UTMs / referrer / landing page / partner_id and forward to internal links | docs/store-metrics.md | | clickIdMetrics | Read ad platform click IDs from URL/cookies, persist _fbp, and decorate outbound internal links | docs/click-id-metrics.md | | hubspotMetrics | Cross-domain transfer of hubspotutk / __hstc cookies | docs/hubspot-metrics.md | | googleTagLinker (init, get, read, decorate) | Generate / parse / attach the Google Tag _gl cross-domain linker parameter | docs/google-tag-linker.md | | decorateNow | Re-apply all four decoration concerns (stored metrics + click IDs + HubSpot + _gl) to specific elements — ideal for SPA route changes | docs/decorate-now.md | | Constants (INCLUDED_DOMAINS, URL_TO_DECORATE, …) | Shared domain / parameter lists | see individual module docs |

Also re-exported: types (MetricsOptions, DecorateEntity, LinkerGetSettings, LinkerReadSettings, LinkerDecorateSettings, QueryParam, StoredData, CookieName, DecorateNowTarget, DecorateNowOptions).

Usage recipes:


Quick start

import {
  storeMetrics,
  clickIdMetrics,
  hubspotMetrics,
  googleTagLinker,
  INCLUDED_DOMAINS,
} from '@livechat/store-metrics'

// 1. Ensure GTM's dataLayer exists before any GA / GTM code runs
window.dataLayer = window.dataLayer || []

// 2. Inbound pass — runs once on page load, as early as possible
storeMetrics() // UTMs, referrer, landing page, partner_id
clickIdMetrics() // read/decorate ad platform click IDs, persist _fbp
hubspotMetrics() // hubspotutk / __hstc cookies

// 3. Suppress GTM's built-in cross-domain decorators (we drive _gl ourselves)
googleTagLinker.init()

// 4. Outbound pass — decorate cross-domain links / forms at interaction time,
//    so that GA / GTM cookies have already been written by the time _gl is built.
const decorate = (entity: HTMLAnchorElement | HTMLFormElement) =>
  googleTagLinker.decorate({
    entity,
    allowedDomains: INCLUDED_DOMAINS,
    ga4Streams: ['XXXXXXXXXX'], // your GA4 measurement IDs (without "G-")
  })

document.addEventListener(
  'mousedown',
  (e) => {
    const a = (e.target as Element).closest('a')
    if (a) decorate(a)
  },
  true,
)

document.addEventListener(
  'submit',
  (e) => {
    if (e.target instanceof HTMLFormElement) decorate(e.target)
  },
  true,
)

Recommended initialization order

The integration is a two-pass flow. The order matters because each step depends on state written by the previous one (URL params → cookies → _gl).

Pass 1 — Inbound (on page load)

  1. Initialize window.dataLayer (so GTM / gtag don't drop early events).
  2. storeMetrics() — persist UTMs / referrer / landing page / partner_id into sessionStorage + localStorage, and immediately decorate any signup links/forms already present in the DOM.
  3. clickIdMetrics() — write _fbp to a cookie, read every supported click ID back out of cookies (including ones vendor scripts wrote themselves), and decorate cross-domain links with them.
  4. hubspotMetrics() — same, for hubspotutk / __hstc.
  5. googleTagLinker.init() — neutralize GTM's native link/form decorators so they don't compete with the decorate() calls below.

Do not call googleTagLinker.get() / decorate() here. The GA / GCL cookies you want to forward are written by GTM / gtag after they boot, which is usually after this synchronous block. Calling decorate() immediately would produce an empty or incomplete _gl value.

Pass 2 — Outbound (on click / submit)

Attach mousedown and submit listeners that call googleTagLinker.decorate({ entity, … }). Decorating at interaction time guarantees:

  • GTM / gtag have had time to set _ga, _ga_*, _gcl_*, FPLC, FPAU.
  • The latest cookie values (including consent updates) are used.
  • The freshly-rebuilt _gl fingerprint is still inside its 1-minute validity window when the destination page parses it.

googleTagLinker.decorate() already skips same-domain destinations and respects the allowedDomains allowlist, so a single delegated listener on the document is safe.


Browser / runtime assumptions

The package assumes a real browser environment and will throw or no-op otherwise. Specifically:

  • window, document, localStorage, sessionStorage, document.cookie, URL, URLSearchParams, history.replaceState, window.btoa / window.atob, window.navigator.{userAgent,language}, Date.getTimezoneOffset must all be available.
  • clickIdMetrics, hubspotMetrics, and googleTagLinker.* call assertBrowser() which throws if window or document is undefined.
  • storeMetrics() does not currently call assertBrowser() but still touches document.location, document.referrer, localStorage, and sessionStorage; treat it as browser-only.
  • Cookie writes use js-cookie with SameSite=Lax and Secure automatically enabled on https: pages.
  • Cookie domain is auto-detected (highest registrable domain reachable from the current host) using a temporary __store_metrics_domain_test cookie. On localhost or raw IPs no domain attribute is used.
  • googleTagLinker.init() and decorate({ disableNativeGtmDecorators: true }) redefine window.google_tag_data.gl.decorators to an empty array via Object.defineProperty. Call this only on pages where you intend to drive _gl yourself instead of relying on GTM's cross-domain settings.

If you need to render the same code on the server (Astro, Next, Hugo with a JS pipeline), guard every call with if (typeof window !== 'undefined') — see docs/usage-astro.md.


Integration notes

  • GTM native decorators vs. this package. GTM can be configured to auto-decorate cross-domain links itself. If both run, you can end up with duplicate or conflicting _gl values. googleTagLinker.init() and the default disableNativeGtmDecorators: true on decorate() clear window.google_tag_data.gl.decorators so only this package writes the parameter. GTM on the destination side still consumes _gl normally — only the source-side decoration is suppressed.
  • fbclid is read from the current URL first, so the very first click on a landing page carries the fresh value even before Meta Pixel has run. If the URL has no fbclid, it falls back to the Meta-managed _fbc cookie (format fb.<subdomain>.<timestamp>.<fbclid>). The package never writes a direct fbclid cookie — Meta Pixel remains the sole owner of _fbc.
  • Click ID URL params are preserved. Unlike the marketing-attribution flow, clickIdMetrics no longer strips click ID params from the URL — bookmarks and shared links keep them.
  • storeMetrics() decorates links that are already in the DOM (it queries a[href*="…"] / form[action*="…"] for each entry in URL_TO_DECORATE and INCLUDED_DOMAINS). If your site adds links after load, re-run the decoration step or use the click/submit listener pattern.
  • Debug mode. Set localStorage.debug = 'true' in the browser console to enable verbose logging from all modules.

Development

npm install
npm run dev        # opens http://localhost:5173 with the debug page (index.html)
npm test           # vitest
npm run lint       # tsc --noEmit
npm run build      # tsc + vite build (ESM + CJS + IIFE + .d.ts)

Publishing

This package uses Changesets:

npm run changeset           # describe the change
npm run version-packages    # apply version bumps
npm run release             # build + publish

For ad-hoc beta releases:

npm version prerelease --preid=beta
npm publish --tag beta --access public