@metricinsights/ca-analytics 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/constants.ts","../src/core/config.ts","../src/core/event.ts","../src/core/transport.ts","../src/core/queue.ts","../src/core/session.ts","../src/core/signals/heartbeat.ts","../src/core/signals/miRender.ts","../src/core/signals/pageview.ts","../src/core/analytics.ts","../src/react/useAnalytics.ts"],"sourcesContent":["/** Entity name bound on every tracked app's portal page. Not configurable. */\nexport const ENTITY_NAME = 'analytics_events';\n\n/** From package.json at build time. */\nexport const VERSION = __VERSION__;\n\n/** MI serves portal pages at /p/<name>, /pl/<name> or /pt/<name>. */\nexport const MI_PAGE_PATH_RE = /^\\/(p[tl]?)\\/([^/]+)(.*)$/;\n\n/** Per-field payload caps that keep a batch within sendBeacon's 64 KiB quota, not schema limits. */\nexport const WIDTHS = {\n app: 100,\n event: 100,\n page_path: 400,\n version: 20,\n meta: 2000,\n} as const;\n\n/** Emitted by ca-analytics. track() rejects them so app code cannot forge them. */\nexport const RESERVED_EVENTS = ['page_view', 'heartbeat', 'component_render', 'element_click'] as const;\n\n/** Custom event names must match this or track() warns once. Silent drift creates permanent duplicate series. */\nexport const EVENT_NAME_RE = /^[a-z][a-z0-9_]{0,99}$/;\n\n/** Per-key meta caps, applied before serializeMeta so one long value can't push another key out. */\nexport const LIMITS = { referrer: 300, query: 300 } as const;\n","import { ENTITY_NAME, MI_PAGE_PATH_RE, WIDTHS } from './constants';\nimport type { Options, ResolvedConfig } from './types';\n\n/** Unsubstituted PP variable, e.g. '[Custom App Analytics Enabled]'. Treated as unset. */\nconst UNSUBSTITUTED_RE = /^\\[[A-Za-z0-9][A-Za-z0-9 _-]*\\]$/;\n\nfunction portalVariables(): Record<string, unknown> {\n if (typeof window === 'undefined') return {};\n\n const block = (window as unknown as Record<string, unknown>).PP_VARIABLES;\n\n return block && typeof block === 'object' && !Array.isArray(block) ? (block as Record<string, unknown>) : {};\n}\n\n/** PP variables arrive as strings, so '0' and 'false' must not read truthy. */\nfunction readFlag(key: string): boolean | undefined {\n const raw = portalVariables()[key];\n\n if (typeof raw === 'boolean') return raw;\n if (typeof raw !== 'string') return undefined;\n\n const value = raw.trim().toLowerCase();\n\n if (value === '' || UNSUBSTITUTED_RE.test(raw.trim())) return undefined;\n\n return value !== '0' && value !== 'false' && value !== 'n' && value !== 'no';\n}\n\nfunction matchPage(): RegExpExecArray | null {\n if (typeof window === 'undefined' || !window.location) return null;\n\n return MI_PAGE_PATH_RE.exec(window.location.pathname);\n}\n\n/** Route below the portal page. '/' at the page root. */\nexport function currentPagePath(): string {\n const match = matchPage();\n\n if (!match) return '/';\n\n const rest = match[3];\n\n return (rest && rest !== '' && rest !== '/' ? rest : '/').slice(0, WIDTHS.page_path);\n}\n\n/** Runtime config, or null when ca-analytics must stay silent: disabled, or not on a portal page. */\nexport function resolveConfig(options: Options = {}): ResolvedConfig | null {\n const enabled = options.enabled ?? readFlag('CUSTOM_APP_ANALYTICS_ENABLED') ?? true;\n\n if (!enabled) return null;\n\n const app = (options.app ?? matchPage()?.[2] ?? '').slice(0, WIDTHS.app);\n\n if (!app) return null;\n\n return { app, endpoint: `/data/page/${encodeURIComponent(app)}/${ENTITY_NAME}` };\n}\n","import { VERSION, WIDTHS } from './constants';\nimport type { EventRow } from './types';\n\nexport interface RowInput {\n app: string;\n event: string;\n sessionId: string;\n pagePath: string;\n elementId?: number;\n meta?: Record<string, unknown>;\n}\n\nconst pad2 = (n: number) => String(n).padStart(2, '0');\n\n/** Integer only: a float or string value widens the `int` column permanently. */\nexport function toId(value: unknown): number {\n const n = Number(value);\n\n return Number.isFinite(n) ? Math.trunc(n) : 0;\n}\n\n/** Space-separated, never ISO — a `T`/`Z` sniffs as text and the column stops being `datetime`. */\nexport function formatTs(date: Date): string {\n return (\n `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())} ` +\n `${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}:${pad2(date.getUTCSeconds())}`\n );\n}\n\nfunction stringify(value: unknown): string | null {\n try {\n const out = JSON.stringify(value);\n\n return typeof out === 'string' ? out : null;\n } catch {\n return null;\n }\n}\n\n/** JSON string capped at WIDTHS.meta. Drops whole keys instead of slicing — a sliced payload is invalid JSON. */\nexport function serializeMeta(meta?: Record<string, unknown>): string {\n if (!meta) return '{}';\n\n const full = stringify(meta);\n\n if (full !== null && full.length <= WIDTHS.meta) return full;\n\n const kept: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(meta)) {\n const candidate = stringify({ ...kept, [key]: value, _truncated: 1 });\n\n if (candidate === null || candidate.length > WIDTHS.meta) continue;\n\n kept[key] = value;\n }\n\n return stringify({ ...kept, _truncated: 1 }) ?? '{}';\n}\n\nexport function uuid(): string {\n const c = typeof crypto !== 'undefined' ? crypto : undefined;\n\n if (c && typeof c.randomUUID === 'function') return c.randomUUID();\n\n // Older Safari has getRandomValues but not randomUUID.\n const bytes = new Uint8Array(16);\n\n if (c && typeof c.getRandomValues === 'function') {\n c.getRandomValues(bytes);\n } else {\n for (let i = 0; i < bytes.length; i += 1) bytes[i] = Math.floor(Math.random() * 256);\n }\n\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/** Only place a row is built. `ts`/`element_id` type mistakes here lock the shared dataset's schema permanently. */\nexport function createRow(input: RowInput): EventRow {\n return {\n id: uuid(),\n ts: formatTs(new Date()),\n app: input.app.slice(0, WIDTHS.app),\n event: input.event.slice(0, WIDTHS.event),\n session_id: input.sessionId.slice(0, 36),\n page_path: input.pagePath.slice(0, WIDTHS.page_path),\n element_id: toId(input.elementId),\n version: VERSION.slice(0, WIDTHS.version),\n meta: serializeMeta(input.meta),\n };\n}\n","export type SendOutcome = 'ok' | 'failed';\n\nconst RETRY_DELAY_MS = 2000;\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\n/** Rows arrive pre-serialized; joining as `[a,b,c]` matches JSON.stringify(rows) byte-for-byte without a second pass. */\nconst toArrayBody = (rows: string[]): string => `[${rows.join(',')}]`;\n\nasync function post(endpoint: string, body: string): Promise<{ ok: boolean; retryable: boolean }> {\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body,\n });\n\n if (!response.ok) {\n return { ok: false, retryable: response.status === 429 || response.status >= 500 };\n }\n\n const payload = (await response.json().catch(() => null)) as { resultCode?: number } | null;\n\n // 200 with a non-zero resultCode is a rejected insert, not a success.\n if (payload && typeof payload.resultCode === 'number' && payload.resultCode !== 0) {\n return { ok: false, retryable: false };\n }\n\n return { ok: true, retryable: false };\n } catch {\n return { ok: false, retryable: true };\n }\n}\n\n/** One retry max, same rows — a lost-response double-insert is caught by readers using COUNT(DISTINCT id). */\nexport async function send(endpoint: string, rows: string[]): Promise<SendOutcome> {\n if (rows.length === 0) return 'ok';\n\n const body = toArrayBody(rows);\n const first = await post(endpoint, body);\n\n if (first.ok) return 'ok';\n if (!first.retryable) return 'failed';\n\n await sleep(RETRY_DELAY_MS);\n\n return (await post(endpoint, body)).ok ? 'ok' : 'failed';\n}\n\n/** Unload path. No retry possible — the page is going away. */\nexport function sendOnUnload(endpoint: string, rows: string[]): void {\n if (rows.length === 0) return;\n\n const body = toArrayBody(rows);\n\n try {\n const blob = new Blob([body], { type: 'application/json' });\n\n if (navigator.sendBeacon && navigator.sendBeacon(endpoint, blob)) return;\n } catch {\n // fall through to keepalive fetch\n }\n\n try {\n void fetch(endpoint, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body,\n keepalive: true,\n });\n } catch {\n // Dropped. Nothing further possible at unload.\n }\n}\n","import { send, sendOnUnload } from './transport';\nimport type { SendOutcome } from './transport';\nimport type { EventRow } from './types';\n\n/** A row's JSON already computed once in push(); send() joins these into the array body instead of re-stringifying. */\ntype Buffered = { json: string; size: number };\n\nconst FLUSH_INTERVAL_MS = 15_000;\nconst MAX_FLUSH_ROWS = 50;\n/** Half of sendBeacon's 64 KiB cumulative quota. The server has no practical ceiling. */\nconst MAX_FLUSH_BYTES = 30_000;\nconst MAX_CONSECUTIVE_FAILURES = 3;\n\n// Counts encoded UTF-8 bytes, not UTF-16 code units — what sendBeacon actually charges against its quota.\nconst textEncoder = new TextEncoder();\n\nexport interface Queue {\n push(row: EventRow): void;\n flush(): void;\n flushOnUnload(): void;\n stop(): void;\n}\n\nexport function createQueue(endpoint: string, onKilled?: () => void): Queue {\n let buffer: Buffered[] = [];\n let bytes = 0;\n let failures = 0;\n let stopped = false;\n let inFlight = false;\n // Guards re-entrancy: onKilled calls shutdown() -> stop() again, but the kill must fire once.\n let killed = false;\n\n const timer = setInterval(() => {\n void flush();\n }, FLUSH_INTERVAL_MS);\n\n function stop(): void {\n stopped = true;\n buffer = [];\n bytes = 0;\n clearInterval(timer);\n }\n\n async function flush(): Promise<void> {\n if (stopped || inFlight || buffer.length === 0) return;\n\n const batch = buffer;\n\n buffer = [];\n bytes = 0;\n inFlight = true;\n\n try {\n let outcome: SendOutcome;\n\n // send() isn't expected to reject, but a rejection must not become an unhandled promise rejection.\n try {\n outcome = await send(\n endpoint,\n batch.map((b) => b.json),\n );\n } catch {\n outcome = 'failed';\n }\n\n if (outcome === 'ok') {\n failures = 0;\n return;\n }\n\n failures += 1;\n\n // Batch dropped, not re-buffered: transport already retried once, and re-sending a batch whose\n // response was merely lost is what creates duplicates.\n if (failures >= MAX_CONSECUTIVE_FAILURES && !killed) {\n killed = true;\n stop();\n\n // Hand the kill up to the owner so it can stop the timers/listeners still producing rows.\n try {\n onKilled?.();\n } catch {\n // swallowed — a host callback must never escape\n }\n }\n } finally {\n inFlight = false;\n }\n }\n\n return {\n push(row: EventRow): void {\n if (stopped) return;\n\n let json: string;\n\n try {\n json = JSON.stringify(row);\n } catch {\n return;\n }\n\n const size = textEncoder.encode(json).length;\n\n buffer.push({ json, size });\n bytes += size;\n\n if (buffer.length >= MAX_FLUSH_ROWS || bytes >= MAX_FLUSH_BYTES) void flush();\n },\n flush(): void {\n void flush();\n },\n flushOnUnload(): void {\n if (stopped || buffer.length === 0) return;\n\n const batch = buffer;\n\n buffer = [];\n bytes = 0;\n sendOnUnload(\n endpoint,\n batch.map((b) => b.json),\n );\n },\n stop,\n };\n}\n","const SID_KEY = 'ca_pa_sid';\nconst LAST_KEY = 'ca_pa_last';\nconst IDLE_MS = 30 * 60 * 1000;\nconst TOUCH_THROTTLE_MS = 5000;\n\nlet memoryId: string | null = null;\nlet lastWrittenAt = 0;\n\n/** Safari private mode and blocked-storage policies throw on access, not just on write. */\nfunction read(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction write(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // In-memory session for the page lifetime beats no tracking.\n }\n}\n\nfunction newId(): string {\n try {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID();\n } catch {}\n\n return `s-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/** Clears in-memory state only. Storage stays, so a reload still finds the session. */\nexport function resetSession(): void {\n memoryId = null;\n lastWrittenAt = 0;\n}\n\n/** Session id, minting a new one after 30 min idle. Extends the window on every call. */\nexport function getSessionId(): string {\n const now = Date.now();\n const storedId = read(SID_KEY);\n const rawLast = read(LAST_KEY);\n const last = rawLast === null ? Number.NaN : Number(rawLast);\n const alive = Number.isFinite(last) && now - last <= IDLE_MS;\n\n let id: string;\n\n if (storedId && alive) {\n // Trusting storage here is what survives a reload.\n id = storedId;\n } else if (!storedId && memoryId) {\n // Blocked localStorage always reads null; reusing the memory id is the only stable fallback.\n id = memoryId;\n } else {\n id = newId();\n write(SID_KEY, id);\n lastWrittenAt = 0;\n }\n\n memoryId = id;\n\n if (now - lastWrittenAt >= TOUCH_THROTTLE_MS) {\n write(LAST_KEY, String(now));\n lastWrittenAt = now;\n }\n\n return id;\n}\n","export const HEARTBEAT_FAST_MS = 60_000;\nexport const HEARTBEAT_SLOW_MS = 300_000;\nexport const HEARTBEAT_FAST_COUNT = 5;\n\n/** Tapered, visible-only — readers derive engaged time as beats x interval, so a hidden beat would count idle time. */\nexport function startHeartbeat(emit: () => void): { stop(): void } {\n let beats = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let stopped = false;\n\n const interval = () => (beats < HEARTBEAT_FAST_COUNT ? HEARTBEAT_FAST_MS : HEARTBEAT_SLOW_MS);\n\n function schedule(): void {\n if (stopped || timer !== undefined) return;\n\n timer = setTimeout(() => {\n timer = undefined;\n\n if (stopped || document.visibilityState !== 'visible') return;\n\n beats += 1;\n\n try {\n emit();\n } catch {\n // A failed emit must not stop the timer.\n }\n\n schedule();\n }, interval());\n }\n\n function clear(): void {\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n }\n\n const onVisibilityChange = () => {\n if (stopped) return;\n\n if (document.visibilityState === 'visible') {\n schedule();\n } else {\n // Discard the partial interval rather than credit time the user was away.\n clear();\n }\n };\n\n document.addEventListener('visibilitychange', onVisibilityChange);\n\n if (document.visibilityState === 'visible') schedule();\n\n return {\n stop(): void {\n stopped = true;\n clear();\n document.removeEventListener('visibilitychange', onVisibilityChange);\n },\n };\n}\n","import { toId } from '../event';\n\nexport const RENDER_CAP = 50;\n\nexport interface RenderPayload {\n elementId: number;\n meta: Record<string, unknown>;\n}\n\n/** Listens on `document` only: `MiNamespace.render()` dispatches a bubbling CustomEvent, so load order doesn't matter. */\nexport function startMiRender(emit: (payload: RenderPayload) => void): { stop(): void } {\n let stopped = false;\n const counts = new Map<string, number>();\n\n const onRender = (event: Event) => {\n if (stopped) return;\n\n try {\n const detail = (event as CustomEvent).detail as { component?: unknown; props?: unknown } | undefined;\n\n if (!detail || typeof detail !== 'object') return;\n\n const component = typeof detail.component === 'string' ? detail.component : '';\n\n if (!component) return;\n\n const props = (detail.props && typeof detail.props === 'object' ? detail.props : {}) as Record<string, unknown>;\n // Apps send either {element, segment} or {element_id, segment_value_id}.\n const elementId = toId(props.element ?? props.element_id ?? 0);\n const segmentId = toId(props.segment ?? props.segment_value_id ?? 0);\n\n const key = `${component}|${elementId}|${segmentId}`;\n const seq = (counts.get(key) ?? 0) + 1;\n\n // Some apps re-render on every filter change; uncapped, one page burns the throttle budget.\n if (seq > RENDER_CAP) return;\n\n counts.set(key, seq);\n\n const meta: Record<string, unknown> = {\n component,\n element_id: elementId,\n segment_id: segmentId,\n };\n\n if (seq === RENDER_CAP) meta.capped = 1;\n\n emit({ elementId, meta });\n } catch {\n // Never throw into the host render path.\n }\n };\n\n document.addEventListener('mi-render', onRender);\n\n return {\n stop(): void {\n stopped = true;\n document.removeEventListener('mi-render', onRender);\n counts.clear();\n },\n };\n}\n","export interface PageViewSignal {\n stop(): void;\n emitNow(path?: string): void;\n}\n\n/** Absolute for an inbound hop, bare route after — a route in another app is not comparable here. */\nconst inboundReferrer = (): string => {\n try {\n const raw = document.referrer;\n\n if (!raw) return '';\n\n const url = new URL(raw);\n\n return `${url.origin}${url.pathname}`;\n } catch {\n return '';\n }\n};\n\n/** Emits once on start, then on every history navigation. The original history method always still runs. */\nexport function startPageView(\n emit: (path: string, referrer: string) => void,\n currentPath: () => string,\n): PageViewSignal {\n let stopped = false;\n let lastPath: string | null = null;\n let lastLocation: string | null = null;\n\n // Dedupe key: path alone can't tell a `?tab=` change from a no-op replaceState.\n const locationKey = (path: string): string => {\n try {\n return `${path}${window.location.search}`;\n } catch {\n return path;\n }\n };\n\n // resolvePath is a thunk so a throwing host currentPath() is caught here too, not in the caller.\n // dedupe is off for emitNow(): an explicit trackPageView() must always send.\n const safeEmit = (resolvePath: () => string, dedupe: boolean) => {\n if (stopped) return;\n\n try {\n const path = resolvePath();\n const location = locationKey(path);\n\n // Routers replaceState for scroll and filter sync without moving. Not a view.\n if (dedupe && location === lastLocation) return;\n\n // GA4 semantics: referrer is the previous page_view's path, not document.referrer, except first.\n const referrer = lastPath === null ? inboundReferrer() : lastPath;\n\n lastPath = path;\n lastLocation = location;\n emit(path, referrer);\n } catch {\n // swallowed\n }\n };\n\n const originalPush = window.history.pushState;\n const originalReplace = window.history.replaceState;\n\n const patch = (original: typeof window.history.pushState) =>\n function patched(this: History, ...args: Parameters<typeof window.history.pushState>) {\n const result = original.apply(this, args);\n\n safeEmit(currentPath, true);\n\n return result;\n };\n\n const patchedPush = patch(originalPush);\n const patchedReplace = patch(originalReplace);\n\n window.history.pushState = patchedPush;\n window.history.replaceState = patchedReplace;\n\n const onPopState = () => safeEmit(currentPath, true);\n\n window.addEventListener('popstate', onPopState);\n\n safeEmit(currentPath, true);\n\n return {\n stop(): void {\n stopped = true;\n lastPath = null;\n lastLocation = null;\n\n // Restore only if this signal still owns the patch — another startPageView may have patched over it.\n if (window.history.pushState === patchedPush) {\n window.history.pushState = originalPush;\n }\n\n if (window.history.replaceState === patchedReplace) {\n window.history.replaceState = originalReplace;\n }\n\n window.removeEventListener('popstate', onPopState);\n },\n emitNow(path?: string): void {\n safeEmit(() => path ?? currentPath(), false);\n },\n };\n}\n","import { EVENT_NAME_RE, LIMITS, RESERVED_EVENTS } from './constants';\nimport { currentPagePath, resolveConfig } from './config';\nimport { createRow, toId } from './event';\nimport { createQueue, type Queue } from './queue';\nimport { getSessionId, resetSession } from './session';\nimport { startHeartbeat } from './signals/heartbeat';\nimport { startMiRender } from './signals/miRender';\nimport { startPageView, type PageViewSignal } from './signals/pageview';\nimport type { JsonValue, Options } from './types';\n\ntype Emit = (\n event: string,\n extra?: { pagePath?: string; elementId?: number; meta?: Record<string, unknown> },\n) => void;\n\ninterface Runtime {\n queue: Queue;\n emit: Emit;\n detachUnload: () => void;\n pageView: PageViewSignal;\n stopHeartbeat: () => void;\n stopMiRender: () => void;\n}\n\nlet runtime: Runtime | null = null;\nconst warnedOnce = new Set<string>();\n\n/** Nothing in ca-analytics may throw into the host app. */\nfunction guard(fn: () => void): void {\n try {\n fn();\n } catch {\n // swallowed\n }\n}\n\n/** Warns about caller misuse only the first time `key` is seen. */\nfunction warnOnce(key: string, message: string): void {\n if (warnedOnce.has(key)) return;\n\n warnedOnce.add(key);\n console.warn(`[ca-analytics] ${message}`);\n}\n\nfunction pageViewMeta(referrer: string): Record<string, unknown> {\n const query = window.location.search.replace(/^\\?/, '');\n\n return { referrer: referrer.slice(0, LIMITS.referrer), query: query.slice(0, LIMITS.query) };\n}\n\n/** Stops whatever of init()'s parts already started, used both for teardown and a failed startup. */\nfunction stopStarted(\n parts: {\n queue: Queue;\n detachUnload: () => void;\n pageView?: PageViewSignal;\n stopHeartbeat?: () => void;\n stopMiRender?: () => void;\n },\n flushFirst: boolean,\n): void {\n if (flushFirst) guard(() => parts.queue.flushOnUnload());\n\n guard(() => parts.pageView?.stop());\n guard(() => parts.stopHeartbeat?.());\n guard(() => parts.stopMiRender?.());\n guard(() => parts.detachUnload());\n guard(() => parts.queue.stop());\n}\n\n/** Unwinds everything `init()` started; `flushFirst` also drains the buffer via the unload path. `runtime` is cleared first so a re-entrant call is a no-op. */\nfunction teardown(flushFirst: boolean): void {\n const current = runtime;\n\n if (!current) return;\n\n runtime = null;\n warnedOnce.clear();\n\n stopStarted(current, flushFirst);\n resetSession();\n}\n\n/** Idempotent. A second call while active is ignored rather than doubling listeners. */\nexport function init(options: Options = {}): void {\n guard(() => {\n if (runtime) return;\n\n const config = resolveConfig(options);\n\n if (!config) return;\n\n // A tripped kill-switch tears down ca-analytics entirely, not just the queue.\n const queue = createQueue(config.endpoint, () => shutdown());\n\n const emit: Emit = (event, extra = {}) => {\n queue.push(\n createRow({\n app: config.app,\n event,\n sessionId: getSessionId(),\n pagePath: extra.pagePath ?? currentPagePath(),\n elementId: extra.elementId,\n meta: extra.meta,\n }),\n );\n };\n\n const onUnload = () => guard(() => queue.flushOnUnload());\n // pagehide alone is unreliable on iOS; visibilitychange is what fires there.\n const onVisibility = () => {\n if (document.visibilityState === 'hidden') onUnload();\n };\n const detachUnload = () => {\n window.removeEventListener('pagehide', onUnload);\n document.removeEventListener('visibilitychange', onVisibility);\n };\n\n let pageView: PageViewSignal | undefined;\n let stopHeartbeat: (() => void) | undefined;\n let stopMiRender: (() => void) | undefined;\n\n // A later step throwing must unwind what already started, or its timers/listeners leak.\n try {\n window.addEventListener('pagehide', onUnload);\n document.addEventListener('visibilitychange', onVisibility);\n\n stopHeartbeat = startHeartbeat(() => emit('heartbeat')).stop;\n stopMiRender = startMiRender((payload) =>\n emit('component_render', { elementId: payload.elementId, meta: payload.meta }),\n ).stop;\n pageView = startPageView(\n (path, referrer) => emit('page_view', { pagePath: path, meta: pageViewMeta(referrer) }),\n currentPagePath,\n );\n } catch (error) {\n stopStarted({ queue, detachUnload, pageView, stopHeartbeat, stopMiRender }, false);\n throw error;\n }\n\n runtime = { queue, emit, detachUnload, pageView, stopHeartbeat, stopMiRender };\n });\n}\n\nexport function track(event: string, props?: Record<string, JsonValue>): void {\n guard(() => {\n if (!runtime) return;\n if (!event) return;\n\n const normalized = event.toLowerCase();\n\n if ((RESERVED_EVENTS as readonly string[]).includes(normalized)) {\n warnOnce('reserved', `\"${event}\" is reserved and was not tracked (further occurrences are silent)`);\n\n return;\n }\n\n if (!EVENT_NAME_RE.test(event)) {\n warnOnce('name', `\"${event}\" is not snake_case ≤100 chars; tracked as-is (further occurrences are silent)`);\n }\n\n runtime.emit(event, { meta: props });\n });\n}\n\n/** `element_id`/`segment_id` resolve identity via `/api/element_info`; everything else is free-form. */\nexport function trackElement(props: { element_id: number; segment_id?: number; [key: string]: JsonValue }): void {\n guard(() => {\n if (!runtime) return;\n\n const { element_id, segment_id, ...extra } = props ?? {};\n const id = toId(element_id);\n\n if (!id) {\n warnOnce('element', `trackElement() ignored a non-finite or zero element_id (${element_id})`);\n\n return;\n }\n\n const segmentRaw = Number(segment_id);\n const segmentFinite = Number.isFinite(segmentRaw);\n\n // Omitting segment_id is a supported call; only a supplied-but-bad value is worth a warning.\n if (segment_id !== undefined && !segmentFinite) {\n warnOnce('segment', `trackElement() got a non-finite segment_id (${segment_id}); recorded as 0`);\n }\n\n // Reserved keys last: a caller's own element_id/segment_id in extra must not win the spread.\n const meta: Record<string, unknown> = {\n ...extra,\n element_id: id,\n segment_id: segmentFinite ? Math.trunc(segmentRaw) : 0,\n };\n\n runtime.emit('element_click', { elementId: id, meta });\n });\n}\n\n/** For routers whose navigation the history patch cannot observe. */\nexport function trackPageView(path?: string): void {\n guard(() => runtime?.pageView.emitNow(path));\n}\n\n/** Flushes what is buffered before stopping — up to 15s of events would otherwise drop. */\nexport function shutdown(): void {\n guard(() => teardown(true));\n}\n\nexport function isActive(): boolean {\n return runtime !== null;\n}\n","import { track, trackElement, trackPageView } from '../core/analytics';\n\n/** Thin wrapper over the module singleton. No provider, no context — ca-analytics renders nothing. */\nconst api = { track, trackElement, trackPageView } as const;\n\nexport function useAnalytics(): typeof api {\n return api;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/core/constants.ts","../src/core/config.ts","../src/core/event.ts","../src/core/transport.ts","../src/core/queue.ts","../src/core/session.ts","../src/core/signals/heartbeat.ts","../src/core/signals/miRender.ts","../src/core/signals/pageview.ts","../src/core/analytics.ts","../src/react/useAnalytics.ts"],"sourcesContent":["/** Entity name bound on every tracked app's portal page. Not configurable. */\nexport const ENTITY_NAME = 'analytics_events';\n\n/** From package.json at build time. */\nexport const VERSION = __VERSION__;\n\n/** MI serves portal pages at /p/<name>, /pl/<name> or /pt/<name>. */\nexport const MI_PAGE_PATH_RE = /^\\/(p[tl]?)\\/([^/]+)(.*)$/;\n\n/** Per-field payload caps that keep a batch within sendBeacon's 64 KiB quota, not schema limits. */\nexport const WIDTHS = {\n app: 100,\n event: 100,\n page_path: 400,\n version: 20,\n meta: 2000,\n} as const;\n\n/** Emitted by ca-analytics. track() rejects them so app code cannot forge them. */\nexport const RESERVED_EVENTS = ['page_view', 'heartbeat', 'component_render', 'element_click'] as const;\n\n/** Custom event names must match this or track() warns once. Silent drift creates permanent duplicate series. */\nexport const EVENT_NAME_RE = /^[a-z][a-z0-9_]{0,99}$/;\n\n/** Per-key meta caps, applied before serializeMeta so one long value can't push another key out. */\nexport const LIMITS = { referrer: 300, query: 300 } as const;\n","import { ENTITY_NAME, MI_PAGE_PATH_RE, WIDTHS } from './constants';\nimport type { Options, ResolvedConfig } from './types';\n\n/** Unsubstituted PP variable, e.g. '[Custom App Analytics Enabled]'. Treated as unset. */\nconst UNSUBSTITUTED_RE = /^\\[[A-Za-z0-9][A-Za-z0-9 _-]*\\]$/;\n\nfunction portalVariables(): Record<string, unknown> {\n if (typeof window === 'undefined') return {};\n\n const block = (window as unknown as Record<string, unknown>).PP_VARIABLES;\n\n return block && typeof block === 'object' && !Array.isArray(block) ? (block as Record<string, unknown>) : {};\n}\n\n/** PP variables arrive as strings, so '0' and 'false' must not read truthy. */\nfunction readFlag(key: string): boolean | undefined {\n const raw = portalVariables()[key];\n\n if (typeof raw === 'boolean') return raw;\n if (typeof raw !== 'string') return undefined;\n\n const value = raw.trim().toLowerCase();\n\n if (value === '' || UNSUBSTITUTED_RE.test(raw.trim())) return undefined;\n\n return value !== '0' && value !== 'false' && value !== 'n' && value !== 'no';\n}\n\nfunction matchPage(): RegExpExecArray | null {\n if (typeof window === 'undefined' || !window.location) return null;\n\n return MI_PAGE_PATH_RE.exec(window.location.pathname);\n}\n\n/** A hash router's fragment (`#/reports`). A plain in-page anchor (`#section`) is not a route. */\nexport function isHashRoute(hash: string): boolean {\n return hash.startsWith('#/');\n}\n\n/** Path and query a hash router keeps in the fragment: `#/reports?tab=1` → `/reports`, `tab=1`. Null without one. */\nfunction hashRoute(): { path: string; query: string } | null {\n const hash = window.location.hash;\n\n if (!isHashRoute(hash)) return null;\n\n const route = hash.slice(1);\n const pathEnd = route.search(/[?#]/);\n const path = pathEnd === -1 ? route : route.slice(0, pathEnd);\n const queryMatch = /\\?([^#]*)/.exec(route);\n\n return { path, query: queryMatch ? queryMatch[1] : '' };\n}\n\n/** Route below the portal page. '/' at the page root. A hash route, when present, is the route. */\nexport function currentPagePath(): string {\n const match = matchPage();\n\n if (!match) return '/';\n\n const rest = hashRoute()?.path ?? match[3];\n\n return (rest && rest !== '' && rest !== '/' ? rest : '/').slice(0, WIDTHS.page_path);\n}\n\n/** `location.search` without the `?`, joined with `&` to a hash route's own query. */\nexport function currentQuery(): string {\n const outer = window.location.search.replace(/^\\?/, '');\n const inner = hashRoute()?.query ?? '';\n\n return outer && inner ? `${outer}&${inner}` : outer || inner;\n}\n\n/** Runtime config, or null when ca-analytics must stay silent: disabled, or not on a portal page. */\nexport function resolveConfig(options: Options = {}): ResolvedConfig | null {\n const enabled = options.enabled ?? readFlag('CUSTOM_APP_ANALYTICS_ENABLED') ?? true;\n\n if (!enabled) return null;\n\n // Page internal_name: both the row's attribution key and the write endpoint's page.\n const app = (matchPage()?.[2] ?? '').slice(0, WIDTHS.app);\n\n if (!app) return null;\n\n return { app, endpoint: `/data/page/${encodeURIComponent(app)}/${ENTITY_NAME}` };\n}\n","import { VERSION, WIDTHS } from './constants';\nimport type { EventRow } from './types';\n\nexport interface RowInput {\n app: string;\n event: string;\n sessionId: string;\n pagePath: string;\n elementId?: number;\n meta?: Record<string, unknown>;\n}\n\nconst pad2 = (n: number) => String(n).padStart(2, '0');\n\nconst INT32_MIN = -(2 ** 31);\nconst INT32_MAX = 2 ** 31 - 1;\n\n/**\n * Integer only: a float or string value widens the `int` column permanently. Out-of-int32 values are 0,\n * since past 2^31-1 the column overflows and past 1e21 the number serializes as `1e+21`.\n */\nexport function toId(value: unknown): number {\n const n = Math.trunc(Number(value));\n\n return Number.isSafeInteger(n) && n >= INT32_MIN && n <= INT32_MAX ? n : 0;\n}\n\n/** Space-separated, never ISO — a `T`/`Z` sniffs as text and the column stops being `datetime`. */\nexport function formatTs(date: Date): string {\n return (\n `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())} ` +\n `${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}:${pad2(date.getUTCSeconds())}`\n );\n}\n\nfunction stringify(value: unknown): string | null {\n try {\n const out = JSON.stringify(value);\n\n return typeof out === 'string' ? out : null;\n } catch {\n return null;\n }\n}\n\n/** JSON string capped at WIDTHS.meta. Drops whole keys instead of slicing — a sliced payload is invalid JSON. */\nexport function serializeMeta(meta?: Record<string, unknown>): string {\n if (!meta) return '{}';\n\n const full = stringify(meta);\n\n if (full !== null && full.length <= WIDTHS.meta) return full;\n\n const kept: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(meta)) {\n const candidate = stringify({ ...kept, [key]: value, _truncated: 1 });\n\n if (candidate === null || candidate.length > WIDTHS.meta) continue;\n\n kept[key] = value;\n }\n\n return stringify({ ...kept, _truncated: 1 }) ?? '{}';\n}\n\nexport function uuid(): string {\n const c = typeof crypto !== 'undefined' ? crypto : undefined;\n\n if (c && typeof c.randomUUID === 'function') return c.randomUUID();\n\n // Older Safari has getRandomValues but not randomUUID.\n const bytes = new Uint8Array(16);\n\n if (c && typeof c.getRandomValues === 'function') {\n c.getRandomValues(bytes);\n } else {\n for (let i = 0; i < bytes.length; i += 1) bytes[i] = Math.floor(Math.random() * 256);\n }\n\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/** Only place a row is built. `ts`/`element_id` type mistakes here lock the shared dataset's schema permanently. */\nexport function createRow(input: RowInput): EventRow {\n return {\n id: uuid(),\n ts: formatTs(new Date()),\n app: input.app.slice(0, WIDTHS.app),\n event: input.event.slice(0, WIDTHS.event),\n session_id: input.sessionId.slice(0, 36),\n page_path: input.pagePath.slice(0, WIDTHS.page_path),\n element_id: toId(input.elementId),\n version: VERSION.slice(0, WIDTHS.version),\n meta: serializeMeta(input.meta),\n };\n}\n","export type SendOutcome = 'ok' | 'failed';\n\nconst RETRY_DELAY_MS = 2000;\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\n/** Rows arrive pre-serialized; joining as `[a,b,c]` matches JSON.stringify(rows) byte-for-byte without a second pass. */\nconst toArrayBody = (rows: string[]): string => `[${rows.join(',')}]`;\n\nasync function post(endpoint: string, body: string): Promise<{ ok: boolean; retryable: boolean }> {\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body,\n });\n\n if (!response.ok) {\n return { ok: false, retryable: response.status === 429 || response.status >= 500 };\n }\n\n const payload = (await response.json().catch(() => null)) as { resultCode?: number } | null;\n\n // 200 with a non-zero resultCode is a rejected insert, not a success.\n if (payload && typeof payload.resultCode === 'number' && payload.resultCode !== 0) {\n return { ok: false, retryable: false };\n }\n\n return { ok: true, retryable: false };\n } catch {\n return { ok: false, retryable: true };\n }\n}\n\n/** One retry max, same rows — a lost-response double-insert is caught by readers using COUNT(DISTINCT id). */\nexport async function send(endpoint: string, rows: string[]): Promise<SendOutcome> {\n if (rows.length === 0) return 'ok';\n\n const body = toArrayBody(rows);\n const first = await post(endpoint, body);\n\n if (first.ok) return 'ok';\n if (!first.retryable) return 'failed';\n\n await sleep(RETRY_DELAY_MS);\n\n return (await post(endpoint, body)).ok ? 'ok' : 'failed';\n}\n\n/** Unload path. No retry possible — the page is going away. */\nexport function sendOnUnload(endpoint: string, rows: string[]): void {\n if (rows.length === 0) return;\n\n const body = toArrayBody(rows);\n\n try {\n const blob = new Blob([body], { type: 'application/json' });\n\n if (navigator.sendBeacon && navigator.sendBeacon(endpoint, blob)) return;\n } catch {\n // fall through to keepalive fetch\n }\n\n // try/catch only sees a synchronous throw; the .catch keeps a rejected request (e.g. over the\n // keepalive quota) from surfacing as an unhandled rejection in the host app.\n try {\n fetch(endpoint, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body,\n keepalive: true,\n }).catch(() => {\n // Dropped. Nothing further possible at unload.\n });\n } catch {\n // Dropped. Nothing further possible at unload.\n }\n}\n","import { send, sendOnUnload } from './transport';\nimport type { SendOutcome } from './transport';\nimport type { EventRow } from './types';\n\n/** A row's JSON already computed once in push(); send() joins these into the array body instead of re-stringifying. */\ntype Buffered = { json: string; size: number };\n\nconst FLUSH_INTERVAL_MS = 15_000;\nconst MAX_FLUSH_ROWS = 50;\n/** Half of sendBeacon's 64 KiB cumulative quota. The server has no practical ceiling. */\nconst MAX_FLUSH_BYTES = 30_000;\nconst MAX_CONSECUTIVE_FAILURES = 3;\n\n// Counts encoded UTF-8 bytes, not UTF-16 code units — what sendBeacon actually charges against its quota.\nconst textEncoder = new TextEncoder();\n\nexport interface Queue {\n push(row: EventRow): void;\n flush(): void;\n flushOnUnload(): void;\n stop(): void;\n}\n\n/** Splits rows into batches whose `[a,b,c]` body stays within maxBytes. A lone row over the limit still goes, alone. */\nfunction chunkBySize(rows: Buffered[], maxBytes: number): Buffered[][] {\n const chunks: Buffered[][] = [];\n let current: Buffered[] = [];\n let size = 2;\n\n for (const row of rows) {\n if (current.length > 0 && size + 1 + row.size > maxBytes) {\n chunks.push(current);\n current = [];\n size = 2;\n }\n\n size += (current.length > 0 ? 1 : 0) + row.size;\n current.push(row);\n }\n\n if (current.length > 0) chunks.push(current);\n\n return chunks;\n}\n\nexport function createQueue(endpoint: string, onKilled?: () => void): Queue {\n let buffer: Buffered[] = [];\n let bytes = 0;\n let failures = 0;\n let stopped = false;\n let inFlight = false;\n // Guards re-entrancy: onKilled calls shutdown() -> stop() again, but the kill must fire once.\n let killed = false;\n\n const timer = setInterval(() => {\n void flush();\n }, FLUSH_INTERVAL_MS);\n\n function stop(): void {\n stopped = true;\n buffer = [];\n bytes = 0;\n clearInterval(timer);\n }\n\n async function flush(): Promise<void> {\n if (stopped || inFlight || buffer.length === 0) return;\n\n const batch = buffer;\n\n buffer = [];\n bytes = 0;\n inFlight = true;\n\n let outcome: SendOutcome = 'failed';\n\n try {\n\n // send() isn't expected to reject, but a rejection must not become an unhandled promise rejection.\n try {\n outcome = await send(\n endpoint,\n batch.map((b) => b.json),\n );\n } catch {\n outcome = 'failed';\n }\n\n // Stopped while the request was out: a stale result must not count toward, or trip, the kill-switch.\n if (stopped) return;\n\n if (outcome === 'ok') {\n failures = 0;\n return;\n }\n\n failures += 1;\n\n // Batch dropped, not re-buffered: transport already retried once, and re-sending a batch whose\n // response was merely lost is what creates duplicates.\n if (failures >= MAX_CONSECUTIVE_FAILURES && !killed) {\n killed = true;\n stop();\n\n // Hand the kill up to the owner so it can stop the timers/listeners still producing rows.\n try {\n onKilled?.();\n } catch {\n // swallowed — a host callback must never escape\n }\n }\n } finally {\n inFlight = false;\n\n // push() only flushes on its own threshold check, which is a no-op while a request is out; rows\n // that piled up meanwhile would otherwise wait for the timer and could outgrow the beacon quota.\n // Only after a success: re-flushing a failure straight away would spend the kill-switch's three\n // strikes within seconds of a brief outage, instead of across timer ticks.\n if (!stopped && outcome === 'ok' && (buffer.length >= MAX_FLUSH_ROWS || bytes >= MAX_FLUSH_BYTES)) void flush();\n }\n }\n\n return {\n push(row: EventRow): void {\n if (stopped) return;\n\n let json: string;\n\n try {\n json = JSON.stringify(row);\n } catch {\n return;\n }\n\n const size = textEncoder.encode(json).length;\n\n buffer.push({ json, size });\n bytes += size;\n\n if (buffer.length >= MAX_FLUSH_ROWS || bytes >= MAX_FLUSH_BYTES) void flush();\n },\n flush(): void {\n void flush();\n },\n flushOnUnload(): void {\n if (stopped || buffer.length === 0) return;\n\n const batch = buffer;\n\n buffer = [];\n bytes = 0;\n\n // One body over the 64 KiB quota fails in both sendBeacon and keepalive fetch and drops every row;\n // flush-sized chunks let at least the ones that fit through.\n for (const chunk of chunkBySize(batch, MAX_FLUSH_BYTES)) {\n sendOnUnload(\n endpoint,\n chunk.map((b) => b.json),\n );\n }\n },\n stop,\n };\n}\n","const SID_KEY = 'ca_pa_sid';\nconst LAST_KEY = 'ca_pa_last';\nconst IDLE_MS = 30 * 60 * 1000;\nconst TOUCH_THROTTLE_MS = 5000;\n\nlet memoryId: string | null = null;\nlet lastWrittenAt = 0;\n\n/** Safari private mode and blocked-storage policies throw on access, not just on write. */\nfunction read(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction write(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // In-memory session for the page lifetime beats no tracking.\n }\n}\n\nfunction newId(): string {\n try {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID();\n } catch {}\n\n return `s-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/** Clears in-memory state only. Storage stays, so a reload still finds the session. */\nexport function resetSession(): void {\n memoryId = null;\n lastWrittenAt = 0;\n}\n\n/** Session id, minting a new one after 30 min idle. Extends the window on every call. */\nexport function getSessionId(): string {\n const now = Date.now();\n const storedId = read(SID_KEY);\n const rawLast = read(LAST_KEY);\n const last = rawLast === null ? Number.NaN : Number(rawLast);\n const alive = Number.isFinite(last) && now - last <= IDLE_MS;\n\n let id: string;\n\n if (storedId && alive) {\n // Trusting storage here is what survives a reload.\n id = storedId;\n } else if (!storedId && memoryId) {\n // Blocked localStorage always reads null; reusing the memory id is the only stable fallback.\n id = memoryId;\n } else {\n id = newId();\n write(SID_KEY, id);\n lastWrittenAt = 0;\n }\n\n memoryId = id;\n\n if (now - lastWrittenAt >= TOUCH_THROTTLE_MS) {\n write(LAST_KEY, String(now));\n lastWrittenAt = now;\n }\n\n return id;\n}\n","export const HEARTBEAT_FAST_MS = 60_000;\nexport const HEARTBEAT_SLOW_MS = 300_000;\nexport const HEARTBEAT_FAST_COUNT = 5;\n\n/** Tapered, visible-only — readers derive engaged time as beats x interval, so a hidden beat would count idle time. */\nexport function startHeartbeat(emit: () => void): { stop(): void } {\n let beats = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let stopped = false;\n\n const interval = () => (beats < HEARTBEAT_FAST_COUNT ? HEARTBEAT_FAST_MS : HEARTBEAT_SLOW_MS);\n\n function schedule(): void {\n if (stopped || timer !== undefined) return;\n\n timer = setTimeout(() => {\n timer = undefined;\n\n if (stopped || document.visibilityState !== 'visible') return;\n\n beats += 1;\n\n try {\n emit();\n } catch {\n // A failed emit must not stop the timer.\n }\n\n schedule();\n }, interval());\n }\n\n function clear(): void {\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n }\n\n const onVisibilityChange = () => {\n if (stopped) return;\n\n if (document.visibilityState === 'visible') {\n schedule();\n } else {\n // Discard the partial interval rather than credit time the user was away.\n clear();\n }\n };\n\n document.addEventListener('visibilitychange', onVisibilityChange);\n\n if (document.visibilityState === 'visible') schedule();\n\n return {\n stop(): void {\n stopped = true;\n clear();\n document.removeEventListener('visibilitychange', onVisibilityChange);\n },\n };\n}\n","import { toId } from '../event';\n\nexport const RENDER_CAP = 50;\n/** Distinct cap buckets per page load. Past this every render of a component shares one bucket, so per-render props can't leak the map. */\nconst MAX_KEYS = 200;\n/** `visibility.preview.control_buttons.lineage` is 4 levels, the deepest shape MI documents. */\nconst PROP_DEPTH = 4;\n/** A `viewer` render is 38 leaves / 1672 chars, so this must not bind before serializeMeta's own cap does. */\nconst PROP_KEYS = 40;\nconst PROP_STRING = 40;\n\n/** Already read into element_id/segment_id above; re-emitting them as props is noise. */\nconst IDENTITY_KEYS = new Set(['element', 'element_id', 'segment', 'segment_id', 'segment_value_id']);\n\nconst SKIP = Symbol('skip');\n\nexport interface RenderPayload {\n elementId: number;\n meta: Record<string, unknown>;\n}\n\nfunction leafValue(value: unknown): unknown {\n if (value === null) return null;\n\n switch (typeof value) {\n case 'boolean':\n return value;\n case 'number':\n return Number.isFinite(value) ? value : SKIP;\n case 'string':\n return value.slice(0, PROP_STRING);\n default:\n return SKIP;\n }\n}\n\n/** Dotted paths, flat output: the dashboard stringifies a nested object into one opaque value, so its per-key distribution would be noise. Returns true when a cap dropped something. */\nfunction flattenProps(source: Record<string, unknown>, out: Record<string, unknown>, prefix: string, depth: number): boolean {\n let dropped = false;\n\n for (const [key, value] of Object.entries(source)) {\n if (Object.keys(out).length >= PROP_KEYS) return true;\n if (depth === 0 && IDENTITY_KEYS.has(key)) continue;\n\n const path = prefix === '' ? key : `${prefix}.${key}`;\n\n // Summarized, not expanded — index-keyed paths would make every array length its own set of keys.\n if (Array.isArray(value)) {\n out[`prop.${path}`] = `arr:${value.length}`;\n continue;\n }\n\n if (value !== null && typeof value === 'object') {\n if (depth + 1 < PROP_DEPTH) dropped = flattenProps(value as Record<string, unknown>, out, path, depth + 1) || dropped;\n else dropped = true;\n\n continue;\n }\n\n const leaf = leafValue(value);\n\n if (leaf !== SKIP) out[`prop.${path}`] = leaf;\n }\n\n return dropped;\n}\n\n/** FNV-1a. Keeps the bucket key short no matter how large the props are. */\nfunction hash(text: string): string {\n let h = 0x811c9dc5;\n\n for (let i = 0; i < text.length; i += 1) {\n h ^= text.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n\n return (h >>> 0).toString(36);\n}\n\n/** Listens on `document` only: `MiNamespace.render()` dispatches a bubbling CustomEvent, so load order doesn't matter. */\nexport function startMiRender(emit: (payload: RenderPayload) => void): { stop(): void } {\n let stopped = false;\n const counts = new Map<string, number>();\n\n const onRender = (event: Event) => {\n if (stopped) return;\n\n try {\n const detail = (event as CustomEvent).detail as { component?: unknown; props?: unknown } | undefined;\n\n if (!detail || typeof detail !== 'object') return;\n\n const component = typeof detail.component === 'string' ? detail.component : '';\n\n if (!component) return;\n\n const props = (detail.props && typeof detail.props === 'object' ? detail.props : {}) as Record<string, unknown>;\n // Three spellings in the wild: preview sends {element, segment}, viewer {element_id, segment_id}, access_denied_popup {element_id, segment_value_id}.\n const elementId = toId(props.element ?? props.element_id ?? 0);\n const segmentId = toId(props.segment ?? props.segment_id ?? props.segment_value_id ?? 0);\n\n const flat: Record<string, unknown> = {};\n const propsDropped = flattenProps(props, flat, '', 0);\n\n // Props are in the key: a component with no element (a popup) is otherwise one bucket for every variant it renders.\n const bucket = `${component}|${elementId}|${segmentId}|${hash(JSON.stringify(flat))}`;\n const key = counts.has(bucket) || counts.size < MAX_KEYS ? bucket : `${component}|*`;\n const seq = (counts.get(key) ?? 0) + 1;\n\n // Some apps re-render on every filter change; uncapped, one page burns the throttle budget.\n if (seq > RENDER_CAP) return;\n\n counts.set(key, seq);\n\n // Reserved keys first: serializeMeta drops trailing keys at the 2000-char cap, so props go before identity does.\n const meta: Record<string, unknown> = {\n component,\n element_id: elementId,\n segment_id: segmentId,\n ...flat,\n };\n\n if (propsDropped) meta.props_dropped = 1;\n if (seq === RENDER_CAP) meta.capped = 1;\n\n emit({ elementId, meta });\n } catch {\n // Never throw into the host render path.\n }\n };\n\n document.addEventListener('mi-render', onRender);\n\n return {\n stop(): void {\n stopped = true;\n document.removeEventListener('mi-render', onRender);\n counts.clear();\n },\n };\n}\n","import { isHashRoute } from '../config';\n\nexport interface PageViewSignal {\n stop(): void;\n emitNow(path?: string): void;\n}\n\n/** Absolute for an inbound hop, bare route after — a route in another app is not comparable here. */\nconst inboundReferrer = (): string => {\n try {\n const raw = document.referrer;\n\n if (!raw) return '';\n\n const url = new URL(raw);\n\n return `${url.origin}${url.pathname}`;\n } catch {\n return '';\n }\n};\n\n/** Emits once on start, then on every history or hash navigation. The original history method always still runs. */\nexport function startPageView(\n emit: (path: string, referrer: string) => void,\n currentPath: () => string,\n): PageViewSignal {\n let stopped = false;\n let lastPath: string | null = null;\n let lastLocation: string | null = null;\n\n // Dedupe key: path alone can't tell a `?tab=` change from a no-op replaceState. A hash route\n // (`#/reports?tab=1`) is part of it; a plain in-page anchor (`#section`) is not a new view.\n const locationKey = (path: string): string => {\n try {\n const { search, hash } = window.location;\n\n return `${path}${search}${isHashRoute(hash) ? hash : ''}`;\n } catch {\n return path;\n }\n };\n\n // resolvePath is a thunk so a throwing host currentPath() is caught here too, not in the caller.\n // dedupe is off for emitNow(): an explicit trackPageView() must always send.\n const safeEmit = (resolvePath: () => string, dedupe: boolean) => {\n if (stopped) return;\n\n try {\n const path = resolvePath();\n const location = locationKey(path);\n\n // Routers replaceState for scroll and filter sync without moving. Not a view.\n if (dedupe && location === lastLocation) return;\n\n // GA4 semantics: referrer is the previous page_view's path, not document.referrer, except first.\n const referrer = lastPath === null ? inboundReferrer() : lastPath;\n\n lastPath = path;\n lastLocation = location;\n emit(path, referrer);\n } catch {\n // swallowed\n }\n };\n\n const originalPush = window.history.pushState;\n const originalReplace = window.history.replaceState;\n\n const patch = (original: typeof window.history.pushState) =>\n function patched(this: History, ...args: Parameters<typeof window.history.pushState>) {\n const result = original.apply(this, args);\n\n safeEmit(currentPath, true);\n\n return result;\n };\n\n const patchedPush = patch(originalPush);\n const patchedReplace = patch(originalReplace);\n\n window.history.pushState = patchedPush;\n window.history.replaceState = patchedReplace;\n\n const onPopState = () => safeEmit(currentPath, true);\n\n window.addEventListener('popstate', onPopState);\n // Hash routers and `location.hash = …` navigate without pushState; dedupe drops the popstate twin.\n window.addEventListener('hashchange', onPopState);\n\n safeEmit(currentPath, true);\n\n return {\n stop(): void {\n stopped = true;\n lastPath = null;\n lastLocation = null;\n\n // Restore only if this signal still owns the patch — another startPageView may have patched over it.\n if (window.history.pushState === patchedPush) {\n window.history.pushState = originalPush;\n }\n\n if (window.history.replaceState === patchedReplace) {\n window.history.replaceState = originalReplace;\n }\n\n window.removeEventListener('popstate', onPopState);\n window.removeEventListener('hashchange', onPopState);\n },\n emitNow(path?: string): void {\n safeEmit(() => path ?? currentPath(), false);\n },\n };\n}\n","import { EVENT_NAME_RE, LIMITS, RESERVED_EVENTS } from './constants';\nimport { currentPagePath, currentQuery, resolveConfig } from './config';\nimport { createRow, toId } from './event';\nimport { createQueue, type Queue } from './queue';\nimport { getSessionId, resetSession } from './session';\nimport { startHeartbeat } from './signals/heartbeat';\nimport { startMiRender } from './signals/miRender';\nimport { startPageView, type PageViewSignal } from './signals/pageview';\nimport type { Options } from './types';\n\ntype Emit = (\n event: string,\n extra?: { pagePath?: string; elementId?: number; meta?: Record<string, unknown> },\n) => void;\n\ninterface Runtime {\n queue: Queue;\n emit: Emit;\n detachUnload: () => void;\n pageView: PageViewSignal;\n stopHeartbeat: () => void;\n stopMiRender: () => void;\n}\n\nlet runtime: Runtime | null = null;\nconst warnedOnce = new Set<string>();\n\n/** Nothing in ca-analytics may throw into the host app. */\nfunction guard(fn: () => void): void {\n try {\n fn();\n } catch {\n // swallowed\n }\n}\n\n/** Warns about caller misuse only the first time `key` is seen. */\nfunction warnOnce(key: string, message: string): void {\n if (warnedOnce.has(key)) return;\n\n warnedOnce.add(key);\n console.warn(`[ca-analytics] ${message}`);\n}\n\nfunction pageViewMeta(referrer: string): Record<string, unknown> {\n const query = currentQuery();\n\n return { referrer: referrer.slice(0, LIMITS.referrer), query: query.slice(0, LIMITS.query) };\n}\n\n/** Stops whatever of init()'s parts already started, used both for teardown and a failed startup. */\nfunction stopStarted(\n parts: {\n queue: Queue;\n detachUnload: () => void;\n pageView?: PageViewSignal;\n stopHeartbeat?: () => void;\n stopMiRender?: () => void;\n },\n flushFirst: boolean,\n): void {\n if (flushFirst) guard(() => parts.queue.flushOnUnload());\n\n guard(() => parts.pageView?.stop());\n guard(() => parts.stopHeartbeat?.());\n guard(() => parts.stopMiRender?.());\n guard(() => parts.detachUnload());\n guard(() => parts.queue.stop());\n}\n\n/** Unwinds everything `init()` started; `flushFirst` also drains the buffer via the unload path. `runtime` is cleared first so a re-entrant call is a no-op. */\nfunction teardown(flushFirst: boolean): void {\n const current = runtime;\n\n if (!current) return;\n\n runtime = null;\n warnedOnce.clear();\n\n stopStarted(current, flushFirst);\n resetSession();\n}\n\n/** Idempotent. A second call while active is ignored rather than doubling listeners. */\nexport function init(options: Options = {}): void {\n guard(() => {\n if (runtime) return;\n\n const config = resolveConfig(options);\n\n if (!config) return;\n\n // A tripped kill-switch tears down ca-analytics entirely, not just the queue — but only while this\n // queue's runtime is still the live one, so a stale queue can't kill a later init().\n const queue: Queue = createQueue(config.endpoint, () => {\n if (runtime?.queue === queue) shutdown();\n });\n\n const emit: Emit = (event, extra = {}) => {\n queue.push(\n createRow({\n app: config.app,\n event,\n sessionId: getSessionId(),\n pagePath: extra.pagePath ?? currentPagePath(),\n elementId: extra.elementId,\n meta: extra.meta,\n }),\n );\n };\n\n const onUnload = () => guard(() => queue.flushOnUnload());\n // pagehide alone is unreliable on iOS; visibilitychange is what fires there.\n const onVisibility = () => {\n if (document.visibilityState === 'hidden') onUnload();\n };\n const detachUnload = () => {\n window.removeEventListener('pagehide', onUnload);\n document.removeEventListener('visibilitychange', onVisibility);\n };\n\n let pageView: PageViewSignal | undefined;\n let stopHeartbeat: (() => void) | undefined;\n let stopMiRender: (() => void) | undefined;\n\n // A later step throwing must unwind what already started, or its timers/listeners leak.\n try {\n window.addEventListener('pagehide', onUnload);\n document.addEventListener('visibilitychange', onVisibility);\n\n stopHeartbeat = startHeartbeat(() => emit('heartbeat')).stop;\n stopMiRender = startMiRender((payload) =>\n emit('component_render', { elementId: payload.elementId, meta: payload.meta }),\n ).stop;\n pageView = startPageView(\n (path, referrer) => emit('page_view', { pagePath: path, meta: pageViewMeta(referrer) }),\n currentPagePath,\n );\n } catch (error) {\n stopStarted({ queue, detachUnload, pageView, stopHeartbeat, stopMiRender }, false);\n throw error;\n }\n\n runtime = { queue, emit, detachUnload, pageView, stopHeartbeat, stopMiRender };\n });\n}\n\n/** `props` is any object, interface-typed included; it is serialized like JSON.stringify (a Date becomes its ISO string). */\nexport function track(event: string, props?: object): void {\n guard(() => {\n if (!runtime) return;\n if (!event) return;\n\n const normalized = event.toLowerCase();\n\n if ((RESERVED_EVENTS as readonly string[]).includes(normalized)) {\n warnOnce('reserved', `\"${event}\" is reserved and was not tracked (further occurrences are silent)`);\n\n return;\n }\n\n if (!EVENT_NAME_RE.test(event)) {\n warnOnce('name', `\"${event}\" is not snake_case ≤100 chars; tracked as-is (further occurrences are silent)`);\n }\n\n runtime.emit(event, { meta: props as Record<string, unknown> | undefined });\n });\n}\n\n/**\n * `element_id`/`segment_id` resolve identity via `/api/element_info`; everything else is free-form. Generic so\n * interface-typed props with extra keys are accepted — an index signature would reject them.\n */\nexport function trackElement<T extends { element_id: number; segment_id?: number }>(props: T): void {\n guard(() => {\n if (!runtime) return;\n\n const { element_id, segment_id, ...extra } = props ?? {};\n const id = toId(element_id);\n\n if (!id) {\n warnOnce('element', `trackElement() ignored a zero, non-finite or out-of-range element_id (${element_id})`);\n\n return;\n }\n\n const segmentRaw = Number(segment_id);\n const segmentFinite = Number.isFinite(segmentRaw);\n\n // Omitting segment_id is a supported call; only a supplied-but-bad value is worth a warning.\n if (segment_id !== undefined && !segmentFinite) {\n warnOnce('segment', `trackElement() got a non-finite segment_id (${segment_id}); recorded as 0`);\n }\n\n // Reserved keys last: a caller's own element_id/segment_id in extra must not win the spread.\n const meta: Record<string, unknown> = {\n ...extra,\n element_id: id,\n segment_id: segmentFinite ? Math.trunc(segmentRaw) : 0,\n };\n\n runtime.emit('element_click', { elementId: id, meta });\n });\n}\n\n/** For routers whose navigation the history patch cannot observe. */\nexport function trackPageView(path?: string): void {\n guard(() => runtime?.pageView.emitNow(path));\n}\n\n/** Flushes what is buffered before stopping — up to 15s of events would otherwise drop. */\nexport function shutdown(): void {\n guard(() => teardown(true));\n}\n\nexport function isActive(): boolean {\n return runtime !== null;\n}\n","import { track, trackElement, trackPageView } from '../core/analytics';\n\n/** Thin wrapper over the module singleton. No provider, no context — ca-analytics renders nothing. */\nconst api = { track, trackElement, trackPageView } as const;\n\nexport function useAnalytics(): typeof api {\n return api;\n}\n"],"mappings":"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","names":["hash","hash"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metricinsights/ca-analytics",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Usage analytics for Metric Insights Custom Apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -28,6 +28,7 @@
28
28
  "build": "tsup",
29
29
  "dev": "tsup --watch",
30
30
  "typecheck": "tsc --noEmit",
31
+ "test": "vitest run",
31
32
  "prepublishOnly": "npm run typecheck && npm run build"
32
33
  },
33
34
  "peerDependencies": {
@@ -43,9 +44,11 @@
43
44
  },
44
45
  "devDependencies": {
45
46
  "@types/react": "^18.3.12",
47
+ "jsdom": "^30.1.1",
46
48
  "react": "^18.3.1",
47
49
  "tsup": "^8.3.5",
48
- "typescript": "^5.7.2"
50
+ "typescript": "^5.7.2",
51
+ "vitest": "^5.0.2"
49
52
  },
50
53
  "keywords": [
51
54
  "metricinsights",
@@ -62,5 +65,8 @@
62
65
  "url": "https://github.com/mi-examples/ca-analytics/issues"
63
66
  },
64
67
  "homepage": "https://github.com/mi-examples/ca-analytics#readme",
65
- "license": "MIT"
68
+ "license": "MIT",
69
+ "engines": {
70
+ "node": ">=22"
71
+ }
66
72
  }