@metricinsights/ca-analytics 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,19 +33,22 @@ npm i @metricinsights/ca-analytics
33
33
 
34
34
  ## Usage
35
35
 
36
- ```ts
36
+ ```tsx
37
37
  import { init, useAnalytics } from '@metricinsights/ca-analytics';
38
38
 
39
39
  // app entry, once
40
40
  init();
41
41
 
42
- // anywhere in the app
43
- const { track } = useAnalytics();
44
- track('tile_clicked', { tile: 'revenue' });
42
+ // in any component
43
+ function RevenueTile() {
44
+ const { track } = useAnalytics();
45
+
46
+ return <button onClick={() => track('tile_clicked', { tile: 'revenue' })}>Revenue</button>;
47
+ }
45
48
  ```
46
49
 
47
50
  `useAnalytics()` is a thin wrapper over the module singleton — no provider, no context, callable
48
- from any component.
51
+ from any component. Outside React, import `track`, `trackElement` and `trackPageView` directly.
49
52
 
50
53
  ## API
51
54
 
@@ -64,11 +67,15 @@ later `init()` inside the 30-minute idle window resumes the *same* `session_id`.
64
67
 
65
68
  | Export | Signature | Notes |
66
69
  | --- | --- | --- |
67
- | `track` | `(event: string, props?: Record<string, unknown>) => void` | Empty and [reserved](#events) names are ignored. |
68
- | `trackElement` | `(props: { element_id: number; segment_id?: number; [key: string]: unknown }) => void` | Emits `element_click`. |
70
+ | `track` | `(event: string, props?: object) => void` | Empty and [reserved](#events) names are ignored. |
71
+ | `trackElement` | `<T extends { element_id: number; segment_id?: number }>(props: T) => void` | Emits `element_click`. |
69
72
  | `trackPageView` | `(path?: string) => void` | For routers the `history` patch cannot observe. Skips dedupe — always emits. |
70
73
  | `useAnalytics` | `() => { track, trackElement, trackPageView }` | React hook; no provider needed. |
71
74
 
75
+ Props can be any object, including interface-typed ones, with values of any type. They are
76
+ serialized with `JSON.stringify` semantics: a `Date` becomes its ISO string, and `undefined` and
77
+ functions are dropped.
78
+
72
79
  ### Constants
73
80
 
74
81
  | Export | Value | Notes |
@@ -93,7 +100,7 @@ Analytics Enabled]`) counts as *not set* and falls through to the option or defa
93
100
 
94
101
  | Event | Fires when | `meta` |
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
+ | `page_view` | Once on `init()`, then on every `pushState` / `replaceState` / `popstate` / `hashchange`, or on demand via `trackPageView()`. | `referrer`, `query` — always both, always strings |
97
104
  | `heartbeat` | Tab visible: every 60s for 5 beats, then every 300s. | `{}` |
98
105
  | `component_render` | The `mi-render` `CustomEvent` MI dispatches on `document`. | `component`, `element_id`, `segment_id`, plus each prop as `prop.<path>` |
99
106
  | `element_click` | `trackElement()`. | `element_id`, `segment_id`, plus your own keys |
@@ -107,15 +114,22 @@ name that does not match is still tracked verbatim but logs one console warning,
107
114
  dataset is append-only with no rename — `'Tile Click'`, `'tile_click '` and `'tileClick'` would
108
115
  become three permanent, unmergeable series. Reserved-name matching is case-insensitive.
109
116
 
110
- Two meta keys are reserved: `_truncated` is stamped only when meta actually exceeds 2000
111
- characters, and the dashboard hides both `_truncated` and `capped` from its property view, so do
117
+ Two meta keys are reserved: `_truncated` is stamped only when meta exceeds 2000 characters or
118
+ cannot be serialized as a whole (for example a `BigInt` or a circular value), and the dashboard hides both `_truncated` and `capped` from its property view, so do
112
119
  not use either as your own prop name.
113
120
 
