@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, …).
Maintainers
Keywords
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:
- Onsite marketing attribution — captures UTMs, referrer, landing page, partner program ids,
internal campaigns, etc. into
sessionStorage/localStorageand forwards them to internal links and forms. - 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_fbpitself; the rest rely on their vendor script's own cookie) and decorates outbound internal links with them. - HubSpot tracking cookies — copies
hubspotutk/__hstcacross domains via the__hutk/__hstcURL parameters. - Google Tag
_gllinker — generates / parses the same_glcross-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-metricsThe 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:
- Hugo / plain JS /
<script type="module">— docs/usage-hugo-static.md - Astro (SSR-safe) — docs/usage-astro.md
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)
- Initialize
window.dataLayer(so GTM / gtag don't drop early events). storeMetrics()— persist UTMs / referrer / landing page / partner_id intosessionStorage+localStorage, and immediately decorate any signup links/forms already present in the DOM.clickIdMetrics()— write_fbpto a cookie, read every supported click ID back out of cookies (including ones vendor scripts wrote themselves), and decorate cross-domain links with them.hubspotMetrics()— same, forhubspotutk/__hstc.googleTagLinker.init()— neutralize GTM's native link/form decorators so they don't compete with thedecorate()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. Callingdecorate()immediately would produce an empty or incomplete_glvalue.
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
_glfingerprint 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.getTimezoneOffsetmust all be available.clickIdMetrics,hubspotMetrics, andgoogleTagLinker.*callassertBrowser()which throws ifwindowordocumentis undefined.storeMetrics()does not currently callassertBrowser()but still touchesdocument.location,document.referrer,localStorage, andsessionStorage; treat it as browser-only.- Cookie writes use
js-cookiewithSameSite=LaxandSecureautomatically enabled onhttps:pages. - Cookie domain is auto-detected (highest registrable domain reachable from the current
host) using a temporary
__store_metrics_domain_testcookie. Onlocalhostor raw IPs no domain attribute is used. googleTagLinker.init()anddecorate({ disableNativeGtmDecorators: true })redefinewindow.google_tag_data.gl.decoratorsto an empty array viaObject.defineProperty. Call this only on pages where you intend to drive_glyourself 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
_glvalues.googleTagLinker.init()and the defaultdisableNativeGtmDecorators: trueondecorate()clearwindow.google_tag_data.gl.decoratorsso only this package writes the parameter. GTM on the destination side still consumes_glnormally — only the source-side decoration is suppressed. fbclidis 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 nofbclid, it falls back to the Meta-managed_fbccookie (formatfb.<subdomain>.<timestamp>.<fbclid>). The package never writes a directfbclidcookie — Meta Pixel remains the sole owner of_fbc.- Click ID URL params are preserved. Unlike the marketing-attribution flow,
clickIdMetricsno longer strips click ID params from the URL — bookmarks and shared links keep them. storeMetrics()decorates links that are already in the DOM (it queriesa[href*="…"]/form[action*="…"]for each entry inURL_TO_DECORATEandINCLUDED_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 + publishFor ad-hoc beta releases:
npm version prerelease --preid=beta
npm publish --tag beta --access public