@metricinsights/ca-analytics 0.1.0 → 0.2.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/README.md +22 -14
- package/dist/index.cjs +57 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -2
- package/dist/index.d.ts +0 -2
- package/dist/index.js +57 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ npm i @metricinsights/ca-analytics
|
|
|
37
37
|
import { init, useAnalytics } from '@metricinsights/ca-analytics';
|
|
38
38
|
|
|
39
39
|
// app entry, once
|
|
40
|
-
init(
|
|
40
|
+
init();
|
|
41
41
|
|
|
42
42
|
// anywhere in the app
|
|
43
43
|
const { track } = useAnalytics();
|
|
@@ -82,14 +82,8 @@ Every option is optional. Each falls back to a portal-page variable, then to a d
|
|
|
82
82
|
|
|
83
83
|
| Option | Variable | Default | Effect |
|
|
84
84
|
| --- | --- | --- | --- |
|
|
85
|
-
| `app` | — | derived from the URL | Portal page internal name. |
|
|
86
85
|
| `enabled` | `CUSTOM_APP_ANALYTICS_ENABLED` | `true` | `false` disables all tracking. |
|
|
87
86
|
|
|
88
|
-
`app` is derived from `location.pathname` against `/^\/(p[tl]?)\/([^/]+)(.*)$/`. **If it cannot
|
|
89
|
-
be derived and isn't passed, `init()` stays silent** rather than writing rows with a blank `app`
|
|
90
|
-
that nothing could attribute. This is why a bare `init()` does nothing on a local dev server,
|
|
91
|
-
where the path is `/`.
|
|
92
|
-
|
|
93
87
|
Portal-page variables are read from `window.PP_VARIABLES` and always arrive as **strings**.
|
|
94
88
|
`'0'`, `'false'`, `'n'`, `'no'` (case-insensitive, trimmed) mean disabled; any other non-empty
|
|
95
89
|
string means enabled. Unset, empty, or left unsubstituted by MI (a literal `[Custom App
|
|
@@ -101,7 +95,7 @@ Analytics Enabled]`) counts as *not set* and falls through to the option or defa
|
|
|
101
95
|
| --- | --- | --- |
|
|
102
96
|
| `page_view` | Once on `init()`, then on every `pushState` / `replaceState` / `popstate`, or on demand via `trackPageView()`. | `referrer`, `query` — always both, always strings |
|
|
103
97
|
| `heartbeat` | Tab visible: every 60s for 5 beats, then every 300s. | `{}` |
|
|
104
|
-
| `component_render` | The `mi-render` `CustomEvent` MI dispatches on `document`. | `component`, `element_id`, `segment_id` |
|
|
98
|
+
| `component_render` | The `mi-render` `CustomEvent` MI dispatches on `document`. | `component`, `element_id`, `segment_id`, plus each prop as `prop.<path>` |
|
|
105
99
|
| `element_click` | `trackElement()`. | `element_id`, `segment_id`, plus your own keys |
|
|
106
100
|
| *(custom)* | `track(name, props)`. | your `props` verbatim |
|
|
107
101
|
|
|
@@ -123,11 +117,22 @@ sync does not emit. `meta.referrer` follows GA4 semantics: the first view of the
|
|
|
123
117
|
previous view. `meta.query` is `location.search` without the leading `?`. Both are capped at 300
|
|
124
118
|
characters; either can be `''`.
|
|
125
119
|
|
|
126
|
-
**`component_render`** reads `detail.component` plus `detail.props
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
120
|
+
**`component_render`** reads `detail.component` plus `detail.props`. `MiNamespace.render()` is a
|
|
121
|
+
pure event emitter, so `document` sees every shared component render — no patching.
|
|
122
|
+
|
|
123
|
+
Identity comes only from `element`/`element_id` and `segment`/`segment_id`/`segment_value_id` —
|
|
124
|
+
the spellings MI's own components use. No other key feeds `element_id`, which joins to
|
|
125
|
+
`/api/element_info`, where a foreign numeric id would join wrong.
|
|
126
|
+
|
|
127
|
+
Every other prop flattens to `prop.<dotted.path>` — flat, because the dashboard summarizes per
|
|
128
|
+
key and a nested object collapses to one opaque string. Scalars verbatim, strings capped at 40,
|
|
129
|
+
arrays as `arr:<length>`, 4 levels deep, 20 keys.
|
|
130
|
+
|
|
131
|
+
Props are stored verbatim and readable by anyone with dashboard access. Keep record-specific text
|
|
132
|
+
out of props you render with.
|
|
133
|
+
|
|
134
|
+
Capped at 50 emits per distinct component + element + segment + props; the capping emit carries
|
|
135
|
+
`capped: 1`. Past 200 buckets a page load, further renders share one.
|
|
131
136
|
|
|
132
137
|
**`trackElement`** needs `element_id` to be finite and non-zero — anything else drops the event
|
|
133
138
|
(one `console.warn` per runtime). `segment_id` is optional and defaults to `0` when omitted, with
|
|
@@ -148,7 +153,7 @@ stamps itself — the client never sends it.
|
|
|
148
153
|
| --- | --- | --- |
|
|
149
154
|
| `id` | text | Client UUID. Reused across a retry so readers dedupe with `COUNT(DISTINCT id)`. |
|
|
150
155
|
| `ts` | datetime | `YYYY-MM-DD HH:MM:SS`, UTC. **Never ISO** — see [One-way doors](#one-way-doors). |
|
|
151
|
-
| `app` | text | Portal page internal name,
|
|
156
|
+
| `app` | text | Portal page internal name, read from `location.pathname` against `/^\/(p[tl]?)\/([^/]+)(.*)$/`. Capped at 100. |
|
|
152
157
|
| `event` | text | Capped at 100. |
|
|
153
158
|
| `session_id` | text | New session after 30 minutes idle. Survives reloads via `localStorage`. |
|
|
154
159
|
| `page_path` | text | Route below the portal page, `/` at the root. Capped at 400. |
|
|
@@ -156,6 +161,9 @@ stamps itself — the client never sends it.
|
|
|
156
161
|
| `version` | text | This package's version, capped at 20. |
|
|
157
162
|
| `meta` | text | JSON string, capped at 2000. |
|
|
158
163
|
|
|
164
|
+
`pp-dev` serves that same path locally. **Off that path shape, `init()` stays silent** rather
|
|
165
|
+
than writing rows with a blank `app` that nothing could attribute.
|
|
166
|
+
|
|
159
167
|
Text columns are wide (10500) and the caps above are client-side payload budget, keeping a batch
|
|
160
168
|
inside `sendBeacon`'s 64 KiB quota — they are not schema limits.
|
|
161
169
|
|
package/dist/index.cjs
CHANGED
|
@@ -34,7 +34,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
34
34
|
|
|
35
35
|
// src/core/constants.ts
|
|
36
36
|
var ENTITY_NAME = "analytics_events";
|
|
37
|
-
var VERSION = "0.
|
|
37
|
+
var VERSION = "0.2.0";
|
|
38
38
|
var MI_PAGE_PATH_RE = /^\/(p[tl]?)\/([^/]+)(.*)$/;
|
|
39
39
|
var WIDTHS = {
|
|
40
40
|
app: 100,
|
|
@@ -75,7 +75,7 @@ function currentPagePath() {
|
|
|
75
75
|
function resolveConfig(options = {}) {
|
|
76
76
|
const enabled = options.enabled ?? readFlag("CUSTOM_APP_ANALYTICS_ENABLED") ?? true;
|
|
77
77
|
if (!enabled) return null;
|
|
78
|
-
const app = (
|
|
78
|
+
const app = (matchPage()?.[2] ?? "").slice(0, WIDTHS.app);
|
|
79
79
|
if (!app) return null;
|
|
80
80
|
return { app, endpoint: `/data/page/${encodeURIComponent(app)}/${ENTITY_NAME}` };
|
|
81
81
|
}
|
|
@@ -380,6 +380,53 @@ function startHeartbeat(emit) {
|
|
|
380
380
|
|
|
381
381
|
// src/core/signals/miRender.ts
|
|
382
382
|
var RENDER_CAP = 50;
|
|
383
|
+
var MAX_KEYS = 200;
|
|
384
|
+
var PROP_DEPTH = 4;
|
|
385
|
+
var PROP_KEYS = 40;
|
|
386
|
+
var PROP_STRING = 40;
|
|
387
|
+
var IDENTITY_KEYS = /* @__PURE__ */ new Set(["element", "element_id", "segment", "segment_id", "segment_value_id"]);
|
|
388
|
+
var SKIP = /* @__PURE__ */ Symbol("skip");
|
|
389
|
+
function leafValue(value) {
|
|
390
|
+
if (value === null) return null;
|
|
391
|
+
switch (typeof value) {
|
|
392
|
+
case "boolean":
|
|
393
|
+
return value;
|
|
394
|
+
case "number":
|
|
395
|
+
return Number.isFinite(value) ? value : SKIP;
|
|
396
|
+
case "string":
|
|
397
|
+
return value.slice(0, PROP_STRING);
|
|
398
|
+
default:
|
|
399
|
+
return SKIP;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function flattenProps(source, out, prefix, depth) {
|
|
403
|
+
let dropped = false;
|
|
404
|
+
for (const [key, value] of Object.entries(source)) {
|
|
405
|
+
if (Object.keys(out).length >= PROP_KEYS) return true;
|
|
406
|
+
if (depth === 0 && IDENTITY_KEYS.has(key)) continue;
|
|
407
|
+
const path = prefix === "" ? key : `${prefix}.${key}`;
|
|
408
|
+
if (Array.isArray(value)) {
|
|
409
|
+
out[`prop.${path}`] = `arr:${value.length}`;
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (value !== null && typeof value === "object") {
|
|
413
|
+
if (depth + 1 < PROP_DEPTH) dropped = flattenProps(value, out, path, depth + 1) || dropped;
|
|
414
|
+
else dropped = true;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const leaf = leafValue(value);
|
|
418
|
+
if (leaf !== SKIP) out[`prop.${path}`] = leaf;
|
|
419
|
+
}
|
|
420
|
+
return dropped;
|
|
421
|
+
}
|
|
422
|
+
function hash(text) {
|
|
423
|
+
let h = 2166136261;
|
|
424
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
425
|
+
h ^= text.charCodeAt(i);
|
|
426
|
+
h = Math.imul(h, 16777619);
|
|
427
|
+
}
|
|
428
|
+
return (h >>> 0).toString(36);
|
|
429
|
+
}
|
|
383
430
|
function startMiRender(emit) {
|
|
384
431
|
let stopped = false;
|
|
385
432
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -392,16 +439,21 @@ function startMiRender(emit) {
|
|
|
392
439
|
if (!component) return;
|
|
393
440
|
const props = detail.props && typeof detail.props === "object" ? detail.props : {};
|
|
394
441
|
const elementId = toId(props.element ?? props.element_id ?? 0);
|
|
395
|
-
const segmentId = toId(props.segment ?? props.segment_value_id ?? 0);
|
|
396
|
-
const
|
|
442
|
+
const segmentId = toId(props.segment ?? props.segment_id ?? props.segment_value_id ?? 0);
|
|
443
|
+
const flat = {};
|
|
444
|
+
const propsDropped = flattenProps(props, flat, "", 0);
|
|
445
|
+
const bucket = `${component}|${elementId}|${segmentId}|${hash(JSON.stringify(flat))}`;
|
|
446
|
+
const key = counts.has(bucket) || counts.size < MAX_KEYS ? bucket : `${component}|*`;
|
|
397
447
|
const seq = (counts.get(key) ?? 0) + 1;
|
|
398
448
|
if (seq > RENDER_CAP) return;
|
|
399
449
|
counts.set(key, seq);
|
|
400
450
|
const meta = {
|
|
401
451
|
component,
|
|
402
452
|
element_id: elementId,
|
|
403
|
-
segment_id: segmentId
|
|
453
|
+
segment_id: segmentId,
|
|
454
|
+
...flat
|
|
404
455
|
};
|
|
456
|
+
if (propsDropped) meta.props_dropped = 1;
|
|
405
457
|
if (seq === RENDER_CAP) meta.capped = 1;
|
|
406
458
|
emit({ elementId, meta });
|
|
407
459
|
} catch {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../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":["export { init, isActive, shutdown, track, trackElement, trackPageView } from './core/analytics';\nexport { ENTITY_NAME, VERSION } from './core/constants';\nexport type { EventRow, JsonValue, Options } from './core/types';\nexport { useAnalytics } from './react/useAnalytics';\n","/** 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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCO,IAAM,cAAc;AAGpB,IAAM,UAAU;AAGhB,IAAM,kBAAkB;AAGxB,IAAM,SAAS;AAAA,EACpB,KAAK;AAAA,EACL,OAAO;AAAA,EACP,WAAW;AAAA,EACX,SAAS;AAAA,EACT,MAAM;AACR;AAGO,IAAM,kBAAkB,CAAC,aAAa,aAAa,oBAAoB,eAAe;AAGtF,IAAM,gBAAgB;AAGtB,IAAM,SAAS,EAAE,UAAU,KAAK,OAAO,IAAI;;;ACrBlD,IAAM,mBAAmB;AAEzB,SAAS,kBAA2C;AAClD,MAAI,OAAO,WAAW,YAAa,QAAO,CAAC;AAE3C,QAAM,QAAS,OAA8C;AAE7D,SAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAAK,QAAoC,CAAC;AAC7G;AAGA,SAAS,SAAS,KAAkC;AAClD,QAAM,MAAM,gBAAgB,EAAE,GAAG;AAEjC,MAAI,OAAO,QAAQ,UAAW,QAAO;AACrC,MAAI,OAAO,QAAQ,SAAU,QAAO;AAEpC,QAAM,QAAQ,IAAI,KAAK,EAAE,YAAY;AAErC,MAAI,UAAU,MAAM,iBAAiB,KAAK,IAAI,KAAK,CAAC,EAAG,QAAO;AAE9D,SAAO,UAAU,OAAO,UAAU,WAAW,UAAU,OAAO,UAAU;AAC1E;AAEA,SAAS,YAAoC;AAC3C,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,SAAU,QAAO;AAE9D,SAAO,gBAAgB,KAAK,OAAO,SAAS,QAAQ;AACtD;AAGO,SAAS,kBAA0B;AACxC,QAAM,QAAQ,UAAU;AAExB,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,OAAO,MAAM,CAAC;AAEpB,UAAQ,QAAQ,SAAS,MAAM,SAAS,MAAM,OAAO,KAAK,MAAM,GAAG,OAAO,SAAS;AACrF;AAGO,SAAS,cAAc,UAAmB,CAAC,GAA0B;AAC1E,QAAM,UAAU,QAAQ,WAAW,SAAS,8BAA8B,KAAK;AAE/E,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,OAAO,QAAQ,OAAO,UAAU,IAAI,CAAC,KAAK,IAAI,MAAM,GAAG,OAAO,GAAG;AAEvE,MAAI,CAAC,IAAK,QAAO;AAEjB,SAAO,EAAE,KAAK,UAAU,cAAc,mBAAmB,GAAG,CAAC,IAAI,WAAW,GAAG;AACjF;;;AC5CA,IAAM,OAAO,CAAC,MAAc,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG;AAG9C,SAAS,KAAK,OAAwB;AAC3C,QAAM,IAAI,OAAO,KAAK;AAEtB,SAAO,OAAO,SAAS,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI;AAC9C;AAGO,SAAS,SAAS,MAAoB;AAC3C,SACE,GAAG,KAAK,eAAe,CAAC,IAAI,KAAK,KAAK,YAAY,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK,WAAW,CAAC,CAAC,IAChF,KAAK,KAAK,YAAY,CAAC,CAAC,IAAI,KAAK,KAAK,cAAc,CAAC,CAAC,IAAI,KAAK,KAAK,cAAc,CAAC,CAAC;AAE3F;AAEA,SAAS,UAAU,OAA+B;AAChD,MAAI;AACF,UAAM,MAAM,KAAK,UAAU,KAAK;AAEhC,WAAO,OAAO,QAAQ,WAAW,MAAM;AAAA,EACzC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,cAAc,MAAwC;AACpE,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,OAAO,UAAU,IAAI;AAE3B,MAAI,SAAS,QAAQ,KAAK,UAAU,OAAO,KAAM,QAAO;AAExD,QAAM,OAAgC,CAAC;AAEvC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,UAAM,YAAY,UAAU,EAAE,GAAG,MAAM,CAAC,GAAG,GAAG,OAAO,YAAY,EAAE,CAAC;AAEpE,QAAI,cAAc,QAAQ,UAAU,SAAS,OAAO,KAAM;AAE1D,SAAK,GAAG,IAAI;AAAA,EACd;AAEA,SAAO,UAAU,EAAE,GAAG,MAAM,YAAY,EAAE,CAAC,KAAK;AAClD;AAEO,SAAS,OAAe;AAC7B,QAAM,IAAI,OAAO,WAAW,cAAc,SAAS;AAEnD,MAAI,KAAK,OAAO,EAAE,eAAe,WAAY,QAAO,EAAE,WAAW;AAGjE,QAAM,QAAQ,IAAI,WAAW,EAAE;AAE/B,MAAI,KAAK,OAAO,EAAE,oBAAoB,YAAY;AAChD,MAAE,gBAAgB,KAAK;AAAA,EACzB,OAAO;AACL,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,EAAG,OAAM,CAAC,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG;AAAA,EACrF;AAEA,QAAM,CAAC,IAAK,MAAM,CAAC,IAAI,KAAQ;AAC/B,QAAM,CAAC,IAAK,MAAM,CAAC,IAAI,KAAQ;AAE/B,QAAM,MAAM,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAE7E,SAAO,GAAG,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,EAAE,CAAC;AAC1G;AAGO,SAAS,UAAU,OAA2B;AACnD,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,IAAI,SAAS,oBAAI,KAAK,CAAC;AAAA,IACvB,KAAK,MAAM,IAAI,MAAM,GAAG,OAAO,GAAG;AAAA,IAClC,OAAO,MAAM,MAAM,MAAM,GAAG,OAAO,KAAK;AAAA,IACxC,YAAY,MAAM,UAAU,MAAM,GAAG,EAAE;AAAA,IACvC,WAAW,MAAM,SAAS,MAAM,GAAG,OAAO,SAAS;AAAA,IACnD,YAAY,KAAK,MAAM,SAAS;AAAA,IAChC,SAAS,QAAQ,MAAM,GAAG,OAAO,OAAO;AAAA,IACxC,MAAM,cAAc,MAAM,IAAI;AAAA,EAChC;AACF;;;AC7FA,IAAM,iBAAiB;AAEvB,IAAM,QAAQ,CAAC,OAAe,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAGpF,IAAM,cAAc,CAAC,SAA2B,IAAI,KAAK,KAAK,GAAG,CAAC;AAElE,eAAe,KAAK,UAAkB,MAA4D;AAChG,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,UAAU;AAAA,MACrC,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,aAAO,EAAE,IAAI,OAAO,WAAW,SAAS,WAAW,OAAO,SAAS,UAAU,IAAI;AAAA,IACnF;AAEA,UAAM,UAAW,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,IAAI;AAGvD,QAAI,WAAW,OAAO,QAAQ,eAAe,YAAY,QAAQ,eAAe,GAAG;AACjF,aAAO,EAAE,IAAI,OAAO,WAAW,MAAM;AAAA,IACvC;AAEA,WAAO,EAAE,IAAI,MAAM,WAAW,MAAM;AAAA,EACtC,QAAQ;AACN,WAAO,EAAE,IAAI,OAAO,WAAW,KAAK;AAAA,EACtC;AACF;AAGA,eAAsB,KAAK,UAAkB,MAAsC;AACjF,MAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,QAAM,OAAO,YAAY,IAAI;AAC7B,QAAM,QAAQ,MAAM,KAAK,UAAU,IAAI;AAEvC,MAAI,MAAM,GAAI,QAAO;AACrB,MAAI,CAAC,MAAM,UAAW,QAAO;AAE7B,QAAM,MAAM,cAAc;AAE1B,UAAQ,MAAM,KAAK,UAAU,IAAI,GAAG,KAAK,OAAO;AAClD;AAGO,SAAS,aAAa,UAAkB,MAAsB;AACnE,MAAI,KAAK,WAAW,EAAG;AAEvB,QAAM,OAAO,YAAY,IAAI;AAE7B,MAAI;AACF,UAAM,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAE1D,QAAI,UAAU,cAAc,UAAU,WAAW,UAAU,IAAI,EAAG;AAAA,EACpE,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,SAAK,MAAM,UAAU;AAAA,MACnB,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C;AAAA,MACA,WAAW;AAAA,IACb,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;;;ACpEA,IAAM,oBAAoB;AAC1B,IAAM,iBAAiB;AAEvB,IAAM,kBAAkB;AACxB,IAAM,2BAA2B;AAGjC,IAAM,cAAc,IAAI,YAAY;AAS7B,SAAS,YAAY,UAAkB,UAA8B;AAC1E,MAAI,SAAqB,CAAC;AAC1B,MAAI,QAAQ;AACZ,MAAI,WAAW;AACf,MAAI,UAAU;AACd,MAAI,WAAW;AAEf,MAAI,SAAS;AAEb,QAAM,QAAQ,YAAY,MAAM;AAC9B,SAAK,MAAM;AAAA,EACb,GAAG,iBAAiB;AAEpB,WAAS,OAAa;AACpB,cAAU;AACV,aAAS,CAAC;AACV,YAAQ;AACR,kBAAc,KAAK;AAAA,EACrB;AAEA,iBAAe,QAAuB;AACpC,QAAI,WAAW,YAAY,OAAO,WAAW,EAAG;AAEhD,UAAM,QAAQ;AAEd,aAAS,CAAC;AACV,YAAQ;AACR,eAAW;AAEX,QAAI;AACF,UAAI;AAGJ,UAAI;AACF,kBAAU,MAAM;AAAA,UACd;AAAA,UACA,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,QACzB;AAAA,MACF,QAAQ;AACN,kBAAU;AAAA,MACZ;AAEA,UAAI,YAAY,MAAM;AACpB,mBAAW;AACX;AAAA,MACF;AAEA,kBAAY;AAIZ,UAAI,YAAY,4BAA4B,CAAC,QAAQ;AACnD,iBAAS;AACT,aAAK;AAGL,YAAI;AACF,qBAAW;AAAA,QACb,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF,UAAE;AACA,iBAAW;AAAA,IACb;AAAA,EACF;AAEA,SAAO;AAAA,IACL,KAAK,KAAqB;AACxB,UAAI,QAAS;AAEb,UAAI;AAEJ,UAAI;AACF,eAAO,KAAK,UAAU,GAAG;AAAA,MAC3B,QAAQ;AACN;AAAA,MACF;AAEA,YAAM,OAAO,YAAY,OAAO,IAAI,EAAE;AAEtC,aAAO,KAAK,EAAE,MAAM,KAAK,CAAC;AAC1B,eAAS;AAET,UAAI,OAAO,UAAU,kBAAkB,SAAS,gBAAiB,MAAK,MAAM;AAAA,IAC9E;AAAA,IACA,QAAc;AACZ,WAAK,MAAM;AAAA,IACb;AAAA,IACA,gBAAsB;AACpB,UAAI,WAAW,OAAO,WAAW,EAAG;AAEpC,YAAM,QAAQ;AAEd,eAAS,CAAC;AACV,cAAQ;AACR;AAAA,QACE;AAAA,QACA,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,MACzB;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACF;;;AC9HA,IAAM,UAAU;AAChB,IAAM,WAAW;AACjB,IAAM,UAAU,KAAK,KAAK;AAC1B,IAAM,oBAAoB;AAE1B,IAAI,WAA0B;AAC9B,IAAI,gBAAgB;AAGpB,SAAS,KAAK,KAA4B;AACxC,MAAI;AACF,WAAO,aAAa,QAAQ,GAAG;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,MAAM,KAAa,OAAqB;AAC/C,MAAI;AACF,iBAAa,QAAQ,KAAK,KAAK;AAAA,EACjC,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,QAAgB;AACvB,MAAI;AACF,QAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,WAAY,QAAO,OAAO,WAAW;AAAA,EACzG,QAAQ;AAAA,EAAC;AAET,SAAO,KAAK,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAChF;AAGO,SAAS,eAAqB;AACnC,aAAW;AACX,kBAAgB;AAClB;AAGO,SAAS,eAAuB;AACrC,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,WAAW,KAAK,OAAO;AAC7B,QAAM,UAAU,KAAK,QAAQ;AAC7B,QAAM,OAAO,YAAY,OAAO,OAAO,MAAM,OAAO,OAAO;AAC3D,QAAM,QAAQ,OAAO,SAAS,IAAI,KAAK,MAAM,QAAQ;AAErD,MAAI;AAEJ,MAAI,YAAY,OAAO;AAErB,SAAK;AAAA,EACP,WAAW,CAAC,YAAY,UAAU;AAEhC,SAAK;AAAA,EACP,OAAO;AACL,SAAK,MAAM;AACX,UAAM,SAAS,EAAE;AACjB,oBAAgB;AAAA,EAClB;AAEA,aAAW;AAEX,MAAI,MAAM,iBAAiB,mBAAmB;AAC5C,UAAM,UAAU,OAAO,GAAG,CAAC;AAC3B,oBAAgB;AAAA,EAClB;AAEA,SAAO;AACT;;;ACrEO,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;AAC1B,IAAM,uBAAuB;AAG7B,SAAS,eAAe,MAAoC;AACjE,MAAI,QAAQ;AACZ,MAAI;AACJ,MAAI,UAAU;AAEd,QAAM,WAAW,MAAO,QAAQ,uBAAuB,oBAAoB;AAE3E,WAAS,WAAiB;AACxB,QAAI,WAAW,UAAU,OAAW;AAEpC,YAAQ,WAAW,MAAM;AACvB,cAAQ;AAER,UAAI,WAAW,SAAS,oBAAoB,UAAW;AAEvD,eAAS;AAET,UAAI;AACF,aAAK;AAAA,MACP,QAAQ;AAAA,MAER;AAEA,eAAS;AAAA,IACX,GAAG,SAAS,CAAC;AAAA,EACf;AAEA,WAAS,QAAc;AACrB,QAAI,UAAU,QAAW;AACvB,mBAAa,KAAK;AAClB,cAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,qBAAqB,MAAM;AAC/B,QAAI,QAAS;AAEb,QAAI,SAAS,oBAAoB,WAAW;AAC1C,eAAS;AAAA,IACX,OAAO;AAEL,YAAM;AAAA,IACR;AAAA,EACF;AAEA,WAAS,iBAAiB,oBAAoB,kBAAkB;AAEhE,MAAI,SAAS,oBAAoB,UAAW,UAAS;AAErD,SAAO;AAAA,IACL,OAAa;AACX,gBAAU;AACV,YAAM;AACN,eAAS,oBAAoB,oBAAoB,kBAAkB;AAAA,IACrE;AAAA,EACF;AACF;;;AC3DO,IAAM,aAAa;AAQnB,SAAS,cAAc,MAA0D;AACtF,MAAI,UAAU;AACd,QAAM,SAAS,oBAAI,IAAoB;AAEvC,QAAM,WAAW,CAAC,UAAiB;AACjC,QAAI,QAAS;AAEb,QAAI;AACF,YAAM,SAAU,MAAsB;AAEtC,UAAI,CAAC,UAAU,OAAO,WAAW,SAAU;AAE3C,YAAM,YAAY,OAAO,OAAO,cAAc,WAAW,OAAO,YAAY;AAE5E,UAAI,CAAC,UAAW;AAEhB,YAAM,QAAS,OAAO,SAAS,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ,CAAC;AAElF,YAAM,YAAY,KAAK,MAAM,WAAW,MAAM,cAAc,CAAC;AAC7D,YAAM,YAAY,KAAK,MAAM,WAAW,MAAM,oBAAoB,CAAC;AAEnE,YAAM,MAAM,GAAG,SAAS,IAAI,SAAS,IAAI,SAAS;AAClD,YAAM,OAAO,OAAO,IAAI,GAAG,KAAK,KAAK;AAGrC,UAAI,MAAM,WAAY;AAEtB,aAAO,IAAI,KAAK,GAAG;AAEnB,YAAM,OAAgC;AAAA,QACpC;AAAA,QACA,YAAY;AAAA,QACZ,YAAY;AAAA,MACd;AAEA,UAAI,QAAQ,WAAY,MAAK,SAAS;AAEtC,WAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IAC1B,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,WAAS,iBAAiB,aAAa,QAAQ;AAE/C,SAAO;AAAA,IACL,OAAa;AACX,gBAAU;AACV,eAAS,oBAAoB,aAAa,QAAQ;AAClD,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AACF;;;ACxDA,IAAM,kBAAkB,MAAc;AACpC,MAAI;AACF,UAAM,MAAM,SAAS;AAErB,QAAI,CAAC,IAAK,QAAO;AAEjB,UAAM,MAAM,IAAI,IAAI,GAAG;AAEvB,WAAO,GAAG,IAAI,MAAM,GAAG,IAAI,QAAQ;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,cACd,MACA,aACgB;AAChB,MAAI,UAAU;AACd,MAAI,WAA0B;AAC9B,MAAI,eAA8B;AAGlC,QAAM,cAAc,CAAC,SAAyB;AAC5C,QAAI;AACF,aAAO,GAAG,IAAI,GAAG,OAAO,SAAS,MAAM;AAAA,IACzC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAIA,QAAM,WAAW,CAAC,aAA2B,WAAoB;AAC/D,QAAI,QAAS;AAEb,QAAI;AACF,YAAM,OAAO,YAAY;AACzB,YAAM,WAAW,YAAY,IAAI;AAGjC,UAAI,UAAU,aAAa,aAAc;AAGzC,YAAM,WAAW,aAAa,OAAO,gBAAgB,IAAI;AAEzD,iBAAW;AACX,qBAAe;AACf,WAAK,MAAM,QAAQ;AAAA,IACrB,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,eAAe,OAAO,QAAQ;AACpC,QAAM,kBAAkB,OAAO,QAAQ;AAEvC,QAAM,QAAQ,CAAC,aACb,SAAS,WAA0B,MAAmD;AACpF,UAAM,SAAS,SAAS,MAAM,MAAM,IAAI;AAExC,aAAS,aAAa,IAAI;AAE1B,WAAO;AAAA,EACT;AAEF,QAAM,cAAc,MAAM,YAAY;AACtC,QAAM,iBAAiB,MAAM,eAAe;AAE5C,SAAO,QAAQ,YAAY;AAC3B,SAAO,QAAQ,eAAe;AAE9B,QAAM,aAAa,MAAM,SAAS,aAAa,IAAI;AAEnD,SAAO,iBAAiB,YAAY,UAAU;AAE9C,WAAS,aAAa,IAAI;AAE1B,SAAO;AAAA,IACL,OAAa;AACX,gBAAU;AACV,iBAAW;AACX,qBAAe;AAGf,UAAI,OAAO,QAAQ,cAAc,aAAa;AAC5C,eAAO,QAAQ,YAAY;AAAA,MAC7B;AAEA,UAAI,OAAO,QAAQ,iBAAiB,gBAAgB;AAClD,eAAO,QAAQ,eAAe;AAAA,MAChC;AAEA,aAAO,oBAAoB,YAAY,UAAU;AAAA,IACnD;AAAA,IACA,QAAQ,MAAqB;AAC3B,eAAS,MAAM,QAAQ,YAAY,GAAG,KAAK;AAAA,IAC7C;AAAA,EACF;AACF;;;AClFA,IAAI,UAA0B;AAC9B,IAAM,aAAa,oBAAI,IAAY;AAGnC,SAAS,MAAM,IAAsB;AACnC,MAAI;AACF,OAAG;AAAA,EACL,QAAQ;AAAA,EAER;AACF;AAGA,SAAS,SAAS,KAAa,SAAuB;AACpD,MAAI,WAAW,IAAI,GAAG,EAAG;AAEzB,aAAW,IAAI,GAAG;AAClB,UAAQ,KAAK,kBAAkB,OAAO,EAAE;AAC1C;AAEA,SAAS,aAAa,UAA2C;AAC/D,QAAM,QAAQ,OAAO,SAAS,OAAO,QAAQ,OAAO,EAAE;AAEtD,SAAO,EAAE,UAAU,SAAS,MAAM,GAAG,OAAO,QAAQ,GAAG,OAAO,MAAM,MAAM,GAAG,OAAO,KAAK,EAAE;AAC7F;AAGA,SAAS,YACP,OAOA,YACM;AACN,MAAI,WAAY,OAAM,MAAM,MAAM,MAAM,cAAc,CAAC;AAEvD,QAAM,MAAM,MAAM,UAAU,KAAK,CAAC;AAClC,QAAM,MAAM,MAAM,gBAAgB,CAAC;AACnC,QAAM,MAAM,MAAM,eAAe,CAAC;AAClC,QAAM,MAAM,MAAM,aAAa,CAAC;AAChC,QAAM,MAAM,MAAM,MAAM,KAAK,CAAC;AAChC;AAGA,SAAS,SAAS,YAA2B;AAC3C,QAAM,UAAU;AAEhB,MAAI,CAAC,QAAS;AAEd,YAAU;AACV,aAAW,MAAM;AAEjB,cAAY,SAAS,UAAU;AAC/B,eAAa;AACf;AAGO,SAAS,KAAK,UAAmB,CAAC,GAAS;AAChD,QAAM,MAAM;AACV,QAAI,QAAS;AAEb,UAAM,SAAS,cAAc,OAAO;AAEpC,QAAI,CAAC,OAAQ;AAGb,UAAM,QAAQ,YAAY,OAAO,UAAU,MAAM,SAAS,CAAC;AAE3D,UAAM,OAAa,CAAC,OAAO,QAAQ,CAAC,MAAM;AACxC,YAAM;AAAA,QACJ,UAAU;AAAA,UACR,KAAK,OAAO;AAAA,UACZ;AAAA,UACA,WAAW,aAAa;AAAA,UACxB,UAAU,MAAM,YAAY,gBAAgB;AAAA,UAC5C,WAAW,MAAM;AAAA,UACjB,MAAM,MAAM;AAAA,QACd,CAAC;AAAA,MACH;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,MAAM,MAAM,MAAM,cAAc,CAAC;AAExD,UAAM,eAAe,MAAM;AACzB,UAAI,SAAS,oBAAoB,SAAU,UAAS;AAAA,IACtD;AACA,UAAM,eAAe,MAAM;AACzB,aAAO,oBAAoB,YAAY,QAAQ;AAC/C,eAAS,oBAAoB,oBAAoB,YAAY;AAAA,IAC/D;AAEA,QAAI;AACJ,QAAI;AACJ,QAAI;AAGJ,QAAI;AACF,aAAO,iBAAiB,YAAY,QAAQ;AAC5C,eAAS,iBAAiB,oBAAoB,YAAY;AAE1D,sBAAgB,eAAe,MAAM,KAAK,WAAW,CAAC,EAAE;AACxD,qBAAe;AAAA,QAAc,CAAC,YAC5B,KAAK,oBAAoB,EAAE,WAAW,QAAQ,WAAW,MAAM,QAAQ,KAAK,CAAC;AAAA,MAC/E,EAAE;AACF,iBAAW;AAAA,QACT,CAAC,MAAM,aAAa,KAAK,aAAa,EAAE,UAAU,MAAM,MAAM,aAAa,QAAQ,EAAE,CAAC;AAAA,QACtF;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,kBAAY,EAAE,OAAO,cAAc,UAAU,eAAe,aAAa,GAAG,KAAK;AACjF,YAAM;AAAA,IACR;AAEA,cAAU,EAAE,OAAO,MAAM,cAAc,UAAU,eAAe,aAAa;AAAA,EAC/E,CAAC;AACH;AAEO,SAAS,MAAM,OAAe,OAAyC;AAC5E,QAAM,MAAM;AACV,QAAI,CAAC,QAAS;AACd,QAAI,CAAC,MAAO;AAEZ,UAAM,aAAa,MAAM,YAAY;AAErC,QAAK,gBAAsC,SAAS,UAAU,GAAG;AAC/D,eAAS,YAAY,IAAI,KAAK,oEAAoE;AAElG;AAAA,IACF;AAEA,QAAI,CAAC,cAAc,KAAK,KAAK,GAAG;AAC9B,eAAS,QAAQ,IAAI,KAAK,qFAAgF;AAAA,IAC5G;AAEA,YAAQ,KAAK,OAAO,EAAE,MAAM,MAAM,CAAC;AAAA,EACrC,CAAC;AACH;AAGO,SAAS,aAAa,OAAoF;AAC/G,QAAM,MAAM;AACV,QAAI,CAAC,QAAS;AAEd,UAAM,EAAE,YAAY,YAAY,GAAG,MAAM,IAAI,SAAS,CAAC;AACvD,UAAM,KAAK,KAAK,UAAU;AAE1B,QAAI,CAAC,IAAI;AACP,eAAS,WAAW,2DAA2D,UAAU,GAAG;AAE5F;AAAA,IACF;AAEA,UAAM,aAAa,OAAO,UAAU;AACpC,UAAM,gBAAgB,OAAO,SAAS,UAAU;AAGhD,QAAI,eAAe,UAAa,CAAC,eAAe;AAC9C,eAAS,WAAW,+CAA+C,UAAU,kBAAkB;AAAA,IACjG;AAGA,UAAM,OAAgC;AAAA,MACpC,GAAG;AAAA,MACH,YAAY;AAAA,MACZ,YAAY,gBAAgB,KAAK,MAAM,UAAU,IAAI;AAAA,IACvD;AAEA,YAAQ,KAAK,iBAAiB,EAAE,WAAW,IAAI,KAAK,CAAC;AAAA,EACvD,CAAC;AACH;AAGO,SAAS,cAAc,MAAqB;AACjD,QAAM,MAAM,SAAS,SAAS,QAAQ,IAAI,CAAC;AAC7C;AAGO,SAAS,WAAiB;AAC/B,QAAM,MAAM,SAAS,IAAI,CAAC;AAC5B;AAEO,SAAS,WAAoB;AAClC,SAAO,YAAY;AACrB;;;AC/MA,IAAM,MAAM,EAAE,OAAO,cAAc,cAAc;AAE1C,SAAS,eAA2B;AACzC,SAAO;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../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":["export { init, isActive, shutdown, track, trackElement, trackPageView } from './core/analytics';\nexport { ENTITY_NAME, VERSION } from './core/constants';\nexport type { EventRow, JsonValue, Options } from './core/types';\nexport { useAnalytics } from './react/useAnalytics';\n","/** 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 // 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\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/** 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","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":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -2,8 +2,6 @@ type JsonValue = string | number | boolean | null | undefined | JsonValue[] | {
|
|
|
2
2
|
[key: string]: JsonValue;
|
|
3
3
|
};
|
|
4
4
|
interface Options {
|
|
5
|
-
/** Portal page internal_name. Default: derived from URL. */
|
|
6
|
-
app?: string;
|
|
7
5
|
/** false disables all tracking. Overrides PP_VARIABLES. */
|
|
8
6
|
enabled?: boolean;
|
|
9
7
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,6 @@ type JsonValue = string | number | boolean | null | undefined | JsonValue[] | {
|
|
|
2
2
|
[key: string]: JsonValue;
|
|
3
3
|
};
|
|
4
4
|
interface Options {
|
|
5
|
-
/** Portal page internal_name. Default: derived from URL. */
|
|
6
|
-
app?: string;
|
|
7
5
|
/** false disables all tracking. Overrides PP_VARIABLES. */
|
|
8
6
|
enabled?: boolean;
|
|
9
7
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/core/constants.ts
|
|
2
2
|
var ENTITY_NAME = "analytics_events";
|
|
3
|
-
var VERSION = "0.
|
|
3
|
+
var VERSION = "0.2.0";
|
|
4
4
|
var MI_PAGE_PATH_RE = /^\/(p[tl]?)\/([^/]+)(.*)$/;
|
|
5
5
|
var WIDTHS = {
|
|
6
6
|
app: 100,
|
|
@@ -41,7 +41,7 @@ function currentPagePath() {
|
|
|
41
41
|
function resolveConfig(options = {}) {
|
|
42
42
|
const enabled = options.enabled ?? readFlag("CUSTOM_APP_ANALYTICS_ENABLED") ?? true;
|
|
43
43
|
if (!enabled) return null;
|
|
44
|
-
const app = (
|
|
44
|
+
const app = (matchPage()?.[2] ?? "").slice(0, WIDTHS.app);
|
|
45
45
|
if (!app) return null;
|
|
46
46
|
return { app, endpoint: `/data/page/${encodeURIComponent(app)}/${ENTITY_NAME}` };
|
|
47
47
|
}
|
|
@@ -346,6 +346,53 @@ function startHeartbeat(emit) {
|
|
|
346
346
|
|
|
347
347
|
// src/core/signals/miRender.ts
|
|
348
348
|
var RENDER_CAP = 50;
|
|
349
|
+
var MAX_KEYS = 200;
|
|
350
|
+
var PROP_DEPTH = 4;
|
|
351
|
+
var PROP_KEYS = 40;
|
|
352
|
+
var PROP_STRING = 40;
|
|
353
|
+
var IDENTITY_KEYS = /* @__PURE__ */ new Set(["element", "element_id", "segment", "segment_id", "segment_value_id"]);
|
|
354
|
+
var SKIP = /* @__PURE__ */ Symbol("skip");
|
|
355
|
+
function leafValue(value) {
|
|
356
|
+
if (value === null) return null;
|
|
357
|
+
switch (typeof value) {
|
|
358
|
+
case "boolean":
|
|
359
|
+
return value;
|
|
360
|
+
case "number":
|
|
361
|
+
return Number.isFinite(value) ? value : SKIP;
|
|
362
|
+
case "string":
|
|
363
|
+
return value.slice(0, PROP_STRING);
|
|
364
|
+
default:
|
|
365
|
+
return SKIP;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function flattenProps(source, out, prefix, depth) {
|
|
369
|
+
let dropped = false;
|
|
370
|
+
for (const [key, value] of Object.entries(source)) {
|
|
371
|
+
if (Object.keys(out).length >= PROP_KEYS) return true;
|
|
372
|
+
if (depth === 0 && IDENTITY_KEYS.has(key)) continue;
|
|
373
|
+
const path = prefix === "" ? key : `${prefix}.${key}`;
|
|
374
|
+
if (Array.isArray(value)) {
|
|
375
|
+
out[`prop.${path}`] = `arr:${value.length}`;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (value !== null && typeof value === "object") {
|
|
379
|
+
if (depth + 1 < PROP_DEPTH) dropped = flattenProps(value, out, path, depth + 1) || dropped;
|
|
380
|
+
else dropped = true;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
const leaf = leafValue(value);
|
|
384
|
+
if (leaf !== SKIP) out[`prop.${path}`] = leaf;
|
|
385
|
+
}
|
|
386
|
+
return dropped;
|
|
387
|
+
}
|
|
388
|
+
function hash(text) {
|
|
389
|
+
let h = 2166136261;
|
|
390
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
391
|
+
h ^= text.charCodeAt(i);
|
|
392
|
+
h = Math.imul(h, 16777619);
|
|
393
|
+
}
|
|
394
|
+
return (h >>> 0).toString(36);
|
|
395
|
+
}
|
|
349
396
|
function startMiRender(emit) {
|
|
350
397
|
let stopped = false;
|
|
351
398
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -358,16 +405,21 @@ function startMiRender(emit) {
|
|
|
358
405
|
if (!component) return;
|
|
359
406
|
const props = detail.props && typeof detail.props === "object" ? detail.props : {};
|
|
360
407
|
const elementId = toId(props.element ?? props.element_id ?? 0);
|
|
361
|
-
const segmentId = toId(props.segment ?? props.segment_value_id ?? 0);
|
|
362
|
-
const
|
|
408
|
+
const segmentId = toId(props.segment ?? props.segment_id ?? props.segment_value_id ?? 0);
|
|
409
|
+
const flat = {};
|
|
410
|
+
const propsDropped = flattenProps(props, flat, "", 0);
|
|
411
|
+
const bucket = `${component}|${elementId}|${segmentId}|${hash(JSON.stringify(flat))}`;
|
|
412
|
+
const key = counts.has(bucket) || counts.size < MAX_KEYS ? bucket : `${component}|*`;
|
|
363
413
|
const seq = (counts.get(key) ?? 0) + 1;
|
|
364
414
|
if (seq > RENDER_CAP) return;
|
|
365
415
|
counts.set(key, seq);
|
|
366
416
|
const meta = {
|
|
367
417
|
component,
|
|
368
418
|
element_id: elementId,
|
|
369
|
-
segment_id: segmentId
|
|
419
|
+
segment_id: segmentId,
|
|
420
|
+
...flat
|
|
370
421
|
};
|
|
422
|
+
if (propsDropped) meta.props_dropped = 1;
|
|
371
423
|
if (seq === RENDER_CAP) meta.capped = 1;
|
|
372
424
|
emit({ elementId, meta });
|
|
373
425
|
} catch {
|
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/** 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 // 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\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/** 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","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":[]}
|