114
- **`page_view`** dedupes on path + search, so a router calling `replaceState` for scroll or filter
115
- sync does not emit. `meta.referrer` follows GA4 semantics: the first view of the document uses
116
- `document.referrer` normalized to origin + pathname; every later view uses the `page_path` of the
117
- previous view. `meta.query` is `location.search` without the leading `?`. Both are capped at 300
118
- characters; either can be `''`.
121
+ **`page_view`** dedupes on path + search (+ hash route, see below), so a router calling
122
+ `replaceState` for scroll or filter sync does not emit. `meta.referrer` follows GA4 semantics: the
123
+ first view of the document uses `document.referrer` normalized to origin + pathname; every later
124
+ view uses the `page_path` of the previous view. `meta.query` is `location.search` without the
125
+ leading `?`. Both are capped at 300 characters; either can be `''`.
126
+
127
+ **Hash routing.** Hash-routed apps (for example React Router's `HashRouter`) are tracked without
128
+ extra setup. When `location.hash` starts with `#/`, the fragment is the route: `#/reports/42?tab=1`
129
+ records `page_path` `/reports/42` (it replaces any path below the portal page), and the fragment's
130
+ own query is appended to `meta.query` with `&` (`?embed=1#/reports?tab=1` → `embed=1&tab=1`). A
131
+ change of the hash route is a new view. A plain in-page anchor (`#section`, anything not starting
132
+ with `#/`) is not a route: it never changes `page_path` and never emits a `page_view`.
119
133
 
120
134
  **`component_render`** reads `detail.component` plus `detail.props`. `MiNamespace.render()` is a
121
135
  pure event emitter, so `document` sees every shared component render — no patching.
@@ -126,15 +140,18 @@ the spellings MI's own components use. No other key feeds `element_id`, which jo
126
140
 
127
141
  Every other prop flattens to `prop.<dotted.path>` — flat, because the dashboard summarizes per
128
142
  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.
143
+ arrays as `arr:<length>`, 4 levels deep, 40 keys. When the depth or key cap drops a prop, the row
144
+ carries `props_dropped: 1`.
130
145
 
131
146
  Props are stored verbatim and readable by anyone with dashboard access. Keep record-specific text
132
147
  out of props you render with.
133
148
 
134
149
  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.
150
+ `capped: 1`. Past 200 distinct buckets per `init()`, a render that would open a new bucket goes into
151
+ one shared overflow bucket per component instead, which has the same cap of 50.
136
152
 
137
- **`trackElement`** needs `element_id` to be finite and non-zero — anything else drops the event
153
+ **`trackElement`** needs `element_id` to be a non-zero number within the `int` column's range
154
+ (−2³¹ … 2³¹−1; a fraction is truncated) — anything else drops the event
138
155
  (one `console.warn` per runtime). `segment_id` is optional and defaults to `0` when omitted, with
139
156
  no warning; a supplied non-finite value also records `0` but logs one `console.warn` per runtime.
140
157
  Extra props pass through to `meta` untouched, and
@@ -156,7 +173,7 @@ stamps itself — the client never sends it.
156
173
  | `app` | text | Portal page internal name, read from `location.pathname` against `/^\/(p[tl]?)\/([^/]+)(.*)$/`. Capped at 100. |
157
174
  | `event` | text | Capped at 100. |
158
175
  | `session_id` | text | New session after 30 minutes idle. Survives reloads via `localStorage`. |
159
- | `page_path` | text | Route below the portal page, `/` at the root. Capped at 400. |
176
+ | `page_path` | text | Route below the portal page, `/` at the root; a `#/` hash route when present (see [Hash routing](#events)). Capped at 400. |
160
177
  | `element_id` | int | `0` when not applicable. Integers only. |
161
178
  | `version` | text | This package's version, capped at 20. |
162
179
  | `meta` | text | JSON string, capped at 2000. |
@@ -168,13 +185,15 @@ Text columns are wide (10500) and the caps above are client-side payload budget,
168
185
  inside `sendBeacon`'s 64 KiB quota — they are not schema limits.
169
186
 
170
187
  `meta` over 2000 characters drops whole keys rather than slicing, so the stored value always
171
- parses, and stamps `_truncated: 1`.
188
+ parses, and stamps `_truncated: 1`. Meta that `JSON.stringify` cannot serialize as a whole takes the
189
+ same path: keys that fail on their own are dropped, and `_truncated: 1` is stamped.
172
190
 
173
191
  ## Delivery
174
192
 
175
193
  Rows buffer and flush on whichever comes first: 15 seconds, 50 rows, or 30 KB encoded. On
176
194
  `pagehide`, and on `visibilitychange` to hidden (which is what actually fires on iOS), the buffer
177
- goes out via `sendBeacon`, falling back to `fetch(…, { keepalive: true })`.
195
+ goes out via `sendBeacon`, falling back to `fetch(…, { keepalive: true })`, in chunks of at most
196
+ 30 KB each.
178
197
 
179
198
  A failed batch retries once after 2 seconds — network errors, 429 and 5xx are retryable; a `200`
180
199
  carrying a non-zero `resultCode` is a rejected insert and is not. A failed batch is dropped, not
@@ -204,6 +223,15 @@ wrapped.
204
223
  > (`int → float → text`). One float or string converts the column permanently.
205
224
  > - **Never send `owner_user_id`.** The server stamps it and creates the column itself.
206
225
 
226
+ ## Releases
227
+
228
+ Releases use the shared workflows from [mi-examples-workflows](https://github.com/mi-examples/mi-examples-workflows) ([release flow](https://github.com/mi-examples/mi-examples-workflows/blob/main/docs/workflows.md#release-workflows)). This repository has no `develop` branch, so there are no betas:
229
+
230
+ 1. Run **Actions → Release → Run workflow**. It opens a release pull request `release/vX.Y.Z → main` with the version bump and the new `CHANGELOG.md` entry.
231
+ 2. Review and edit the entry in the pull request, then merge it.
232
+ 3. Merging publishes to npm under `latest` and creates the tag and the GitHub release.
233
+
234
+ Versions come from [Conventional Commits](https://www.conventionalcommits.org/) (`feat` → minor; `fix`, `perf`, `revert` → patch; `!` or `BREAKING CHANGE:` → major). Publishing uses npm Trusted Publishing (OIDC) from `.github/workflows/release.yml`, so don't rename that file.
207
235
  ## License
208
236
 
209
237
  MIT © 2026 Metric Insights
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.2.0";
37
+ var VERSION = "0.3.0";
38
38
  var MI_PAGE_PATH_RE = /^\/(p[tl]?)\/([^/]+)(.*)$/;
39
39
  var WIDTHS = {
40
40
  app: 100,
@@ -66,12 +66,29 @@ function matchPage() {
66
66
  if (typeof window === "undefined" || !window.location) return null;
67
67
  return MI_PAGE_PATH_RE.exec(window.location.pathname);
68
68
  }
69
+ function isHashRoute(hash2) {
70
+ return hash2.startsWith("#/");
71
+ }
72
+ function hashRoute() {
73
+ const hash2 = window.location.hash;
74
+ if (!isHashRoute(hash2)) return null;
75
+ const route = hash2.slice(1);
76
+ const pathEnd = route.search(/[?#]/);
77
+ const path = pathEnd === -1 ? route : route.slice(0, pathEnd);
78
+ const queryMatch = /\?([^#]*)/.exec(route);
79
+ return { path, query: queryMatch ? queryMatch[1] : "" };
80
+ }
69
81
  function currentPagePath() {
70
82
  const match = matchPage();
71
83
  if (!match) return "/";
72
- const rest = match[3];
84
+ const rest = hashRoute()?.path ?? match[3];
73
85
  return (rest && rest !== "" && rest !== "/" ? rest : "/").slice(0, WIDTHS.page_path);
74
86
  }
87
+ function currentQuery() {
88
+ const outer = window.location.search.replace(/^\?/, "");
89
+ const inner = hashRoute()?.query ?? "";
90
+ return outer && inner ? `${outer}&${inner}` : outer || inner;
91
+ }
75
92
  function resolveConfig(options = {}) {
76
93
  const enabled = options.enabled ?? readFlag("CUSTOM_APP_ANALYTICS_ENABLED") ?? true;
77
94
  if (!enabled) return null;
@@ -82,9 +99,11 @@ function resolveConfig(options = {}) {
82
99
 
83
100
  // src/core/event.ts
84
101
  var pad2 = (n) => String(n).padStart(2, "0");
102
+ var INT32_MIN = -(2 ** 31);
103
+ var INT32_MAX = 2 ** 31 - 1;
85
104
  function toId(value) {
86
- const n = Number(value);
87
- return Number.isFinite(n) ? Math.trunc(n) : 0;
105
+ const n = Math.trunc(Number(value));
106
+ return Number.isSafeInteger(n) && n >= INT32_MIN && n <= INT32_MAX ? n : 0;
88
107
  }
89
108
  function formatTs(date) {
90
109
  return `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())} ${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}:${pad2(date.getUTCSeconds())}`;
@@ -179,12 +198,13 @@ function sendOnUnload(endpoint, rows) {
179
198
  } catch {
180
199
  }
181
200
  try {
182
- void fetch(endpoint, {
201
+ fetch(endpoint, {
183
202
  method: "POST",
184
203
  credentials: "same-origin",
185
204
  headers: { "Content-Type": "application/json" },
186
205
  body,
187
206
  keepalive: true
207
+ }).catch(() => {
188
208
  });
189
209
  } catch {
190
210
  }
@@ -196,6 +216,22 @@ var MAX_FLUSH_ROWS = 50;
196
216
  var MAX_FLUSH_BYTES = 3e4;
197
217
  var MAX_CONSECUTIVE_FAILURES = 3;
198
218
  var textEncoder = new TextEncoder();
219
+ function chunkBySize(rows, maxBytes) {
220
+ const chunks = [];
221
+ let current = [];
222
+ let size = 2;
223
+ for (const row of rows) {
224
+ if (current.length > 0 && size + 1 + row.size > maxBytes) {
225
+ chunks.push(current);
226
+ current = [];
227
+ size = 2;
228
+ }
229
+ size += (current.length > 0 ? 1 : 0) + row.size;
230
+ current.push(row);
231
+ }
232
+ if (current.length > 0) chunks.push(current);
233
+ return chunks;
234
+ }
199
235
  function createQueue(endpoint, onKilled) {
200
236
  let buffer = [];
201
237
  let bytes = 0;
@@ -218,8 +254,8 @@ function createQueue(endpoint, onKilled) {
218
254
  buffer = [];
219
255
  bytes = 0;
220
256
  inFlight = true;
257
+ let outcome = "failed";
221
258
  try {
222
- let outcome;
223
259
  try {
224
260
  outcome = await send(
225
261
  endpoint,
@@ -228,6 +264,7 @@ function createQueue(endpoint, onKilled) {
228
264
  } catch {
229
265
  outcome = "failed";
230
266
  }
267
+ if (stopped) return;
231
268
  if (outcome === "ok") {
232
269
  failures = 0;
233
270
  return;
@@ -243,6 +280,7 @@ function createQueue(endpoint, onKilled) {
243
280
  }
244
281
  } finally {
245
282
  inFlight = false;
283
+ if (!stopped && outcome === "ok" && (buffer.length >= MAX_FLUSH_ROWS || bytes >= MAX_FLUSH_BYTES)) void flush();
246
284
  }
247
285
  }
248
286
  return {
@@ -267,10 +305,12 @@ function createQueue(endpoint, onKilled) {
267
305
  const batch = buffer;
268
306
  buffer = [];
269
307
  bytes = 0;
270
- sendOnUnload(
271
- endpoint,
272
- batch.map((b) => b.json)
273
- );
308
+ for (const chunk of chunkBySize(batch, MAX_FLUSH_BYTES)) {
309
+ sendOnUnload(
310
+ endpoint,
311
+ chunk.map((b) => b.json)
312
+ );
313
+ }
274
314
  },
275
315
  stop
276
316
  };
@@ -486,7 +526,8 @@ function startPageView(emit, currentPath) {
486
526
  let lastLocation = null;
487
527
  const locationKey = (path) => {
488
528
  try {
489
- return `${path}${window.location.search}`;
529
+ const { search, hash: hash2 } = window.location;
530
+ return `${path}${search}${isHashRoute(hash2) ? hash2 : ""}`;
490
531
  } catch {
491
532
  return path;
492
533
  }
@@ -517,6 +558,7 @@ function startPageView(emit, currentPath) {
517
558
  window.history.replaceState = patchedReplace;
518
559
  const onPopState = () => safeEmit(currentPath, true);
519
560
  window.addEventListener("popstate", onPopState);
561
+ window.addEventListener("hashchange", onPopState);
520
562
  safeEmit(currentPath, true);
521
563
  return {
522
564
  stop() {
@@ -530,6 +572,7 @@ function startPageView(emit, currentPath) {
530
572
  window.history.replaceState = originalReplace;
531
573
  }
532
574
  window.removeEventListener("popstate", onPopState);
575
+ window.removeEventListener("hashchange", onPopState);
533
576
  },
534
577
  emitNow(path) {
535
578
  safeEmit(() => path ?? currentPath(), false);
@@ -552,7 +595,7 @@ function warnOnce(key, message) {
552
595
  console.warn(`[ca-analytics] ${message}`);
553
596
  }
554
597
  function pageViewMeta(referrer) {
555
- const query = window.location.search.replace(/^\?/, "");
598
+ const query = currentQuery();
556
599
  return { referrer: referrer.slice(0, LIMITS.referrer), query: query.slice(0, LIMITS.query) };
557
600
  }
558
601
  function stopStarted(parts, flushFirst) {
@@ -576,7 +619,9 @@ function init(options = {}) {
576
619
  if (runtime) return;
577
620
  const config = resolveConfig(options);
578
621
  if (!config) return;
579
- const queue = createQueue(config.endpoint, () => shutdown());
622
+ const queue = createQueue(config.endpoint, () => {
623
+ if (runtime?.queue === queue) shutdown();
624
+ });
580
625
  const emit = (event, extra = {}) => {
581
626
  queue.push(
582
627
  createRow({
@@ -639,7 +684,7 @@ function trackElement(props) {
639
684
  const { element_id, segment_id, ...extra } = props ?? {};
640
685
  const id = toId(element_id);
641
686
  if (!id) {
642
- warnOnce("element", `trackElement() ignored a non-finite or zero element_id (${element_id})`);
687
+ warnOnce("element", `trackElement() ignored a zero, non-finite or out-of-range element_id (${element_id})`);
643
688
  return;
644
689
  }
645
690
  const segmentRaw = Number(segment_id);
@@ -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 // 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":[]}
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/** A hash router's fragment (`#/reports`). A plain in-page anchor (`#section`) is not a route. */\nexport function isHashRoute(hash: string): boolean {\n return hash.startsWith('#/');\n}\n\n/** Path and query a hash router keeps in the fragment: `#/reports?tab=1` → `/reports`, `tab=1`. Null without one. */\nfunction hashRoute(): { path: string; query: string } | null {\n const hash = window.location.hash;\n\n if (!isHashRoute(hash)) return null;\n\n const route = hash.slice(1);\n const pathEnd = route.search(/[?#]/);\n const path = pathEnd === -1 ? route : route.slice(0, pathEnd);\n const queryMatch = /\\?([^#]*)/.exec(route);\n\n return { path, query: queryMatch ? queryMatch[1] : '' };\n}\n\n/** Route below the portal page. '/' at the page root. A hash route, when present, is the route. */\nexport function currentPagePath(): string {\n const match = matchPage();\n\n if (!match) return '/';\n\n const rest = hashRoute()?.path ?? match[3];\n\n return (rest && rest !== '' && rest !== '/' ? rest : '/').slice(0, WIDTHS.page_path);\n}\n\n/** `location.search` without the `?`, joined with `&` to a hash route's own query. */\nexport function currentQuery(): string {\n const outer = window.location.search.replace(/^\\?/, '');\n const inner = hashRoute()?.query ?? '';\n\n return outer && inner ? `${outer}&${inner}` : outer || inner;\n}\n\n/** Runtime config, or null when ca-analytics must stay silent: disabled, or not on a portal page. */\nexport function resolveConfig(options: Options = {}): ResolvedConfig | null {\n const enabled = options.enabled ?? readFlag('CUSTOM_APP_ANALYTICS_ENABLED') ?? true;\n\n if (!enabled) return null;\n\n // Page internal_name: both the row's attribution key and the write endpoint's page.\n const app = (matchPage()?.[2] ?? '').slice(0, WIDTHS.app);\n\n if (!app) return null;\n\n return { app, endpoint: `/data/page/${encodeURIComponent(app)}/${ENTITY_NAME}` };\n}\n","import { VERSION, WIDTHS } from './constants';\nimport type { EventRow } from './types';\n\nexport interface RowInput {\n app: string;\n event: string;\n sessionId: string;\n pagePath: string;\n elementId?: number;\n meta?: Record<string, unknown>;\n}\n\nconst pad2 = (n: number) => String(n).padStart(2, '0');\n\nconst INT32_MIN = -(2 ** 31);\nconst INT32_MAX = 2 ** 31 - 1;\n\n/**\n * Integer only: a float or string value widens the `int` column permanently. Out-of-int32 values are 0,\n * since past 2^31-1 the column overflows and past 1e21 the number serializes as `1e+21`.\n */\nexport function toId(value: unknown): number {\n const n = Math.trunc(Number(value));\n\n return Number.isSafeInteger(n) && n >= INT32_MIN && n <= INT32_MAX ? n : 0;\n}\n\n/** Space-separated, never ISO — a `T`/`Z` sniffs as text and the column stops being `datetime`. */\nexport function formatTs(date: Date): string {\n return (\n `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())} ` +\n `${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}:${pad2(date.getUTCSeconds())}`\n );\n}\n\nfunction stringify(value: unknown): string | null {\n try {\n const out = JSON.stringify(value);\n\n return typeof out === 'string' ? out : null;\n } catch {\n return null;\n }\n}\n\n/** JSON string capped at WIDTHS.meta. Drops whole keys instead of slicing — a sliced payload is invalid JSON. */\nexport function serializeMeta(meta?: Record<string, unknown>): string {\n if (!meta) return '{}';\n\n const full = stringify(meta);\n\n if (full !== null && full.length <= WIDTHS.meta) return full;\n\n const kept: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(meta)) {\n const candidate = stringify({ ...kept, [key]: value, _truncated: 1 });\n\n if (candidate === null || candidate.length > WIDTHS.meta) continue;\n\n kept[key] = value;\n }\n\n return stringify({ ...kept, _truncated: 1 }) ?? '{}';\n}\n\nexport function uuid(): string {\n const c = typeof crypto !== 'undefined' ? crypto : undefined;\n\n if (c && typeof c.randomUUID === 'function') return c.randomUUID();\n\n // Older Safari has getRandomValues but not randomUUID.\n const bytes = new Uint8Array(16);\n\n if (c && typeof c.getRandomValues === 'function') {\n c.getRandomValues(bytes);\n } else {\n for (let i = 0; i < bytes.length; i += 1) bytes[i] = Math.floor(Math.random() * 256);\n }\n\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/** Only place a row is built. `ts`/`element_id` type mistakes here lock the shared dataset's schema permanently. */\nexport function createRow(input: RowInput): EventRow {\n return {\n id: uuid(),\n ts: formatTs(new Date()),\n app: input.app.slice(0, WIDTHS.app),\n event: input.event.slice(0, WIDTHS.event),\n session_id: input.sessionId.slice(0, 36),\n page_path: input.pagePath.slice(0, WIDTHS.page_path),\n element_id: toId(input.elementId),\n version: VERSION.slice(0, WIDTHS.version),\n meta: serializeMeta(input.meta),\n };\n}\n","export type SendOutcome = 'ok' | 'failed';\n\nconst RETRY_DELAY_MS = 2000;\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\n/** Rows arrive pre-serialized; joining as `[a,b,c]` matches JSON.stringify(rows) byte-for-byte without a second pass. */\nconst toArrayBody = (rows: string[]): string => `[${rows.join(',')}]`;\n\nasync function post(endpoint: string, body: string): Promise<{ ok: boolean; retryable: boolean }> {\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body,\n });\n\n if (!response.ok) {\n return { ok: false, retryable: response.status === 429 || response.status >= 500 };\n }\n\n const payload = (await response.json().catch(() => null)) as { resultCode?: number } | null;\n\n // 200 with a non-zero resultCode is a rejected insert, not a success.\n if (payload && typeof payload.resultCode === 'number' && payload.resultCode !== 0) {\n return { ok: false, retryable: false };\n }\n\n return { ok: true, retryable: false };\n } catch {\n return { ok: false, retryable: true };\n }\n}\n\n/** One retry max, same rows — a lost-response double-insert is caught by readers using COUNT(DISTINCT id). */\nexport async function send(endpoint: string, rows: string[]): Promise<SendOutcome> {\n if (rows.length === 0) return 'ok';\n\n const body = toArrayBody(rows);\n const first = await post(endpoint, body);\n\n if (first.ok) return 'ok';\n if (!first.retryable) return 'failed';\n\n await sleep(RETRY_DELAY_MS);\n\n return (await post(endpoint, body)).ok ? 'ok' : 'failed';\n}\n\n/** Unload path. No retry possible — the page is going away. */\nexport function sendOnUnload(endpoint: string, rows: string[]): void {\n if (rows.length === 0) return;\n\n const body = toArrayBody(rows);\n\n try {\n const blob = new Blob([body], { type: 'application/json' });\n\n if (navigator.sendBeacon && navigator.sendBeacon(endpoint, blob)) return;\n } catch {\n // fall through to keepalive fetch\n }\n\n // try/catch only sees a synchronous throw; the .catch keeps a rejected request (e.g. over the\n // keepalive quota) from surfacing as an unhandled rejection in the host app.\n try {\n fetch(endpoint, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body,\n keepalive: true,\n }).catch(() => {\n // Dropped. Nothing further possible at unload.\n });\n } catch {\n // Dropped. Nothing further possible at unload.\n }\n}\n","import { send, sendOnUnload } from './transport';\nimport type { SendOutcome } from './transport';\nimport type { EventRow } from './types';\n\n/** A row's JSON already computed once in push(); send() joins these into the array body instead of re-stringifying. */\ntype Buffered = { json: string; size: number };\n\nconst FLUSH_INTERVAL_MS = 15_000;\nconst MAX_FLUSH_ROWS = 50;\n/** Half of sendBeacon's 64 KiB cumulative quota. The server has no practical ceiling. */\nconst MAX_FLUSH_BYTES = 30_000;\nconst MAX_CONSECUTIVE_FAILURES = 3;\n\n// Counts encoded UTF-8 bytes, not UTF-16 code units — what sendBeacon actually charges against its quota.\nconst textEncoder = new TextEncoder();\n\nexport interface Queue {\n push(row: EventRow): void;\n flush(): void;\n flushOnUnload(): void;\n stop(): void;\n}\n\n/** Splits rows into batches whose `[a,b,c]` body stays within maxBytes. A lone row over the limit still goes, alone. */\nfunction chunkBySize(rows: Buffered[], maxBytes: number): Buffered[][] {\n const chunks: Buffered[][] = [];\n let current: Buffered[] = [];\n let size = 2;\n\n for (const row of rows) {\n if (current.length > 0 && size + 1 + row.size > maxBytes) {\n chunks.push(current);\n current = [];\n size = 2;\n }\n\n size += (current.length > 0 ? 1 : 0) + row.size;\n current.push(row);\n }\n\n if (current.length > 0) chunks.push(current);\n\n return chunks;\n}\n\nexport function createQueue(endpoint: string, onKilled?: () => void): Queue {\n let buffer: Buffered[] = [];\n let bytes = 0;\n let failures = 0;\n let stopped = false;\n let inFlight = false;\n // Guards re-entrancy: onKilled calls shutdown() -> stop() again, but the kill must fire once.\n let killed = false;\n\n const timer = setInterval(() => {\n void flush();\n }, FLUSH_INTERVAL_MS);\n\n function stop(): void {\n stopped = true;\n buffer = [];\n bytes = 0;\n clearInterval(timer);\n }\n\n async function flush(): Promise<void> {\n if (stopped || inFlight || buffer.length === 0) return;\n\n const batch = buffer;\n\n buffer = [];\n bytes = 0;\n inFlight = true;\n\n let outcome: SendOutcome = 'failed';\n\n try {\n\n // send() isn't expected to reject, but a rejection must not become an unhandled promise rejection.\n try {\n outcome = await send(\n endpoint,\n batch.map((b) => b.json),\n );\n } catch {\n outcome = 'failed';\n }\n\n // Stopped while the request was out: a stale result must not count toward, or trip, the kill-switch.\n if (stopped) return;\n\n if (outcome === 'ok') {\n failures = 0;\n return;\n }\n\n failures += 1;\n\n // Batch dropped, not re-buffered: transport already retried once, and re-sending a batch whose\n // response was merely lost is what creates duplicates.\n if (failures >= MAX_CONSECUTIVE_FAILURES && !killed) {\n killed = true;\n stop();\n\n // Hand the kill up to the owner so it can stop the timers/listeners still producing rows.\n try {\n onKilled?.();\n } catch {\n // swallowed — a host callback must never escape\n }\n }\n } finally {\n inFlight = false;\n\n // push() only flushes on its own threshold check, which is a no-op while a request is out; rows\n // that piled up meanwhile would otherwise wait for the timer and could outgrow the beacon quota.\n // Only after a success: re-flushing a failure straight away would spend the kill-switch's three\n // strikes within seconds of a brief outage, instead of across timer ticks.\n if (!stopped && outcome === 'ok' && (buffer.length >= MAX_FLUSH_ROWS || bytes >= MAX_FLUSH_BYTES)) void flush();\n }\n }\n\n return {\n push(row: EventRow): void {\n if (stopped) return;\n\n let json: string;\n\n try {\n json = JSON.stringify(row);\n } catch {\n return;\n }\n\n const size = textEncoder.encode(json).length;\n\n buffer.push({ json, size });\n bytes += size;\n\n if (buffer.length >= MAX_FLUSH_ROWS || bytes >= MAX_FLUSH_BYTES) void flush();\n },\n flush(): void {\n void flush();\n },\n flushOnUnload(): void {\n if (stopped || buffer.length === 0) return;\n\n const batch = buffer;\n\n buffer = [];\n bytes = 0;\n\n // One body over the 64 KiB quota fails in both sendBeacon and keepalive fetch and drops every row;\n // flush-sized chunks let at least the ones that fit through.\n for (const chunk of chunkBySize(batch, MAX_FLUSH_BYTES)) {\n sendOnUnload(\n endpoint,\n chunk.map((b) => b.json),\n );\n }\n },\n stop,\n };\n}\n","const SID_KEY = 'ca_pa_sid';\nconst LAST_KEY = 'ca_pa_last';\nconst IDLE_MS = 30 * 60 * 1000;\nconst TOUCH_THROTTLE_MS = 5000;\n\nlet memoryId: string | null = null;\nlet lastWrittenAt = 0;\n\n/** Safari private mode and blocked-storage policies throw on access, not just on write. */\nfunction read(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction write(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // In-memory session for the page lifetime beats no tracking.\n }\n}\n\nfunction newId(): string {\n try {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID();\n } catch {}\n\n return `s-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/** Clears in-memory state only. Storage stays, so a reload still finds the session. */\nexport function resetSession(): void {\n memoryId = null;\n lastWrittenAt = 0;\n}\n\n/** Session id, minting a new one after 30 min idle. Extends the window on every call. */\nexport function getSessionId(): string {\n const now = Date.now();\n const storedId = read(SID_KEY);\n const rawLast = read(LAST_KEY);\n const last = rawLast === null ? Number.NaN : Number(rawLast);\n const alive = Number.isFinite(last) && now - last <= IDLE_MS;\n\n let id: string;\n\n if (storedId && alive) {\n // Trusting storage here is what survives a reload.\n id = storedId;\n } else if (!storedId && memoryId) {\n // Blocked localStorage always reads null; reusing the memory id is the only stable fallback.\n id = memoryId;\n } else {\n id = newId();\n write(SID_KEY, id);\n lastWrittenAt = 0;\n }\n\n memoryId = id;\n\n if (now - lastWrittenAt >= TOUCH_THROTTLE_MS) {\n write(LAST_KEY, String(now));\n lastWrittenAt = now;\n }\n\n return id;\n}\n","export const HEARTBEAT_FAST_MS = 60_000;\nexport const HEARTBEAT_SLOW_MS = 300_000;\nexport const HEARTBEAT_FAST_COUNT = 5;\n\n/** Tapered, visible-only — readers derive engaged time as beats x interval, so a hidden beat would count idle time. */\nexport function startHeartbeat(emit: () => void): { stop(): void } {\n let beats = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let stopped = false;\n\n const interval = () => (beats < HEARTBEAT_FAST_COUNT ? HEARTBEAT_FAST_MS : HEARTBEAT_SLOW_MS);\n\n function schedule(): void {\n if (stopped || timer !== undefined) return;\n\n timer = setTimeout(() => {\n timer = undefined;\n\n if (stopped || document.visibilityState !== 'visible') return;\n\n beats += 1;\n\n try {\n emit();\n } catch {\n // A failed emit must not stop the timer.\n }\n\n schedule();\n }, interval());\n }\n\n function clear(): void {\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n }\n\n const onVisibilityChange = () => {\n if (stopped) return;\n\n if (document.visibilityState === 'visible') {\n schedule();\n } else {\n // Discard the partial interval rather than credit time the user was away.\n clear();\n }\n };\n\n document.addEventListener('visibilitychange', onVisibilityChange);\n\n if (document.visibilityState === 'visible') schedule();\n\n return {\n stop(): void {\n stopped = true;\n clear();\n document.removeEventListener('visibilitychange', onVisibilityChange);\n },\n };\n}\n","import { toId } from '../event';\n\nexport const RENDER_CAP = 50;\n/** Distinct cap buckets per page load. Past this every render of a component shares one bucket, so per-render props can't leak the map. */\nconst MAX_KEYS = 200;\n/** `visibility.preview.control_buttons.lineage` is 4 levels, the deepest shape MI documents. */\nconst PROP_DEPTH = 4;\n/** A `viewer` render is 38 leaves / 1672 chars, so this must not bind before serializeMeta's own cap does. */\nconst PROP_KEYS = 40;\nconst PROP_STRING = 40;\n\n/** Already read into element_id/segment_id above; re-emitting them as props is noise. */\nconst IDENTITY_KEYS = new Set(['element', 'element_id', 'segment', 'segment_id', 'segment_value_id']);\n\nconst SKIP = Symbol('skip');\n\nexport interface RenderPayload {\n elementId: number;\n meta: Record<string, unknown>;\n}\n\nfunction leafValue(value: unknown): unknown {\n if (value === null) return null;\n\n switch (typeof value) {\n case 'boolean':\n return value;\n case 'number':\n return Number.isFinite(value) ? value : SKIP;\n case 'string':\n return value.slice(0, PROP_STRING);\n default:\n return SKIP;\n }\n}\n\n/** Dotted paths, flat output: the dashboard stringifies a nested object into one opaque value, so its per-key distribution would be noise. Returns true when a cap dropped something. */\nfunction flattenProps(source: Record<string, unknown>, out: Record<string, unknown>, prefix: string, depth: number): boolean {\n let dropped = false;\n\n for (const [key, value] of Object.entries(source)) {\n if (Object.keys(out).length >= PROP_KEYS) return true;\n if (depth === 0 && IDENTITY_KEYS.has(key)) continue;\n\n const path = prefix === '' ? key : `${prefix}.${key}`;\n\n // Summarized, not expanded — index-keyed paths would make every array length its own set of keys.\n if (Array.isArray(value)) {\n out[`prop.${path}`] = `arr:${value.length}`;\n continue;\n }\n\n if (value !== null && typeof value === 'object') {\n if (depth + 1 < PROP_DEPTH) dropped = flattenProps(value as Record<string, unknown>, out, path, depth + 1) || dropped;\n else dropped = true;\n\n continue;\n }\n\n const leaf = leafValue(value);\n\n if (leaf !== SKIP) out[`prop.${path}`] = leaf;\n }\n\n return dropped;\n}\n\n/** FNV-1a. Keeps the bucket key short no matter how large the props are. */\nfunction hash(text: string): string {\n let h = 0x811c9dc5;\n\n for (let i = 0; i < text.length; i += 1) {\n h ^= text.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n\n return (h >>> 0).toString(36);\n}\n\n/** Listens on `document` only: `MiNamespace.render()` dispatches a bubbling CustomEvent, so load order doesn't matter. */\nexport function startMiRender(emit: (payload: RenderPayload) => void): { stop(): void } {\n let stopped = false;\n const counts = new Map<string, number>();\n\n const onRender = (event: Event) => {\n if (stopped) return;\n\n try {\n const detail = (event as CustomEvent).detail as { component?: unknown; props?: unknown } | undefined;\n\n if (!detail || typeof detail !== 'object') return;\n\n const component = typeof detail.component === 'string' ? detail.component : '';\n\n if (!component) return;\n\n const props = (detail.props && typeof detail.props === 'object' ? detail.props : {}) as Record<string, unknown>;\n // Three spellings in the wild: preview sends {element, segment}, viewer {element_id, segment_id}, access_denied_popup {element_id, segment_value_id}.\n const elementId = toId(props.element ?? props.element_id ?? 0);\n const segmentId = toId(props.segment ?? props.segment_id ?? props.segment_value_id ?? 0);\n\n const flat: Record<string, unknown> = {};\n const propsDropped = flattenProps(props, flat, '', 0);\n\n // Props are in the key: a component with no element (a popup) is otherwise one bucket for every variant it renders.\n const bucket = `${component}|${elementId}|${segmentId}|${hash(JSON.stringify(flat))}`;\n const key = counts.has(bucket) || counts.size < MAX_KEYS ? bucket : `${component}|*`;\n const seq = (counts.get(key) ?? 0) + 1;\n\n // Some apps re-render on every filter change; uncapped, one page burns the throttle budget.\n if (seq > RENDER_CAP) return;\n\n counts.set(key, seq);\n\n // Reserved keys first: serializeMeta drops trailing keys at the 2000-char cap, so props go before identity does.\n const meta: Record<string, unknown> = {\n component,\n element_id: elementId,\n segment_id: segmentId,\n ...flat,\n };\n\n if (propsDropped) meta.props_dropped = 1;\n if (seq === RENDER_CAP) meta.capped = 1;\n\n emit({ elementId, meta });\n } catch {\n // Never throw into the host render path.\n }\n };\n\n document.addEventListener('mi-render', onRender);\n\n return {\n stop(): void {\n stopped = true;\n document.removeEventListener('mi-render', onRender);\n counts.clear();\n },\n };\n}\n","import { isHashRoute } from '../config';\n\nexport interface PageViewSignal {\n stop(): void;\n emitNow(path?: string): void;\n}\n\n/** Absolute for an inbound hop, bare route after — a route in another app is not comparable here. */\nconst inboundReferrer = (): string => {\n try {\n const raw = document.referrer;\n\n if (!raw) return '';\n\n const url = new URL(raw);\n\n return `${url.origin}${url.pathname}`;\n } catch {\n return '';\n }\n};\n\n/** Emits once on start, then on every history or hash navigation. The original history method always still runs. */\nexport function startPageView(\n emit: (path: string, referrer: string) => void,\n currentPath: () => string,\n): PageViewSignal {\n let stopped = false;\n let lastPath: string | null = null;\n let lastLocation: string | null = null;\n\n // Dedupe key: path alone can't tell a `?tab=` change from a no-op replaceState. A hash route\n // (`#/reports?tab=1`) is part of it; a plain in-page anchor (`#section`) is not a new view.\n const locationKey = (path: string): string => {\n try {\n const { search, hash } = window.location;\n\n return `${path}${search}${isHashRoute(hash) ? hash : ''}`;\n } catch {\n return path;\n }\n };\n\n // resolvePath is a thunk so a throwing host currentPath() is caught here too, not in the caller.\n // dedupe is off for emitNow(): an explicit trackPageView() must always send.\n const safeEmit = (resolvePath: () => string, dedupe: boolean) => {\n if (stopped) return;\n\n try {\n const path = resolvePath();\n const location = locationKey(path);\n\n // Routers replaceState for scroll and filter sync without moving. Not a view.\n if (dedupe && location === lastLocation) return;\n\n // GA4 semantics: referrer is the previous page_view's path, not document.referrer, except first.\n const referrer = lastPath === null ? inboundReferrer() : lastPath;\n\n lastPath = path;\n lastLocation = location;\n emit(path, referrer);\n } catch {\n // swallowed\n }\n };\n\n const originalPush = window.history.pushState;\n const originalReplace = window.history.replaceState;\n\n const patch = (original: typeof window.history.pushState) =>\n function patched(this: History, ...args: Parameters<typeof window.history.pushState>) {\n const result = original.apply(this, args);\n\n safeEmit(currentPath, true);\n\n return result;\n };\n\n const patchedPush = patch(originalPush);\n const patchedReplace = patch(originalReplace);\n\n window.history.pushState = patchedPush;\n window.history.replaceState = patchedReplace;\n\n const onPopState = () => safeEmit(currentPath, true);\n\n window.addEventListener('popstate', onPopState);\n // Hash routers and `location.hash = …` navigate without pushState; dedupe drops the popstate twin.\n window.addEventListener('hashchange', onPopState);\n\n safeEmit(currentPath, true);\n\n return {\n stop(): void {\n stopped = true;\n lastPath = null;\n lastLocation = null;\n\n // Restore only if this signal still owns the patch — another startPageView may have patched over it.\n if (window.history.pushState === patchedPush) {\n window.history.pushState = originalPush;\n }\n\n if (window.history.replaceState === patchedReplace) {\n window.history.replaceState = originalReplace;\n }\n\n window.removeEventListener('popstate', onPopState);\n window.removeEventListener('hashchange', onPopState);\n },\n emitNow(path?: string): void {\n safeEmit(() => path ?? currentPath(), false);\n },\n };\n}\n","import { EVENT_NAME_RE, LIMITS, RESERVED_EVENTS } from './constants';\nimport { currentPagePath, currentQuery, resolveConfig } from './config';\nimport { createRow, toId } from './event';\nimport { createQueue, type Queue } from './queue';\nimport { getSessionId, resetSession } from './session';\nimport { startHeartbeat } from './signals/heartbeat';\nimport { startMiRender } from './signals/miRender';\nimport { startPageView, type PageViewSignal } from './signals/pageview';\nimport type { Options } from './types';\n\ntype Emit = (\n event: string,\n extra?: { pagePath?: string; elementId?: number; meta?: Record<string, unknown> },\n) => void;\n\ninterface Runtime {\n queue: Queue;\n emit: Emit;\n detachUnload: () => void;\n pageView: PageViewSignal;\n stopHeartbeat: () => void;\n stopMiRender: () => void;\n}\n\nlet runtime: Runtime | null = null;\nconst warnedOnce = new Set<string>();\n\n/** Nothing in ca-analytics may throw into the host app. */\nfunction guard(fn: () => void): void {\n try {\n fn();\n } catch {\n // swallowed\n }\n}\n\n/** Warns about caller misuse only the first time `key` is seen. */\nfunction warnOnce(key: string, message: string): void {\n if (warnedOnce.has(key)) return;\n\n warnedOnce.add(key);\n console.warn(`[ca-analytics] ${message}`);\n}\n\nfunction pageViewMeta(referrer: string): Record<string, unknown> {\n const query = currentQuery();\n\n return { referrer: referrer.slice(0, LIMITS.referrer), query: query.slice(0, LIMITS.query) };\n}\n\n/** Stops whatever of init()'s parts already started, used both for teardown and a failed startup. */\nfunction stopStarted(\n parts: {\n queue: Queue;\n detachUnload: () => void;\n pageView?: PageViewSignal;\n stopHeartbeat?: () => void;\n stopMiRender?: () => void;\n },\n flushFirst: boolean,\n): void {\n if (flushFirst) guard(() => parts.queue.flushOnUnload());\n\n guard(() => parts.pageView?.stop());\n guard(() => parts.stopHeartbeat?.());\n guard(() => parts.stopMiRender?.());\n guard(() => parts.detachUnload());\n guard(() => parts.queue.stop());\n}\n\n/** Unwinds everything `init()` started; `flushFirst` also drains the buffer via the unload path. `runtime` is cleared first so a re-entrant call is a no-op. */\nfunction teardown(flushFirst: boolean): void {\n const current = runtime;\n\n if (!current) return;\n\n runtime = null;\n warnedOnce.clear();\n\n stopStarted(current, flushFirst);\n resetSession();\n}\n\n/** Idempotent. A second call while active is ignored rather than doubling listeners. */\nexport function init(options: Options = {}): void {\n guard(() => {\n if (runtime) return;\n\n const config = resolveConfig(options);\n\n if (!config) return;\n\n // A tripped kill-switch tears down ca-analytics entirely, not just the queue — but only while this\n // queue's runtime is still the live one, so a stale queue can't kill a later init().\n const queue: Queue = createQueue(config.endpoint, () => {\n if (runtime?.queue === queue) shutdown();\n });\n\n const emit: Emit = (event, extra = {}) => {\n queue.push(\n createRow({\n app: config.app,\n event,\n sessionId: getSessionId(),\n pagePath: extra.pagePath ?? currentPagePath(),\n elementId: extra.elementId,\n meta: extra.meta,\n }),\n );\n };\n\n const onUnload = () => guard(() => queue.flushOnUnload());\n // pagehide alone is unreliable on iOS; visibilitychange is what fires there.\n const onVisibility = () => {\n if (document.visibilityState === 'hidden') onUnload();\n };\n const detachUnload = () => {\n window.removeEventListener('pagehide', onUnload);\n document.removeEventListener('visibilitychange', onVisibility);\n };\n\n let pageView: PageViewSignal | undefined;\n let stopHeartbeat: (() => void) | undefined;\n let stopMiRender: (() => void) | undefined;\n\n // A later step throwing must unwind what already started, or its timers/listeners leak.\n try {\n window.addEventListener('pagehide', onUnload);\n document.addEventListener('visibilitychange', onVisibility);\n\n stopHeartbeat = startHeartbeat(() => emit('heartbeat')).stop;\n stopMiRender = startMiRender((payload) =>\n emit('component_render', { elementId: payload.elementId, meta: payload.meta }),\n ).stop;\n pageView = startPageView(\n (path, referrer) => emit('page_view', { pagePath: path, meta: pageViewMeta(referrer) }),\n currentPagePath,\n );\n } catch (error) {\n stopStarted({ queue, detachUnload, pageView, stopHeartbeat, stopMiRender }, false);\n throw error;\n }\n\n runtime = { queue, emit, detachUnload, pageView, stopHeartbeat, stopMiRender };\n });\n}\n\n/** `props` is any object, interface-typed included; it is serialized like JSON.stringify (a Date becomes its ISO string). */\nexport function track(event: string, props?: object): void {\n guard(() => {\n if (!runtime) return;\n if (!event) return;\n\n const normalized = event.toLowerCase();\n\n if ((RESERVED_EVENTS as readonly string[]).includes(normalized)) {\n warnOnce('reserved', `\"${event}\" is reserved and was not tracked (further occurrences are silent)`);\n\n return;\n }\n\n if (!EVENT_NAME_RE.test(event)) {\n warnOnce('name', `\"${event}\" is not snake_case ≤100 chars; tracked as-is (further occurrences are silent)`);\n }\n\n runtime.emit(event, { meta: props as Record<string, unknown> | undefined });\n });\n}\n\n/**\n * `element_id`/`segment_id` resolve identity via `/api/element_info`; everything else is free-form. Generic so\n * interface-typed props with extra keys are accepted — an index signature would reject them.\n */\nexport function trackElement<T extends { element_id: number; segment_id?: number }>(props: T): void {\n guard(() => {\n if (!runtime) return;\n\n const { element_id, segment_id, ...extra } = props ?? {};\n const id = toId(element_id);\n\n if (!id) {\n warnOnce('element', `trackElement() ignored a zero, non-finite or out-of-range element_id (${element_id})`);\n\n return;\n }\n\n const segmentRaw = Number(segment_id);\n const segmentFinite = Number.isFinite(segmentRaw);\n\n // Omitting segment_id is a supported call; only a supplied-but-bad value is worth a warning.\n if (segment_id !== undefined && !segmentFinite) {\n warnOnce('segment', `trackElement() got a non-finite segment_id (${segment_id}); recorded as 0`);\n }\n\n // Reserved keys last: a caller's own element_id/segment_id in extra must not win the spread.\n const meta: Record<string, unknown> = {\n ...extra,\n element_id: id,\n segment_id: segmentFinite ? Math.trunc(segmentRaw) : 0,\n };\n\n runtime.emit('element_click', { elementId: id, meta });\n });\n}\n\n/** For routers whose navigation the history patch cannot observe. */\nexport function trackPageView(path?: string): void {\n guard(() => runtime?.pageView.emitNow(path));\n}\n\n/** Flushes what is buffered before stopping — up to 15s of events would otherwise drop. */\nexport function shutdown(): void {\n guard(() => teardown(true));\n}\n\nexport function isActive(): boolean {\n return runtime !== null;\n}\n","import { track, trackElement, trackPageView } from '../core/analytics';\n\n/** Thin wrapper over the module singleton. No provider, no context — ca-analytics renders nothing. */\nconst api = { track, trackElement, trackPageView } as const;\n\nexport function useAnalytics(): typeof api {\n return api;\n}\n"],"mappings":"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","names":["hash","hash"]}
package/dist/index.d.cts CHANGED
@@ -1,3 +1,4 @@
1
+ /** No longer used by the tracking signatures, which accept any value; still exported so existing imports keep compiling. */
1
2
  type JsonValue = string | number | boolean | null | undefined | JsonValue[] | {
2
3
  [key: string]: JsonValue;
3
4
  };
@@ -20,13 +21,16 @@ interface EventRow {
20
21
 
21
22
  /** Idempotent. A second call while active is ignored rather than doubling listeners. */
22
23
  declare function init(options?: Options): void;
23
- declare function track(event: string, props?: Record<string, JsonValue>): void;
24
- /** `element_id`/`segment_id` resolve identity via `/api/element_info`; everything else is free-form. */
25
- declare function trackElement(props: {
24
+ /** `props` is any object, interface-typed included; it is serialized like JSON.stringify (a Date becomes its ISO string). */
25
+ declare function track(event: string, props?: object): void;
26
+ /**
27
+ * `element_id`/`segment_id` resolve identity via `/api/element_info`; everything else is free-form. Generic so
28
+ * interface-typed props with extra keys are accepted — an index signature would reject them.
29
+ */
30
+ declare function trackElement<T extends {
26
31
  element_id: number;
27
32
  segment_id?: number;
28
- [key: string]: JsonValue;
29
- }): void;
33
+ }>(props: T): void;
30
34
  /** For routers whose navigation the history patch cannot observe. */
31
35
  declare function trackPageView(path?: string): void;
32
36
  /** Flushes what is buffered before stopping — up to 15s of events would otherwise drop. */
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /** No longer used by the tracking signatures, which accept any value; still exported so existing imports keep compiling. */
1
2
  type JsonValue = string | number | boolean | null | undefined | JsonValue[] | {
2
3
  [key: string]: JsonValue;
3
4
  };
@@ -20,13 +21,16 @@ interface EventRow {
20
21
 
21
22
  /** Idempotent. A second call while active is ignored rather than doubling listeners. */
22
23
  declare function init(options?: Options): void;
23
- declare function track(event: string, props?: Record<string, JsonValue>): void;
24
- /** `element_id`/`segment_id` resolve identity via `/api/element_info`; everything else is free-form. */
25
- declare function trackElement(props: {
24
+ /** `props` is any object, interface-typed included; it is serialized like JSON.stringify (a Date becomes its ISO string). */
25
+ declare function track(event: string, props?: object): void;
26
+ /**
27
+ * `element_id`/`segment_id` resolve identity via `/api/element_info`; everything else is free-form. Generic so
28
+ * interface-typed props with extra keys are accepted — an index signature would reject them.
29
+ */
30
+ declare function trackElement<T extends {
26
31
  element_id: number;
27
32
  segment_id?: number;
28
- [key: string]: JsonValue;
29
- }): void;
33
+ }>(props: T): void;
30
34
  /** For routers whose navigation the history patch cannot observe. */
31
35
  declare function trackPageView(path?: string): void;
32
36
  /** Flushes what is buffered before stopping — up to 15s of events would otherwise drop. */
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.2.0";
3
+ var VERSION = "0.3.0";
4
4
  var MI_PAGE_PATH_RE = /^\/(p[tl]?)\/([^/]+)(.*)$/;
5
5
  var WIDTHS = {
6
6
  app: 100,
@@ -32,12 +32,29 @@ function matchPage() {
32
32
  if (typeof window === "undefined" || !window.location) return null;
33
33
  return MI_PAGE_PATH_RE.exec(window.location.pathname);
34
34
  }
35
+ function isHashRoute(hash2) {
36
+ return hash2.startsWith("#/");
37
+ }
38
+ function hashRoute() {
39
+ const hash2 = window.location.hash;
40
+ if (!isHashRoute(hash2)) return null;
41
+ const route = hash2.slice(1);
42
+ const pathEnd = route.search(/[?#]/);
43
+ const path = pathEnd === -1 ? route : route.slice(0, pathEnd);
44
+ const queryMatch = /\?([^#]*)/.exec(route);
45
+ return { path, query: queryMatch ? queryMatch[1] : "" };
46
+ }
35
47
  function currentPagePath() {
36
48
  const match = matchPage();
37
49
  if (!match) return "/";
38
- const rest = match[3];
50
+ const rest = hashRoute()?.path ?? match[3];
39
51
  return (rest && rest !== "" && rest !== "/" ? rest : "/").slice(0, WIDTHS.page_path);
40
52
  }
53
+ function currentQuery() {
54
+ const outer = window.location.search.replace(/^\?/, "");
55
+ const inner = hashRoute()?.query ?? "";
56
+ return outer && inner ? `${outer}&${inner}` : outer || inner;
57
+ }
41
58
  function resolveConfig(options = {}) {
42
59
  const enabled = options.enabled ?? readFlag("CUSTOM_APP_ANALYTICS_ENABLED") ?? true;
43
60
  if (!enabled) return null;
@@ -48,9 +65,11 @@ function resolveConfig(options = {}) {
48
65
 
49
66
  // src/core/event.ts
50
67
  var pad2 = (n) => String(n).padStart(2, "0");
68
+ var INT32_MIN = -(2 ** 31);
69
+ var INT32_MAX = 2 ** 31 - 1;
51
70
  function toId(value) {
52
- const n = Number(value);
53
- return Number.isFinite(n) ? Math.trunc(n) : 0;
71
+ const n = Math.trunc(Number(value));
72
+ return Number.isSafeInteger(n) && n >= INT32_MIN && n <= INT32_MAX ? n : 0;
54
73
  }
55
74
  function formatTs(date) {
56
75
  return `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())} ${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}:${pad2(date.getUTCSeconds())}`;
@@ -145,12 +164,13 @@ function sendOnUnload(endpoint, rows) {
145
164
  } catch {
146
165
  }
147
166
  try {
148
- void fetch(endpoint, {
167
+ fetch(endpoint, {
149
168
  method: "POST",
150
169
  credentials: "same-origin",
151
170
  headers: { "Content-Type": "application/json" },
152
171
  body,
153
172
  keepalive: true
173
+ }).catch(() => {
154
174
  });
155
175
  } catch {
156
176
  }
@@ -162,6 +182,22 @@ var MAX_FLUSH_ROWS = 50;
162
182
  var MAX_FLUSH_BYTES = 3e4;
163
183
  var MAX_CONSECUTIVE_FAILURES = 3;
164
184
  var textEncoder = new TextEncoder();
185
+ function chunkBySize(rows, maxBytes) {
186
+ const chunks = [];
187
+ let current = [];
188
+ let size = 2;
189
+ for (const row of rows) {
190
+ if (current.length > 0 && size + 1 + row.size > maxBytes) {
191
+ chunks.push(current);
192
+ current = [];
193
+ size = 2;
194
+ }
195
+ size += (current.length > 0 ? 1 : 0) + row.size;
196
+ current.push(row);
197
+ }
198
+ if (current.length > 0) chunks.push(current);
199
+ return chunks;
200
+ }
165
201
  function createQueue(endpoint, onKilled) {
166
202
  let buffer = [];
167
203
  let bytes = 0;
@@ -184,8 +220,8 @@ function createQueue(endpoint, onKilled) {
184
220
  buffer = [];
185
221
  bytes = 0;
186
222
  inFlight = true;
223
+ let outcome = "failed";
187
224
  try {
188
- let outcome;
189
225
  try {
190
226
  outcome = await send(
191
227
  endpoint,
@@ -194,6 +230,7 @@ function createQueue(endpoint, onKilled) {
194
230
  } catch {
195
231
  outcome = "failed";
196
232
  }
233
+ if (stopped) return;
197
234
  if (outcome === "ok") {
198
235
  failures = 0;
199
236
  return;
@@ -209,6 +246,7 @@ function createQueue(endpoint, onKilled) {
209
246
  }
210
247
  } finally {
211
248
  inFlight = false;
249
+ if (!stopped && outcome === "ok" && (buffer.length >= MAX_FLUSH_ROWS || bytes >= MAX_FLUSH_BYTES)) void flush();
212
250
  }
213
251
  }
214
252
  return {
@@ -233,10 +271,12 @@ function createQueue(endpoint, onKilled) {
233
271
  const batch = buffer;
234
272
  buffer = [];
235
273
  bytes = 0;
236
- sendOnUnload(
237
- endpoint,
238
- batch.map((b) => b.json)
239
- );
274
+ for (const chunk of chunkBySize(batch, MAX_FLUSH_BYTES)) {
275
+ sendOnUnload(
276
+ endpoint,
277
+ chunk.map((b) => b.json)
278
+ );
279
+ }
240
280
  },
241
281
  stop
242
282
  };
@@ -452,7 +492,8 @@ function startPageView(emit, currentPath) {
452
492
  let lastLocation = null;
453
493
  const locationKey = (path) => {
454
494
  try {
455
- return `${path}${window.location.search}`;
495
+ const { search, hash: hash2 } = window.location;
496
+ return `${path}${search}${isHashRoute(hash2) ? hash2 : ""}`;
456
497
  } catch {
457
498
  return path;
458
499
  }
@@ -483,6 +524,7 @@ function startPageView(emit, currentPath) {
483
524
  window.history.replaceState = patchedReplace;
484
525
  const onPopState = () => safeEmit(currentPath, true);
485
526
  window.addEventListener("popstate", onPopState);
527
+ window.addEventListener("hashchange", onPopState);
486
528
  safeEmit(currentPath, true);
487
529
  return {
488
530
  stop() {
@@ -496,6 +538,7 @@ function startPageView(emit, currentPath) {
496
538
  window.history.replaceState = originalReplace;
497
539
  }
498
540
  window.removeEventListener("popstate", onPopState);
541
+ window.removeEventListener("hashchange", onPopState);
499
542
  },
500
543
  emitNow(path) {
501
544
  safeEmit(() => path ?? currentPath(), false);
@@ -518,7 +561,7 @@ function warnOnce(key, message) {
518
561
  console.warn(`[ca-analytics] ${message}`);
519
562
  }
520
563
  function pageViewMeta(referrer) {
521
- const query = window.location.search.replace(/^\?/, "");
564
+ const query = currentQuery();
522
565
  return { referrer: referrer.slice(0, LIMITS.referrer), query: query.slice(0, LIMITS.query) };
523
566
  }
524
567
  function stopStarted(parts, flushFirst) {
@@ -542,7 +585,9 @@ function init(options = {}) {
542
585
  if (runtime) return;
543
586
  const config = resolveConfig(options);
544
587
  if (!config) return;
545
- const queue = createQueue(config.endpoint, () => shutdown());
588
+ const queue = createQueue(config.endpoint, () => {
589
+ if (runtime?.queue === queue) shutdown();
590
+ });
546
591
  const emit = (event, extra = {}) => {
547
592
  queue.push(
548
593
  createRow({
@@ -605,7 +650,7 @@ function trackElement(props) {
605
650
  const { element_id, segment_id, ...extra } = props ?? {};
606
651
  const id = toId(element_id);
607
652
  if (!id) {
608
- warnOnce("element", `trackElement() ignored a non-finite or zero element_id (${element_id})`);
653
+ warnOnce("element", `trackElement() ignored a zero, non-finite or out-of-range element_id (${element_id})`);
609
654
  return;
610
655
  }
611
656
  const segmentRaw = Number(segment_id);
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 // 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":[]}
1
+ {"version":3,"sources":["../src/core/constants.ts","../src/core/config.ts","../src/core/event.ts","../src/core/transport.ts","../src/core/queue.ts","../src/core/session.ts","../src/core/signals/heartbeat.ts","../src/core/signals/miRender.ts","../src/core/signals/pageview.ts","../src/core/analytics.ts","../src/react/useAnalytics.ts"],"sourcesContent":["/** Entity name bound on every tracked app's portal page. Not configurable. */\nexport const ENTITY_NAME = 'analytics_events';\n\n/** From package.json at build time. */\nexport const VERSION = __VERSION__;\n\n/** MI serves portal pages at /p/<name>, /pl/<name> or /pt/<name>. */\nexport const MI_PAGE_PATH_RE = /^\\/(p[tl]?)\\/([^/]+)(.*)$/;\n\n/** Per-field payload caps that keep a batch within sendBeacon's 64 KiB quota, not schema limits. */\nexport const WIDTHS = {\n app: 100,\n event: 100,\n page_path: 400,\n version: 20,\n meta: 2000,\n} as const;\n\n/** Emitted by ca-analytics. track() rejects them so app code cannot forge them. */\nexport const RESERVED_EVENTS = ['page_view', 'heartbeat', 'component_render', 'element_click'] as const;\n\n/** Custom event names must match this or track() warns once. Silent drift creates permanent duplicate series. */\nexport const EVENT_NAME_RE = /^[a-z][a-z0-9_]{0,99}$/;\n\n/** Per-key meta caps, applied before serializeMeta so one long value can't push another key out. */\nexport const LIMITS = { referrer: 300, query: 300 } as const;\n","import { ENTITY_NAME, MI_PAGE_PATH_RE, WIDTHS } from './constants';\nimport type { Options, ResolvedConfig } from './types';\n\n/** Unsubstituted PP variable, e.g. '[Custom App Analytics Enabled]'. Treated as unset. */\nconst UNSUBSTITUTED_RE = /^\\[[A-Za-z0-9][A-Za-z0-9 _-]*\\]$/;\n\nfunction portalVariables(): Record<string, unknown> {\n if (typeof window === 'undefined') return {};\n\n const block = (window as unknown as Record<string, unknown>).PP_VARIABLES;\n\n return block && typeof block === 'object' && !Array.isArray(block) ? (block as Record<string, unknown>) : {};\n}\n\n/** PP variables arrive as strings, so '0' and 'false' must not read truthy. */\nfunction readFlag(key: string): boolean | undefined {\n const raw = portalVariables()[key];\n\n if (typeof raw === 'boolean') return raw;\n if (typeof raw !== 'string') return undefined;\n\n const value = raw.trim().toLowerCase();\n\n if (value === '' || UNSUBSTITUTED_RE.test(raw.trim())) return undefined;\n\n return value !== '0' && value !== 'false' && value !== 'n' && value !== 'no';\n}\n\nfunction matchPage(): RegExpExecArray | null {\n if (typeof window === 'undefined' || !window.location) return null;\n\n return MI_PAGE_PATH_RE.exec(window.location.pathname);\n}\n\n/** A hash router's fragment (`#/reports`). A plain in-page anchor (`#section`) is not a route. */\nexport function isHashRoute(hash: string): boolean {\n return hash.startsWith('#/');\n}\n\n/** Path and query a hash router keeps in the fragment: `#/reports?tab=1` → `/reports`, `tab=1`. Null without one. */\nfunction hashRoute(): { path: string; query: string } | null {\n const hash = window.location.hash;\n\n if (!isHashRoute(hash)) return null;\n\n const route = hash.slice(1);\n const pathEnd = route.search(/[?#]/);\n const path = pathEnd === -1 ? route : route.slice(0, pathEnd);\n const queryMatch = /\\?([^#]*)/.exec(route);\n\n return { path, query: queryMatch ? queryMatch[1] : '' };\n}\n\n/** Route below the portal page. '/' at the page root. A hash route, when present, is the route. */\nexport function currentPagePath(): string {\n const match = matchPage();\n\n if (!match) return '/';\n\n const rest = hashRoute()?.path ?? match[3];\n\n return (rest && rest !== '' && rest !== '/' ? rest : '/').slice(0, WIDTHS.page_path);\n}\n\n/** `location.search` without the `?`, joined with `&` to a hash route's own query. */\nexport function currentQuery(): string {\n const outer = window.location.search.replace(/^\\?/, '');\n const inner = hashRoute()?.query ?? '';\n\n return outer && inner ? `${outer}&${inner}` : outer || inner;\n}\n\n/** Runtime config, or null when ca-analytics must stay silent: disabled, or not on a portal page. */\nexport function resolveConfig(options: Options = {}): ResolvedConfig | null {\n const enabled = options.enabled ?? readFlag('CUSTOM_APP_ANALYTICS_ENABLED') ?? true;\n\n if (!enabled) return null;\n\n // Page internal_name: both the row's attribution key and the write endpoint's page.\n const app = (matchPage()?.[2] ?? '').slice(0, WIDTHS.app);\n\n if (!app) return null;\n\n return { app, endpoint: `/data/page/${encodeURIComponent(app)}/${ENTITY_NAME}` };\n}\n","import { VERSION, WIDTHS } from './constants';\nimport type { EventRow } from './types';\n\nexport interface RowInput {\n app: string;\n event: string;\n sessionId: string;\n pagePath: string;\n elementId?: number;\n meta?: Record<string, unknown>;\n}\n\nconst pad2 = (n: number) => String(n).padStart(2, '0');\n\nconst INT32_MIN = -(2 ** 31);\nconst INT32_MAX = 2 ** 31 - 1;\n\n/**\n * Integer only: a float or string value widens the `int` column permanently. Out-of-int32 values are 0,\n * since past 2^31-1 the column overflows and past 1e21 the number serializes as `1e+21`.\n */\nexport function toId(value: unknown): number {\n const n = Math.trunc(Number(value));\n\n return Number.isSafeInteger(n) && n >= INT32_MIN && n <= INT32_MAX ? n : 0;\n}\n\n/** Space-separated, never ISO — a `T`/`Z` sniffs as text and the column stops being `datetime`. */\nexport function formatTs(date: Date): string {\n return (\n `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())} ` +\n `${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}:${pad2(date.getUTCSeconds())}`\n );\n}\n\nfunction stringify(value: unknown): string | null {\n try {\n const out = JSON.stringify(value);\n\n return typeof out === 'string' ? out : null;\n } catch {\n return null;\n }\n}\n\n/** JSON string capped at WIDTHS.meta. Drops whole keys instead of slicing — a sliced payload is invalid JSON. */\nexport function serializeMeta(meta?: Record<string, unknown>): string {\n if (!meta) return '{}';\n\n const full = stringify(meta);\n\n if (full !== null && full.length <= WIDTHS.meta) return full;\n\n const kept: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(meta)) {\n const candidate = stringify({ ...kept, [key]: value, _truncated: 1 });\n\n if (candidate === null || candidate.length > WIDTHS.meta) continue;\n\n kept[key] = value;\n }\n\n return stringify({ ...kept, _truncated: 1 }) ?? '{}';\n}\n\nexport function uuid(): string {\n const c = typeof crypto !== 'undefined' ? crypto : undefined;\n\n if (c && typeof c.randomUUID === 'function') return c.randomUUID();\n\n // Older Safari has getRandomValues but not randomUUID.\n const bytes = new Uint8Array(16);\n\n if (c && typeof c.getRandomValues === 'function') {\n c.getRandomValues(bytes);\n } else {\n for (let i = 0; i < bytes.length; i += 1) bytes[i] = Math.floor(Math.random() * 256);\n }\n\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/** Only place a row is built. `ts`/`element_id` type mistakes here lock the shared dataset's schema permanently. */\nexport function createRow(input: RowInput): EventRow {\n return {\n id: uuid(),\n ts: formatTs(new Date()),\n app: input.app.slice(0, WIDTHS.app),\n event: input.event.slice(0, WIDTHS.event),\n session_id: input.sessionId.slice(0, 36),\n page_path: input.pagePath.slice(0, WIDTHS.page_path),\n element_id: toId(input.elementId),\n version: VERSION.slice(0, WIDTHS.version),\n meta: serializeMeta(input.meta),\n };\n}\n","export type SendOutcome = 'ok' | 'failed';\n\nconst RETRY_DELAY_MS = 2000;\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\n/** Rows arrive pre-serialized; joining as `[a,b,c]` matches JSON.stringify(rows) byte-for-byte without a second pass. */\nconst toArrayBody = (rows: string[]): string => `[${rows.join(',')}]`;\n\nasync function post(endpoint: string, body: string): Promise<{ ok: boolean; retryable: boolean }> {\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body,\n });\n\n if (!response.ok) {\n return { ok: false, retryable: response.status === 429 || response.status >= 500 };\n }\n\n const payload = (await response.json().catch(() => null)) as { resultCode?: number } | null;\n\n // 200 with a non-zero resultCode is a rejected insert, not a success.\n if (payload && typeof payload.resultCode === 'number' && payload.resultCode !== 0) {\n return { ok: false, retryable: false };\n }\n\n return { ok: true, retryable: false };\n } catch {\n return { ok: false, retryable: true };\n }\n}\n\n/** One retry max, same rows — a lost-response double-insert is caught by readers using COUNT(DISTINCT id). */\nexport async function send(endpoint: string, rows: string[]): Promise<SendOutcome> {\n if (rows.length === 0) return 'ok';\n\n const body = toArrayBody(rows);\n const first = await post(endpoint, body);\n\n if (first.ok) return 'ok';\n if (!first.retryable) return 'failed';\n\n await sleep(RETRY_DELAY_MS);\n\n return (await post(endpoint, body)).ok ? 'ok' : 'failed';\n}\n\n/** Unload path. No retry possible — the page is going away. */\nexport function sendOnUnload(endpoint: string, rows: string[]): void {\n if (rows.length === 0) return;\n\n const body = toArrayBody(rows);\n\n try {\n const blob = new Blob([body], { type: 'application/json' });\n\n if (navigator.sendBeacon && navigator.sendBeacon(endpoint, blob)) return;\n } catch {\n // fall through to keepalive fetch\n }\n\n // try/catch only sees a synchronous throw; the .catch keeps a rejected request (e.g. over the\n // keepalive quota) from surfacing as an unhandled rejection in the host app.\n try {\n fetch(endpoint, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body,\n keepalive: true,\n }).catch(() => {\n // Dropped. Nothing further possible at unload.\n });\n } catch {\n // Dropped. Nothing further possible at unload.\n }\n}\n","import { send, sendOnUnload } from './transport';\nimport type { SendOutcome } from './transport';\nimport type { EventRow } from './types';\n\n/** A row's JSON already computed once in push(); send() joins these into the array body instead of re-stringifying. */\ntype Buffered = { json: string; size: number };\n\nconst FLUSH_INTERVAL_MS = 15_000;\nconst MAX_FLUSH_ROWS = 50;\n/** Half of sendBeacon's 64 KiB cumulative quota. The server has no practical ceiling. */\nconst MAX_FLUSH_BYTES = 30_000;\nconst MAX_CONSECUTIVE_FAILURES = 3;\n\n// Counts encoded UTF-8 bytes, not UTF-16 code units — what sendBeacon actually charges against its quota.\nconst textEncoder = new TextEncoder();\n\nexport interface Queue {\n push(row: EventRow): void;\n flush(): void;\n flushOnUnload(): void;\n stop(): void;\n}\n\n/** Splits rows into batches whose `[a,b,c]` body stays within maxBytes. A lone row over the limit still goes, alone. */\nfunction chunkBySize(rows: Buffered[], maxBytes: number): Buffered[][] {\n const chunks: Buffered[][] = [];\n let current: Buffered[] = [];\n let size = 2;\n\n for (const row of rows) {\n if (current.length > 0 && size + 1 + row.size > maxBytes) {\n chunks.push(current);\n current = [];\n size = 2;\n }\n\n size += (current.length > 0 ? 1 : 0) + row.size;\n current.push(row);\n }\n\n if (current.length > 0) chunks.push(current);\n\n return chunks;\n}\n\nexport function createQueue(endpoint: string, onKilled?: () => void): Queue {\n let buffer: Buffered[] = [];\n let bytes = 0;\n let failures = 0;\n let stopped = false;\n let inFlight = false;\n // Guards re-entrancy: onKilled calls shutdown() -> stop() again, but the kill must fire once.\n let killed = false;\n\n const timer = setInterval(() => {\n void flush();\n }, FLUSH_INTERVAL_MS);\n\n function stop(): void {\n stopped = true;\n buffer = [];\n bytes = 0;\n clearInterval(timer);\n }\n\n async function flush(): Promise<void> {\n if (stopped || inFlight || buffer.length === 0) return;\n\n const batch = buffer;\n\n buffer = [];\n bytes = 0;\n inFlight = true;\n\n let outcome: SendOutcome = 'failed';\n\n try {\n\n // send() isn't expected to reject, but a rejection must not become an unhandled promise rejection.\n try {\n outcome = await send(\n endpoint,\n batch.map((b) => b.json),\n );\n } catch {\n outcome = 'failed';\n }\n\n // Stopped while the request was out: a stale result must not count toward, or trip, the kill-switch.\n if (stopped) return;\n\n if (outcome === 'ok') {\n failures = 0;\n return;\n }\n\n failures += 1;\n\n // Batch dropped, not re-buffered: transport already retried once, and re-sending a batch whose\n // response was merely lost is what creates duplicates.\n if (failures >= MAX_CONSECUTIVE_FAILURES && !killed) {\n killed = true;\n stop();\n\n // Hand the kill up to the owner so it can stop the timers/listeners still producing rows.\n try {\n onKilled?.();\n } catch {\n // swallowed — a host callback must never escape\n }\n }\n } finally {\n inFlight = false;\n\n // push() only flushes on its own threshold check, which is a no-op while a request is out; rows\n // that piled up meanwhile would otherwise wait for the timer and could outgrow the beacon quota.\n // Only after a success: re-flushing a failure straight away would spend the kill-switch's three\n // strikes within seconds of a brief outage, instead of across timer ticks.\n if (!stopped && outcome === 'ok' && (buffer.length >= MAX_FLUSH_ROWS || bytes >= MAX_FLUSH_BYTES)) void flush();\n }\n }\n\n return {\n push(row: EventRow): void {\n if (stopped) return;\n\n let json: string;\n\n try {\n json = JSON.stringify(row);\n } catch {\n return;\n }\n\n const size = textEncoder.encode(json).length;\n\n buffer.push({ json, size });\n bytes += size;\n\n if (buffer.length >= MAX_FLUSH_ROWS || bytes >= MAX_FLUSH_BYTES) void flush();\n },\n flush(): void {\n void flush();\n },\n flushOnUnload(): void {\n if (stopped || buffer.length === 0) return;\n\n const batch = buffer;\n\n buffer = [];\n bytes = 0;\n\n // One body over the 64 KiB quota fails in both sendBeacon and keepalive fetch and drops every row;\n // flush-sized chunks let at least the ones that fit through.\n for (const chunk of chunkBySize(batch, MAX_FLUSH_BYTES)) {\n sendOnUnload(\n endpoint,\n chunk.map((b) => b.json),\n );\n }\n },\n stop,\n };\n}\n","const SID_KEY = 'ca_pa_sid';\nconst LAST_KEY = 'ca_pa_last';\nconst IDLE_MS = 30 * 60 * 1000;\nconst TOUCH_THROTTLE_MS = 5000;\n\nlet memoryId: string | null = null;\nlet lastWrittenAt = 0;\n\n/** Safari private mode and blocked-storage policies throw on access, not just on write. */\nfunction read(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction write(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // In-memory session for the page lifetime beats no tracking.\n }\n}\n\nfunction newId(): string {\n try {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID();\n } catch {}\n\n return `s-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/** Clears in-memory state only. Storage stays, so a reload still finds the session. */\nexport function resetSession(): void {\n memoryId = null;\n lastWrittenAt = 0;\n}\n\n/** Session id, minting a new one after 30 min idle. Extends the window on every call. */\nexport function getSessionId(): string {\n const now = Date.now();\n const storedId = read(SID_KEY);\n const rawLast = read(LAST_KEY);\n const last = rawLast === null ? Number.NaN : Number(rawLast);\n const alive = Number.isFinite(last) && now - last <= IDLE_MS;\n\n let id: string;\n\n if (storedId && alive) {\n // Trusting storage here is what survives a reload.\n id = storedId;\n } else if (!storedId && memoryId) {\n // Blocked localStorage always reads null; reusing the memory id is the only stable fallback.\n id = memoryId;\n } else {\n id = newId();\n write(SID_KEY, id);\n lastWrittenAt = 0;\n }\n\n memoryId = id;\n\n if (now - lastWrittenAt >= TOUCH_THROTTLE_MS) {\n write(LAST_KEY, String(now));\n lastWrittenAt = now;\n }\n\n return id;\n}\n","export const HEARTBEAT_FAST_MS = 60_000;\nexport const HEARTBEAT_SLOW_MS = 300_000;\nexport const HEARTBEAT_FAST_COUNT = 5;\n\n/** Tapered, visible-only — readers derive engaged time as beats x interval, so a hidden beat would count idle time. */\nexport function startHeartbeat(emit: () => void): { stop(): void } {\n let beats = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let stopped = false;\n\n const interval = () => (beats < HEARTBEAT_FAST_COUNT ? HEARTBEAT_FAST_MS : HEARTBEAT_SLOW_MS);\n\n function schedule(): void {\n if (stopped || timer !== undefined) return;\n\n timer = setTimeout(() => {\n timer = undefined;\n\n if (stopped || document.visibilityState !== 'visible') return;\n\n beats += 1;\n\n try {\n emit();\n } catch {\n // A failed emit must not stop the timer.\n }\n\n schedule();\n }, interval());\n }\n\n function clear(): void {\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n }\n\n const onVisibilityChange = () => {\n if (stopped) return;\n\n if (document.visibilityState === 'visible') {\n schedule();\n } else {\n // Discard the partial interval rather than credit time the user was away.\n clear();\n }\n };\n\n document.addEventListener('visibilitychange', onVisibilityChange);\n\n if (document.visibilityState === 'visible') schedule();\n\n return {\n stop(): void {\n stopped = true;\n clear();\n document.removeEventListener('visibilitychange', onVisibilityChange);\n },\n };\n}\n","import { toId } from '../event';\n\nexport const RENDER_CAP = 50;\n/** Distinct cap buckets per page load. Past this every render of a component shares one bucket, so per-render props can't leak the map. */\nconst MAX_KEYS = 200;\n/** `visibility.preview.control_buttons.lineage` is 4 levels, the deepest shape MI documents. */\nconst PROP_DEPTH = 4;\n/** A `viewer` render is 38 leaves / 1672 chars, so this must not bind before serializeMeta's own cap does. */\nconst PROP_KEYS = 40;\nconst PROP_STRING = 40;\n\n/** Already read into element_id/segment_id above; re-emitting them as props is noise. */\nconst IDENTITY_KEYS = new Set(['element', 'element_id', 'segment', 'segment_id', 'segment_value_id']);\n\nconst SKIP = Symbol('skip');\n\nexport interface RenderPayload {\n elementId: number;\n meta: Record<string, unknown>;\n}\n\nfunction leafValue(value: unknown): unknown {\n if (value === null) return null;\n\n switch (typeof value) {\n case 'boolean':\n return value;\n case 'number':\n return Number.isFinite(value) ? value : SKIP;\n case 'string':\n return value.slice(0, PROP_STRING);\n default:\n return SKIP;\n }\n}\n\n/** Dotted paths, flat output: the dashboard stringifies a nested object into one opaque value, so its per-key distribution would be noise. Returns true when a cap dropped something. */\nfunction flattenProps(source: Record<string, unknown>, out: Record<string, unknown>, prefix: string, depth: number): boolean {\n let dropped = false;\n\n for (const [key, value] of Object.entries(source)) {\n if (Object.keys(out).length >= PROP_KEYS) return true;\n if (depth === 0 && IDENTITY_KEYS.has(key)) continue;\n\n const path = prefix === '' ? key : `${prefix}.${key}`;\n\n // Summarized, not expanded — index-keyed paths would make every array length its own set of keys.\n if (Array.isArray(value)) {\n out[`prop.${path}`] = `arr:${value.length}`;\n continue;\n }\n\n if (value !== null && typeof value === 'object') {\n if (depth + 1 < PROP_DEPTH) dropped = flattenProps(value as Record<string, unknown>, out, path, depth + 1) || dropped;\n else dropped = true;\n\n continue;\n }\n\n const leaf = leafValue(value);\n\n if (leaf !== SKIP) out[`prop.${path}`] = leaf;\n }\n\n return dropped;\n}\n\n/** FNV-1a. Keeps the bucket key short no matter how large the props are. */\nfunction hash(text: string): string {\n let h = 0x811c9dc5;\n\n for (let i = 0; i < text.length; i += 1) {\n h ^= text.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n\n return (h >>> 0).toString(36);\n}\n\n/** Listens on `document` only: `MiNamespace.render()` dispatches a bubbling CustomEvent, so load order doesn't matter. */\nexport function startMiRender(emit: (payload: RenderPayload) => void): { stop(): void } {\n let stopped = false;\n const counts = new Map<string, number>();\n\n const onRender = (event: Event) => {\n if (stopped) return;\n\n try {\n const detail = (event as CustomEvent).detail as { component?: unknown; props?: unknown } | undefined;\n\n if (!detail || typeof detail !== 'object') return;\n\n const component = typeof detail.component === 'string' ? detail.component : '';\n\n if (!component) return;\n\n const props = (detail.props && typeof detail.props === 'object' ? detail.props : {}) as Record<string, unknown>;\n // Three spellings in the wild: preview sends {element, segment}, viewer {element_id, segment_id}, access_denied_popup {element_id, segment_value_id}.\n const elementId = toId(props.element ?? props.element_id ?? 0);\n const segmentId = toId(props.segment ?? props.segment_id ?? props.segment_value_id ?? 0);\n\n const flat: Record<string, unknown> = {};\n const propsDropped = flattenProps(props, flat, '', 0);\n\n // Props are in the key: a component with no element (a popup) is otherwise one bucket for every variant it renders.\n const bucket = `${component}|${elementId}|${segmentId}|${hash(JSON.stringify(flat))}`;\n const key = counts.has(bucket) || counts.size < MAX_KEYS ? bucket : `${component}|*`;\n const seq = (counts.get(key) ?? 0) + 1;\n\n // Some apps re-render on every filter change; uncapped, one page burns the throttle budget.\n if (seq > RENDER_CAP) return;\n\n counts.set(key, seq);\n\n // Reserved keys first: serializeMeta drops trailing keys at the 2000-char cap, so props go before identity does.\n const meta: Record<string, unknown> = {\n component,\n element_id: elementId,\n segment_id: segmentId,\n ...flat,\n };\n\n if (propsDropped) meta.props_dropped = 1;\n if (seq === RENDER_CAP) meta.capped = 1;\n\n emit({ elementId, meta });\n } catch {\n // Never throw into the host render path.\n }\n };\n\n document.addEventListener('mi-render', onRender);\n\n return {\n stop(): void {\n stopped = true;\n document.removeEventListener('mi-render', onRender);\n counts.clear();\n },\n };\n}\n","import { isHashRoute } from '../config';\n\nexport interface PageViewSignal {\n stop(): void;\n emitNow(path?: string): void;\n}\n\n/** Absolute for an inbound hop, bare route after — a route in another app is not comparable here. */\nconst inboundReferrer = (): string => {\n try {\n const raw = document.referrer;\n\n if (!raw) return '';\n\n const url = new URL(raw);\n\n return `${url.origin}${url.pathname}`;\n } catch {\n return '';\n }\n};\n\n/** Emits once on start, then on every history or hash navigation. The original history method always still runs. */\nexport function startPageView(\n emit: (path: string, referrer: string) => void,\n currentPath: () => string,\n): PageViewSignal {\n let stopped = false;\n let lastPath: string | null = null;\n let lastLocation: string | null = null;\n\n // Dedupe key: path alone can't tell a `?tab=` change from a no-op replaceState. A hash route\n // (`#/reports?tab=1`) is part of it; a plain in-page anchor (`#section`) is not a new view.\n const locationKey = (path: string): string => {\n try {\n const { search, hash } = window.location;\n\n return `${path}${search}${isHashRoute(hash) ? hash : ''}`;\n } catch {\n return path;\n }\n };\n\n // resolvePath is a thunk so a throwing host currentPath() is caught here too, not in the caller.\n // dedupe is off for emitNow(): an explicit trackPageView() must always send.\n const safeEmit = (resolvePath: () => string, dedupe: boolean) => {\n if (stopped) return;\n\n try {\n const path = resolvePath();\n const location = locationKey(path);\n\n // Routers replaceState for scroll and filter sync without moving. Not a view.\n if (dedupe && location === lastLocation) return;\n\n // GA4 semantics: referrer is the previous page_view's path, not document.referrer, except first.\n const referrer = lastPath === null ? inboundReferrer() : lastPath;\n\n lastPath = path;\n lastLocation = location;\n emit(path, referrer);\n } catch {\n // swallowed\n }\n };\n\n const originalPush = window.history.pushState;\n const originalReplace = window.history.replaceState;\n\n const patch = (original: typeof window.history.pushState) =>\n function patched(this: History, ...args: Parameters<typeof window.history.pushState>) {\n const result = original.apply(this, args);\n\n safeEmit(currentPath, true);\n\n return result;\n };\n\n const patchedPush = patch(originalPush);\n const patchedReplace = patch(originalReplace);\n\n window.history.pushState = patchedPush;\n window.history.replaceState = patchedReplace;\n\n const onPopState = () => safeEmit(currentPath, true);\n\n window.addEventListener('popstate', onPopState);\n // Hash routers and `location.hash = …` navigate without pushState; dedupe drops the popstate twin.\n window.addEventListener('hashchange', onPopState);\n\n safeEmit(currentPath, true);\n\n return {\n stop(): void {\n stopped = true;\n lastPath = null;\n lastLocation = null;\n\n // Restore only if this signal still owns the patch — another startPageView may have patched over it.\n if (window.history.pushState === patchedPush) {\n window.history.pushState = originalPush;\n }\n\n if (window.history.replaceState === patchedReplace) {\n window.history.replaceState = originalReplace;\n }\n\n window.removeEventListener('popstate', onPopState);\n window.removeEventListener('hashchange', onPopState);\n },\n emitNow(path?: string): void {\n safeEmit(() => path ?? currentPath(), false);\n },\n };\n}\n","import { EVENT_NAME_RE, LIMITS, RESERVED_EVENTS } from './constants';\nimport { currentPagePath, currentQuery, resolveConfig } from './config';\nimport { createRow, toId } from './event';\nimport { createQueue, type Queue } from './queue';\nimport { getSessionId, resetSession } from './session';\nimport { startHeartbeat } from './signals/heartbeat';\nimport { startMiRender } from './signals/miRender';\nimport { startPageView, type PageViewSignal } from './signals/pageview';\nimport type { Options } from './types';\n\ntype Emit = (\n event: string,\n extra?: { pagePath?: string; elementId?: number; meta?: Record<string, unknown> },\n) => void;\n\ninterface Runtime {\n queue: Queue;\n emit: Emit;\n detachUnload: () => void;\n pageView: PageViewSignal;\n stopHeartbeat: () => void;\n stopMiRender: () => void;\n}\n\nlet runtime: Runtime | null = null;\nconst warnedOnce = new Set<string>();\n\n/** Nothing in ca-analytics may throw into the host app. */\nfunction guard(fn: () => void): void {\n try {\n fn();\n } catch {\n // swallowed\n }\n}\n\n/** Warns about caller misuse only the first time `key` is seen. */\nfunction warnOnce(key: string, message: string): void {\n if (warnedOnce.has(key)) return;\n\n warnedOnce.add(key);\n console.warn(`[ca-analytics] ${message}`);\n}\n\nfunction pageViewMeta(referrer: string): Record<string, unknown> {\n const query = currentQuery();\n\n return { referrer: referrer.slice(0, LIMITS.referrer), query: query.slice(0, LIMITS.query) };\n}\n\n/** Stops whatever of init()'s parts already started, used both for teardown and a failed startup. */\nfunction stopStarted(\n parts: {\n queue: Queue;\n detachUnload: () => void;\n pageView?: PageViewSignal;\n stopHeartbeat?: () => void;\n stopMiRender?: () => void;\n },\n flushFirst: boolean,\n): void {\n if (flushFirst) guard(() => parts.queue.flushOnUnload());\n\n guard(() => parts.pageView?.stop());\n guard(() => parts.stopHeartbeat?.());\n guard(() => parts.stopMiRender?.());\n guard(() => parts.detachUnload());\n guard(() => parts.queue.stop());\n}\n\n/** Unwinds everything `init()` started; `flushFirst` also drains the buffer via the unload path. `runtime` is cleared first so a re-entrant call is a no-op. */\nfunction teardown(flushFirst: boolean): void {\n const current = runtime;\n\n if (!current) return;\n\n runtime = null;\n warnedOnce.clear();\n\n stopStarted(current, flushFirst);\n resetSession();\n}\n\n/** Idempotent. A second call while active is ignored rather than doubling listeners. */\nexport function init(options: Options = {}): void {\n guard(() => {\n if (runtime) return;\n\n const config = resolveConfig(options);\n\n if (!config) return;\n\n // A tripped kill-switch tears down ca-analytics entirely, not just the queue — but only while this\n // queue's runtime is still the live one, so a stale queue can't kill a later init().\n const queue: Queue = createQueue(config.endpoint, () => {\n if (runtime?.queue === queue) shutdown();\n });\n\n const emit: Emit = (event, extra = {}) => {\n queue.push(\n createRow({\n app: config.app,\n event,\n sessionId: getSessionId(),\n pagePath: extra.pagePath ?? currentPagePath(),\n elementId: extra.elementId,\n meta: extra.meta,\n }),\n );\n };\n\n const onUnload = () => guard(() => queue.flushOnUnload());\n // pagehide alone is unreliable on iOS; visibilitychange is what fires there.\n const onVisibility = () => {\n if (document.visibilityState === 'hidden') onUnload();\n };\n const detachUnload = () => {\n window.removeEventListener('pagehide', onUnload);\n document.removeEventListener('visibilitychange', onVisibility);\n };\n\n let pageView: PageViewSignal | undefined;\n let stopHeartbeat: (() => void) | undefined;\n let stopMiRender: (() => void) | undefined;\n\n // A later step throwing must unwind what already started, or its timers/listeners leak.\n try {\n window.addEventListener('pagehide', onUnload);\n document.addEventListener('visibilitychange', onVisibility);\n\n stopHeartbeat = startHeartbeat(() => emit('heartbeat')).stop;\n stopMiRender = startMiRender((payload) =>\n emit('component_render', { elementId: payload.elementId, meta: payload.meta }),\n ).stop;\n pageView = startPageView(\n (path, referrer) => emit('page_view', { pagePath: path, meta: pageViewMeta(referrer) }),\n currentPagePath,\n );\n } catch (error) {\n stopStarted({ queue, detachUnload, pageView, stopHeartbeat, stopMiRender }, false);\n throw error;\n }\n\n runtime = { queue, emit, detachUnload, pageView, stopHeartbeat, stopMiRender };\n });\n}\n\n/** `props` is any object, interface-typed included; it is serialized like JSON.stringify (a Date becomes its ISO string). */\nexport function track(event: string, props?: object): void {\n guard(() => {\n if (!runtime) return;\n if (!event) return;\n\n const normalized = event.toLowerCase();\n\n if ((RESERVED_EVENTS as readonly string[]).includes(normalized)) {\n warnOnce('reserved', `\"${event}\" is reserved and was not tracked (further occurrences are silent)`);\n\n return;\n }\n\n if (!EVENT_NAME_RE.test(event)) {\n warnOnce('name', `\"${event}\" is not snake_case ≤100 chars; tracked as-is (further occurrences are silent)`);\n }\n\n runtime.emit(event, { meta: props as Record<string, unknown> | undefined });\n });\n}\n\n/**\n * `element_id`/`segment_id` resolve identity via `/api/element_info`; everything else is free-form. Generic so\n * interface-typed props with extra keys are accepted — an index signature would reject them.\n */\nexport function trackElement<T extends { element_id: number; segment_id?: number }>(props: T): void {\n guard(() => {\n if (!runtime) return;\n\n const { element_id, segment_id, ...extra } = props ?? {};\n const id = toId(element_id);\n\n if (!id) {\n warnOnce('element', `trackElement() ignored a zero, non-finite or out-of-range element_id (${element_id})`);\n\n return;\n }\n\n const segmentRaw = Number(segment_id);\n const segmentFinite = Number.isFinite(segmentRaw);\n\n // Omitting segment_id is a supported call; only a supplied-but-bad value is worth a warning.\n if (segment_id !== undefined && !segmentFinite) {\n warnOnce('segment', `trackElement() got a non-finite segment_id (${segment_id}); recorded as 0`);\n }\n\n // Reserved keys last: a caller's own element_id/segment_id in extra must not win the spread.\n const meta: Record<string, unknown> = {\n ...extra,\n element_id: id,\n segment_id: segmentFinite ? Math.trunc(segmentRaw) : 0,\n };\n\n runtime.emit('element_click', { elementId: id, meta });\n });\n}\n\n/** For routers whose navigation the history patch cannot observe. */\nexport function trackPageView(path?: string): void {\n guard(() => runtime?.pageView.emitNow(path));\n}\n\n/** Flushes what is buffered before stopping — up to 15s of events would otherwise drop. */\nexport function shutdown(): void {\n guard(() => teardown(true));\n}\n\nexport function isActive(): boolean {\n return runtime !== null;\n}\n","import { track, trackElement, trackPageView } from '../core/analytics';\n\n/** Thin wrapper over the module singleton. No provider, no context — ca-analytics renders nothing. */\nconst api = { track, trackElement, trackPageView } as const;\n\nexport function useAnalytics(): typeof api {\n return api;\n}\n"],"mappings":"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","names":["hash","hash"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metricinsights/ca-analytics",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Usage analytics for Metric Insights Custom Apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -28,6 +28,7 @@
28
28
  "build": "tsup",
29
29
  "dev": "tsup --watch",
30
30
  "typecheck": "tsc --noEmit",
31
+ "test": "vitest run",
31
32
  "prepublishOnly": "npm run typecheck && npm run build"
32
33
  },
33
34
  "peerDependencies": {
@@ -43,9 +44,11 @@
43
44
  },
44
45
  "devDependencies": {
45
46
  "@types/react": "^18.3.12",
47
+ "jsdom": "^30.1.1",
46
48
  "react": "^18.3.1",
47
49
  "tsup": "^8.3.5",
48
- "typescript": "^5.7.2"
50
+ "typescript": "^5.7.2",
51
+ "vitest": "^5.0.2"
49
52
  },
50
53
  "keywords": [
51
54
  "metricinsights",
@@ -62,5 +65,8 @@
62
65
  "url": "https://github.com/mi-examples/ca-analytics/issues"
63
66
  },
64
67
  "homepage": "https://github.com/mi-examples/ca-analytics#readme",
65
- "license": "MIT"
68
+ "license": "MIT",
69
+ "engines": {
70
+ "node": ">=22"
71
+ }
66
72
  }