/** * Analytics: session, impression, click. * * Impression ownership (plan v1.1 §3.4) is the load-bearing idea. The SDK * renders inside the customer's page, so it is the only party that knows an * impression actually happened — the server knows only that bytes were * requested, which is not the same thing and is spectacularly not the same * thing behind a CDN. So every SDK request carries `sdk=1`, the server * suppresses its own impression, and this module owns the count. * * Which means the rules here are not conveniences, they are the integrity of * CVG: * * - ONE impression per island per page view. Not per render, not per state * change, not per React strict-mode double-invoke. * - VISIBILITY, not mount. A component rendered below the fold, or in a * background tab, has not been seen. * - the impression carries what the viewer was actually shown, including * whether the trust chrome was on. */ import type { ContentSource } from "./wire.js"; export type EventType = "impression" | "click" | "conversion" | "custom"; export interface AnalyticsContext { apiBase: string; organizationId: string; campaignId: string; variantId?: string; surfaceId?: string; /** * The same transport seam every other request honors. Without it, beacons * bypass an integrator's proxied or instrumented fetch (CSP connect-src, * test transports) and the loss is invisible — content renders, counts * read zero. */ fetchImpl?: typeof fetch; } export interface ImpressionDetail { /** * Where the copy the viewer saw came from. `cached-stale` is cached copy * served past its freshness window (still within its serving window). */ contentSource: ContentSource | "cached-stale"; /** Was adapted copy on screen (the chrome decision)? Reported so chrome * state is measurable; the visible mark itself is the integrator's opt-in. */ chromeMarked: boolean; /** W8 — set when the server served a deterministic winner. */ degraded?: string | null; island?: string; } export declare function sessionId(): string; /** Internal: a random identifier (a UUID where the platform has one). */ export declare function randomId(): string; /** * POST an event. Fire-and-forget: telemetry never blocks or breaks a render. * * Keys the server reads from `eventData`: `variantId`, `surfaceId`, * `productId`, `generatedContentId`, `context` (any JSON object), and for a * `conversion`, `amount` (a number), `currency` (an ISO 4217 code) and * `checkoutToken`. The server stores `currency` and `checkoutToken` only * when the conversion also has an `amount`. `variantId` defaults to the value in the first argument, * and `surfaceId` does too when that value is a UUID. The SDK sets * `sessionId`. `source` is kept only for values reserved for SparkleTree's * Shopify extensions. Other top-level keys are not stored; put your own * detail under `eventData.context`. */ export declare function track(context: AnalyticsContext, eventType: EventType, eventData?: Record): Promise; /** * Records at most one impression per island per page view. * * The guard is an instance, not a module global, so two islands on one page * each get their own — while a single island being re-rendered, re-subscribed * or double-invoked by React strict mode gets exactly one. */ export declare class ImpressionRecorder { private readonly context; private fired; constructor(context: AnalyticsContext); get hasFired(): boolean; /** Idempotent. Returns true only for the call that actually recorded. */ record(detail: ImpressionDetail): boolean; } export interface VisibilityOptions { /** Fraction of the element that must be visible. */ threshold?: number; /** * How long it must stay that visible before it counts. A creative that * scrolled past during a fling was not seen by anybody. */ dwellMs?: number; } /** * Fire `onVisible` once, when the element has been meaningfully on screen. * * Where IntersectionObserver does not exist, this fires immediately rather * than never: under-counting every impression in older browsers would be a * silent, systematic bias in the numbers a customer is billed and optimised * against. Over-counting there is the lesser wrong, and it is at least * visible. */ export declare function observeVisibility(element: Element, onVisible: () => void, options?: VisibilityOptions): () => void; //# sourceMappingURL=analytics.d.ts.map