İçeriğe geç
Belgeler menüsüWeb SDK (@metrics/web)

@metrics/web

Browser SDK and tracking pixel of Postlip Metrics. It speaks the Segment Tracking API Spec (see docs/api/tracking-api.md): page views, product events, identity, attribution and consent, plus automatic capture of engagement, clicks, errors, forms and the page layout that heatmaps are drawn on. It is ≤ 12 KB gzip, has no dependencies and sets no cookies.

Bu sayfa derleme sırasında depodaki packages/sdk-web/README.md dosyasından üretilir.

Bu sayfa paketin README dosyasından üretilir ve İngilizcedir; kod, seçenek ve API adları paketle birebir aynıdır.

Install

Script tag (recommended for websites): paste the snippet into <head>. It queues calls until m.js arrives.

HTML
<script>
  !function(){var m=window.metrics=window.metrics||[];if(m.load)return;m.q=[];["page","track","identify","group","alias","reset","consent"].forEach(function(k){m[k]=function(){m.q.push([k].concat([].slice.call(arguments)))}});
  var s=document.createElement("script");s.async=1;s.src="https://collect.<your-domain>/m.js";document.head.appendChild(s);
  m.load=function(k,o){m.q.unshift(["load",k,o])};}();
  metrics.load("pk_live_…", { consent: "required" });
  metrics.page();
</script>

When m.js is loaded from the collector, the collector's origin is the default host.

npm / bundlers (no globals):

TypeScript
import { createMetrics } from '@metrics/web';

const metrics = createMetrics();
metrics.load('pk_live_…', { host: 'https://collect.<your-domain>' });

examples/index.html is a runnable page.

API

CallNotes
load(writeKey, options?)Once per page. Reads GET {host}/v1/config/{writeKey} (cached per tab for ttlSeconds). If that fails, built-in defaults are used.
page(name?, properties?)Page views are captured automatically, including SPA routes. A page() call within 100 ms of a route change replaces the automatic one.
track(event, properties?, messageId?)Product event. Names starting with $ are reserved for system events and refused. messageId (1–100 printable ASCII characters, no spaces) is the id a server copy of the same event carries, so the event is stored once; anything else is ignored and the SDK makes its own id.
identify(userId?, traits?)userId must be opaque (no e-mail or phone). It is kept for later messages.
group(groupId, traits?)Links the person to an account.
alias(userId, previousId?)previousId defaults to the current user id or anonymous id.
reset()On logout: new anonymous id, no user id, new session.
consent({ analytics?, marketing? })Applies and remembers the visitor's choice (see below).
ready(cb?)Promise / callback once the config is applied and queued calls are sent.
flush()Sends the queue now.
getAnonymousId(), getContext()Returns ids, session, consent, attribution (with method), click ids and the Google Analytics ids (ga), so a server-side event can join this visit (pass it as the message context).
destroy()Removes listeners and wrappers.

Options

host, consent (required · implied · none), autocapture (false or per-toggle overrides), debug (see Debug mode), gaMeasurementId (G-…, see Google Analytics ids), respectGpc, trackHashChanges, remoteConfig: false, flushAt (20), flushInterval (5000 ms), and any remote config field: excludedPaths, maskSelectors, ignoreSelectors, stripQueryParams, sessionTimeoutMinutes, interactionSampleRate, dataLayerEvents. Options override what the panel configured.

Debug mode

debug: true logs every message, drop and server rejection to the console. Opening any page of the site with ?metrics_debug=1 does the same for the rest of the tab's session, without a code change, and also marks the messages as test traffic (context.test): they show up in the live event stream but stay out of the reports. ?metrics_debug=0 turns it off. The switch lives in sessionStorage (mtr_d).

In debug mode the npm build also asks the remote config for the app's tracking plan in brief (?schemaHints=1: planned events with their required properties, blocked events, the schema mode) and warns in the console when a track() call names a blocked event, an unplanned event (will be rejected in strict mode) or misses a required property. The event is still sent; the server decides. The script-tag build (m.js) leaves these hints out to stay within its size budget: there, the server's rejections are logged as before.

Internal traffic

Opening any page of the site with ?metrics_internal=1 marks this browser as internal: every message carries context.internal: true, and the reports leave it out like the app's other internal traffic (traffic filters, internal IP ranges). The flag stays in localStorage (mtr_internal) until a page is opened with ?metrics_internal=0. Share the link with your team; it sets nothing else.

ModeBefore consent()After consent({ analytics: true, marketing: true })
required (default)Page views and page-leave timing are counted cookieless: the anonymous id and session live in sessionStorage (this tab only). Nothing goes to localStorage, no click ids are sent, and no interaction capture runs.Ids move to localStorage (the same anonymous id, so the earlier page views join). Click ids seen on the landing page are sent.
impliedEverything is on until the visitor declines. Declining purges localStorage; cookieless counting continues.
noneEverything is on.
  • analytics controls persistent identity, the retry queue and interaction capture.
  • marketing controls click ids (gclid, fbclid/fbc/fbp, li_fat_id, msclkid, ttclid, …) and whether first_touch / last_non_direct_touch persist across visits.
  • Every message carries context.consent. The choice itself is stored in localStorage (mtr_c) so it holds on the next page.
  • context.attribution.method says how the touches were kept: consent when they persist across visits (marketing consent), same_visit when they are only this tab's. A server event that forwards getContext() carries it too, so the lead's attribution shows how much of the history was visible.
  • respectGpc: true turns marketing off when navigator.globalPrivacyControl is set.

Google Analytics ids

With analytics consent, the SDK reads the Google Analytics cookies the site's GA4 tag already set — _ga (client id) and _ga_<measurement id> (session id) — and sends them as context.ga { clientId, sessionId }. The GA4 Measurement Protocol destination uses them, so the conversions it sends join the visitor's GA4 session instead of starting a new one. If the page runs several GA4 properties, pass gaMeasurementId: 'G-…' to pick the right session cookie (otherwise the first _ga_… cookie is read). Without analytics consent the cookies are not read; the SDK never writes them.

Automatic capture

Every event below can be switched off from the panel or with autocapture. Interaction events (clicks, scroll, layout, forms) follow interactionSampleRate, which is decided once per session. Coordinates are 0…1000 of the document on both axes.

EventWhen
pageLoad, pushState / replaceState (a new path), popstate, bfcache restore, #hash with trackHashChanges.
$pageleaveWhen the tab is hidden (final: false) and on pagehide / route change (final: true). Carries the deepest scroll and active time: the tab is visible and there was input in the last 30 s. Hiding the tab pauses the page view; it does not end it. scroll_reversals counts how often the reader turned back by more than half a screen (the excessive-scrolling signal).
$scroll25 / 50 / 75 / 90 / 100 %, once each per page view.
$clickTarget kind, tag, label (≤ 80 chars), selector, href, section. dead means no DOM or URL change within 1 s, so the event is sent after that second. Clicks inside fixed/sticky elements use the viewport position.
$rageclick≥ 3 clicks within 1 s inside 30 px, one event per burst.
$outbound, $downloadLinks to another site; file links (pdf, zip, docx, …) and download links.
$formstart, submit, abandon, with the field name and type only. Values are never read.
$exceptionwindow.onerror and unhandled rejections. Query strings are stripped and e-mails masked. Capped per page.
$layoutThe page skeleton (headings, links, buttons, fields, images, media) with a fixed flag and a structure hash. Sent once per path, device class and hash per session.
$webvitalsLCP, CLS, INP, FCP, TTFB through PerformanceObserver. Off by default.
dataLayerdataLayer.push({ event }) for the names in dataLayerEvents becomes track(event, props). GTM keys are dropped. The entry's event_id (or messageId) becomes the message's messageId when it is a valid one, so the same event sent from your server with that id is stored once; it is not repeated as a property.

Privacy

  • data-metrics-ignore: the element and its children are never captured (clicks, layout, forms).
  • data-metrics-mask, or any element matching maskSelectors: the label becomes ***.
  • Inputs, textareas, selects and contenteditable regions never yield text.
  • Excluded paths (from the panel, same patterns as the server: /panel, /account/*, /**/private): nothing is captured there automatically. Your own track() calls still go out, but with the page reduced to [excluded]. When such a page shows up as a referrer or previous page, it becomes https://site/[excluded].
  • Captured URLs lose the stripQueryParams entries, sensitive keys (email, token, password…), e-mail or phone-looking values, credentials and #…=… fragments. utm_* and click ids are exempt.
  • No IP handling and no cookies are written. The _fbc, _fbp and li_fat_id cookies are only read, and only sent with marketing consent. The _ga and _ga_<id> cookies are only read, with analytics consent.
  • Without consent nothing goes to localStorage, except the internal-browser switch when someone opens a ?metrics_internal=1 link on purpose.

Transport

Messages are batched (20 messages, 5 s, or when the page is hidden) into POST {host}/v1/batch with a text/plain body { writeKey, batch, sentAt }, so there is no CORS preflight. Regular sends use fetch(keepalive, credentials: 'omit'), so failures can be retried; when the page is hidden, navigator.sendBeacon is used. Bodies are split under the contract limits (≤ 500 KB / 500 messages, and ≤ 64 KB per beacon). A message over 32 KB is dropped and logged in debug mode. Batches that fail with 5xx, 429 (honouring Retry-After) or a network error are retried with backoff from 10 s up to 24 h. With analytics consent, the bounded retry queue survives reloads in localStorage.