@plasius/nfr 1.0.1 → 1.0.3

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
@@ -1,7 +1,7 @@
1
1
  # @plasius/nfr
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@plasius/nfr.svg)](https://www.npmjs.com/package/@plasius/nfr)
4
- [![Build Status](https://img.shields.io/github/actions/workflow/status/Plasius-LTD/nfr/ci.yml?branch=main&label=build&style=flat)](https://github.com/plasius/nfr/actions/workflows/ci.yml)
4
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/Plasius-LTD/nfr/ci.yml?branch=main&label=build&style=flat)](https://github.com/Plasius-LTD/nfr/actions/workflows/ci.yml)
5
5
  [![coverage](https://img.shields.io/codecov/c/github/Plasius-LTD/nfr)](https://codecov.io/gh/Plasius-LTD/nfr)
6
6
  [![License](https://img.shields.io/github/license/Plasius-LTD/nfr)](./LICENSE)
7
7
  [![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-yes-blue.svg)](./CODE_OF_CONDUCT.md)
@@ -12,7 +12,7 @@
12
12
 
13
13
  ## Overview
14
14
 
15
- `@plasius/nfr` provides a scoped state management solution for React applications. It allows developers to create isolated, testable, and composable stores without introducing heavy dependencies.
15
+ `@plasius/nfr` provides Non-Functional Requirement assistance, exposing platform agnostic Analytics, Performance Tracking and more.
16
16
 
17
17
  ---
18
18
 
@@ -26,35 +26,50 @@ npm install @plasius/nfr
26
26
 
27
27
  ## Usage Example
28
28
 
29
- ### Analytics
29
+ ### Analytics & Performance
30
30
 
31
- ```ts
31
+ ```tsx
32
+ import React from "react";
32
33
  import { withInteractionTracking, trackPerf, initPerformanceTracking } from "@plasius/nfr";
33
34
 
34
35
  // Example: wrap a component with interaction tracking
35
- const TrackedButton = withInteractionTracking("Button", (props) => {
36
- return <button {...props}>Click me</button>;
37
- });
36
+ const Button = React.forwardRef<HTMLButtonElement, React.ComponentProps<"button">>(
37
+ (props, ref) => (
38
+ <button ref={ref} {...props}>
39
+ Click me
40
+ </button>
41
+ )
42
+ );
43
+ const TrackedButton = withInteractionTracking(Button, { origin: "docs" });
38
44
 
39
45
  // Example: initialize performance tracking
40
-
41
46
  const teardown = initPerformanceTracking({
42
47
  track: trackPerf, // re-use our analytics pipeline
43
- resourceSampleRate: 0.25, // optional (default 0.25)
48
+ resourceSampleRate: 0.25, // optional (default 0.25; set 0 to disable)
44
49
  resourceFilter: (r) => r.initiatorType !== "img", // optional
45
50
  includeNetworkInfo: true, // optional (default true)
46
51
  includeMemorySnapshot: false, // optional (default false)
47
52
  });
48
53
 
49
- // Example: manual event
54
+ // Example: manual performance event (use a supported PerfCategory)
50
55
  trackPerf({
51
- category: "custom",
56
+ category: "resource",
52
57
  name: "user-action",
53
58
  ts: Date.now(),
54
59
  details: { action: "something-happened" },
55
60
  });
56
61
  ```
57
62
 
63
+ > Notes:
64
+ > - Web Vitals are loaded via optional `web-vitals`; if it isn’t installed, those metrics are skipped.
65
+ > - `initPerformanceTracking` safely no-ops in SSR/non-DOM environments.
66
+
67
+ ---
68
+
69
+ ## Testing
70
+
71
+ Run `npm test -- --coverage` to execute the Vitest suite (jsdom) and generate coverage reports in `coverage/` (currently ~95% line coverage). The harness automatically preloads a tiny shim to provide `vm.constants.DONT_CONTEXTIFY` on Node 20 so jsdom can start safely.
72
+
58
73
  ---
59
74
 
60
75
  ## Contributing
package/dist/index.cjs CHANGED
@@ -40,7 +40,6 @@ __export(index_exports, {
40
40
  module.exports = __toCommonJS(index_exports);
41
41
 
42
42
  // src/telemetry/analytics.ts
43
- var import_meta = {};
44
43
  var trackPerf = (e) => {
45
44
  const { ts, ...rest } = e;
46
45
  activeSink.track({
@@ -49,10 +48,12 @@ var trackPerf = (e) => {
49
48
  ts: ts ?? Date.now()
50
49
  });
51
50
  };
52
- var isProd = typeof import_meta !== "undefined" && import_meta.env?.MODE === "production" || process.env.NODE_ENV === "production";
51
+ var metaEnv = false ? void 0 : void 0;
52
+ var isProd = metaEnv?.MODE === "production" || typeof process !== "undefined" && process.env?.NODE_ENV === "production";
53
+ var hasWindow = typeof window !== "undefined";
53
54
  var consoleSink = {
54
- track: (event) => console.info("[analytics:track]", event),
55
- page: (name, props) => console.info("[analytics:page]", { name, props })
55
+ track: (event) => console.info(event.name, event.props),
56
+ page: (name, props) => console.info(name, props)
56
57
  };
57
58
  var dataLayerSink = {
58
59
  track: (event) => {
@@ -72,7 +73,7 @@ var dataLayerSink = {
72
73
  });
73
74
  }
74
75
  };
75
- var activeSink = isProd ? dataLayerSink : consoleSink;
76
+ var activeSink = isProd && hasWindow ? dataLayerSink : consoleSink;
76
77
  var setAnalyticsSink = (sink) => {
77
78
  activeSink = sink;
78
79
  };
@@ -126,7 +127,7 @@ var now = () => Date.now();
126
127
  var pageURL = () => typeof location !== "undefined" ? location.href : void 0;
127
128
  var navType = () => {
128
129
  try {
129
- const nav = performance.getEntriesByType("navigation")[0];
130
+ const nav = typeof performance !== "undefined" ? performance.getEntriesByType("navigation")[0] : void 0;
130
131
  return nav?.type ?? performance.navigation?.type;
131
132
  } catch {
132
133
  return void 0;
@@ -238,9 +239,9 @@ function observeLongTasks(track2) {
238
239
  };
239
240
  }
240
241
  function observeResources(track2, sampleRate, filter) {
241
- if (!("PerformanceObserver" in window)) return () => {
242
+ if (typeof window === "undefined" || !("PerformanceObserver" in window)) return () => {
242
243
  };
243
- const shouldSample = Math.max(0, Math.min(1, sampleRate)) || 0.25;
244
+ const shouldSample = Math.min(1, Math.max(0, sampleRate ?? 0.25));
244
245
  const take = () => Math.random() < shouldSample;
245
246
  let obs;
246
247
  const toEvent = (e) => ({
@@ -280,13 +281,20 @@ function wireVisibility(track2) {
280
281
  const onHidden = (type) => () => {
281
282
  track2({ category: "visibility", name: type, ts: now(), url: pageURL() });
282
283
  };
283
- document.addEventListener("visibilitychange", () => {
284
- if (document.visibilityState === "hidden") onHidden("hidden")();
285
- });
286
- window.addEventListener("pagehide", onHidden("pagehide"));
284
+ if (typeof document === "undefined" || typeof window === "undefined") {
285
+ return () => {
286
+ };
287
+ }
288
+ const handleHidden = onHidden("hidden");
289
+ const handleVisibilityChange = () => {
290
+ if (document.visibilityState === "hidden") handleHidden();
291
+ };
292
+ const handlePagehide = onHidden("pagehide");
293
+ document.addEventListener("visibilitychange", handleVisibilityChange);
294
+ window.addEventListener("pagehide", handlePagehide);
287
295
  return () => {
288
- document.removeEventListener("visibilitychange", onHidden("hidden"));
289
- window.removeEventListener("pagehide", onHidden("pagehide"));
296
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
297
+ window.removeEventListener("pagehide", handlePagehide);
290
298
  };
291
299
  }
292
300
  function snapshotNetwork(track2) {
@@ -322,6 +330,11 @@ function snapshotMemory(track2) {
322
330
  });
323
331
  }
324
332
  function initPerformanceTracking(opts) {
333
+ if (typeof window === "undefined" || typeof document === "undefined" || typeof performance === "undefined") {
334
+ const noop = () => {
335
+ };
336
+ return Object.assign(noop, { ready: Promise.resolve() });
337
+ }
325
338
  const {
326
339
  track: track2,
327
340
  resourceSampleRate = 0.25,
@@ -329,7 +342,7 @@ function initPerformanceTracking(opts) {
329
342
  includeNetworkInfo = true,
330
343
  includeMemorySnapshot = false
331
344
  } = opts;
332
- void wireWebVitals(track2);
345
+ const webVitalsReady = wireWebVitals(track2);
333
346
  if (document.readyState === "complete") {
334
347
  reportNavigationTiming(track2);
335
348
  reportPaintTimings(track2);
@@ -344,11 +357,12 @@ function initPerformanceTracking(opts) {
344
357
  const offVis = wireVisibility(track2);
345
358
  if (includeNetworkInfo) snapshotNetwork(track2);
346
359
  if (includeMemorySnapshot) snapshotMemory(track2);
347
- return () => {
360
+ const teardown = () => {
348
361
  offLong();
349
362
  offRes();
350
363
  offVis();
351
364
  };
365
+ return Object.assign(teardown, { ready: webVitalsReady });
352
366
  }
353
367
  // Annotate the CommonJS export names for ESM import in node:
354
368
  0 && (module.exports = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/telemetry/analytics.ts","../src/telemetry/withInteractionTracking.tsx","../src/performance/webVitals.ts"],"sourcesContent":["export * from \"./telemetry/index.js\";\nexport * from \"./performance/index.js\";\n","import { PerfEvent } from \"../performance/index.js\";\n\nexport type AnalyticsEvent = {\n name: string;\n props?: Record<string, unknown>;\n ts?: number;\n};\n\nexport interface AnalyticsSink {\n track: (event: AnalyticsEvent) => void;\n page?: (name: string, props?: Record<string, unknown>) => void;\n}\n\n/**\n * Send a performance event through the active analytics sink.\n * We flatten the payload so `dataLayer`-style sinks can consume it directly.\n */\nexport const trackPerf = (e: PerfEvent) => {\n // Preserve provided timestamp if present; otherwise add one in the sink (or here)\n const { ts, ...rest } = e;\n activeSink.track({\n name: \"perf\",\n props: rest,\n ts: ts ?? Date.now(),\n });\n};\n\nconst isProd =\n (typeof import.meta !== \"undefined\" &&\n (import.meta as any).env?.MODE === \"production\") ||\n process.env.NODE_ENV === \"production\";\n\n// Example sinks\nconst consoleSink: AnalyticsSink = {\n track: (event) => console.info(\"[analytics:track]\", event),\n page: (name, props) => console.info(\"[analytics:page]\", { name, props }),\n};\n\n// Replace with your real one later:\nconst dataLayerSink: AnalyticsSink = {\n track: (event) => {\n // Example: Google Tag Manager dataLayer\n (window as any).dataLayer = (window as any).dataLayer || [];\n (window as any).dataLayer.push({\n event: event.name,\n ...event.props,\n ts: event.ts ?? Date.now(),\n });\n },\n page: (name, props) => {\n (window as any).dataLayer = (window as any).dataLayer || [];\n (window as any).dataLayer.push({\n event: \"page_view\",\n page: name,\n ...props,\n });\n },\n};\n\nlet activeSink: AnalyticsSink = isProd ? dataLayerSink : consoleSink;\n\nexport const setAnalyticsSink = (sink: AnalyticsSink) => {\n activeSink = sink;\n};\n\nexport const track = (name: string, props?: Record<string, unknown>) =>\n activeSink.track({ name, props, ts: Date.now() });\n\nexport const page = (name: string, props?: Record<string, unknown>) =>\n activeSink.page?.(name, props);\n","import React from \"react\";\nimport { track } from \"./analytics.js\";\n\ntype WithInteractionOpts = {\n origin?: string; // e.g. story name\n};\n\nexport function withInteractionTracking<P extends object, R = unknown>(\n Component: React.ForwardRefExoticComponent<\n React.PropsWithoutRef<P> & React.RefAttributes<R>\n >,\n opts?: WithInteractionOpts\n): React.ForwardRefExoticComponent<\n React.PropsWithoutRef<P> & React.RefAttributes<R>\n>;\n\nexport function withInteractionTracking<P extends object>(\n Component: React.ComponentType<P>,\n opts?: WithInteractionOpts\n): React.FC<P>;\n\nexport function withInteractionTracking<P extends object, R = unknown>(\n Component:\n | React.ForwardRefExoticComponent<\n React.PropsWithoutRef<P> & React.RefAttributes<R>\n >\n | React.ComponentType<P>,\n { origin }: WithInteractionOpts = {}\n): any {\n const Wrapped = React.forwardRef<R, P>(function Wrapped(props, ref) {\n const onInteractionCapture = (\n e: React.PointerEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>\n ) => {\n // Normalize event type\n const isKey = (e as React.KeyboardEvent).key !== undefined;\n const isPointer = (e as React.PointerEvent).pointerType !== undefined;\n const type = isKey ? \"keydown\" : isPointer ? \"pointerup\" : e.type;\n\n // Only track meaningful keyboard activations (Enter/Space)\n let key: string | undefined;\n if (isKey) {\n key = (e as React.KeyboardEvent).key;\n if (key !== \"Enter\" && key !== \" \") return; // ignore other keys\n }\n\n // For pointers: only primary button releases\n if (isPointer) {\n const pe = e as React.PointerEvent;\n if (type === \"pointerup\" && pe.button !== 0) return;\n }\n\n const target = e.target as HTMLElement | null;\n const label =\n target?.getAttribute?.(\"aria-label\") ||\n target?.getAttribute?.(\"data-analytics-label\") ||\n target?.textContent?.trim() ||\n target?.tagName;\n\n track(\"ui.interaction\", {\n origin,\n label,\n type,\n key,\n pointerType: isPointer ? (e as React.PointerEvent).pointerType : undefined,\n });\n };\n\n const Comp = Component as any; // Allow ref passing for both ref-aware and plain components\n return (\n <div\n onPointerUpCapture={onInteractionCapture}\n onKeyDownCapture={onInteractionCapture}\n >\n <Comp ref={ref} {...(props as any)} />\n </div>\n );\n });\n\n Wrapped.displayName = `WithInteractionTracking(${Component.displayName || Component.name || \"Component\"})`;\n return Wrapped;\n}\n","/*\n * Performance & Web Vitals tracking\n * ---------------------------------\n * A lightweight collector that wires up:\n * - Core Web Vitals (LCP, INP, CLS, FCP, TTFB) via optional dynamic import of `web-vitals`\n * - Long tasks via PerformanceObserver('longtask')\n * - Navigation timing (TTFB, DOMContentLoaded, load, etc.)\n * - Paint timings (FP, FCP fallback)\n * - Resource timings (optionally sampled)\n * - Visibility lifecycle (hidden, pagehide)\n * - Network info (downlink, rtt, effectiveType) when available\n * - Memory snapshot (JS heap) when available (Chromium only)\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport type VitalName = \"LCP\" | \"INP\" | \"CLS\" | \"FCP\" | \"TTFB\";\nexport type PerfCategory =\n | \"web-vitals\"\n | \"nav-timing\"\n | \"paint\"\n | \"resource\"\n | \"longtask\"\n | \"visibility\"\n | \"snapshot\";\n\nexport type PerfEvent = {\n category: PerfCategory;\n name: string; // e.g., \"LCP\", \"DOMContentLoaded\", \"resource:script\"\n value?: number; // ms for most; CLS is unitless\n rating?: \"good\" | \"needs-improvement\" | \"poor\";\n delta?: number; // for web-vitals deltas\n id?: string; // web-vitals id\n url?: string; // current page\n navigationType?: string; // navigate, reload, back_forward, prerender\n ts?: number; // epoch ms when measured\n details?: Record<string, any>;\n};\n\nexport type PerfTracker = (event: PerfEvent) => void;\n\nexport type InitOptions = {\n track: PerfTracker;\n /** Sample a subset of resource entries to avoid high-volume beacons */\n resourceSampleRate?: number; // 0..1, default 0.25\n /** Predicate to include a resource entry */\n resourceFilter?: (e: PerformanceResourceTiming) => boolean;\n /** Include network information (effectiveType/downlink/rtt) snapshots */\n includeNetworkInfo?: boolean;\n /** Include JS heap memory snapshot where supported */\n includeMemorySnapshot?: boolean;\n};\n\nconst now = () => Date.now();\nconst pageURL = () => (typeof location !== \"undefined\" ? location.href : undefined);\nconst navType = () => {\n try {\n const nav = performance.getEntriesByType(\"navigation\")[0] as PerformanceNavigationTiming | undefined;\n return nav?.type ?? (performance as any).navigation?.type;\n } catch {\n return undefined;\n }\n};\n\n// ---------------------------------------------------------------------------\n// Web Vitals (via optional dynamic import of `web-vitals`)\n// ---------------------------------------------------------------------------\nasync function wireWebVitals(track: PerfTracker) {\n try {\n // @ts-ignore - optional dependency; we ignore TS resolution and rely on runtime try/catch\n const mod: any = await import(/* webpackChunkName: \"web-vitals\" */ \"web-vitals\");\n const handlers: [string, (onReport: any, opts?: any) => void, any?][] = [\n [\"LCP\", mod.onLCP],\n [\"INP\", mod.onINP],\n [\"CLS\", mod.onCLS],\n [\"FCP\", mod.onFCP],\n [\"TTFB\", mod.onTTFB],\n ];\n\n handlers.forEach(([name, fn]) => {\n if (typeof fn === \"function\") {\n fn((metric: any) => {\n const { value, rating, delta, id, navigationType: nt, attribution } = metric;\n track({\n category: \"web-vitals\",\n name,\n value,\n rating,\n delta,\n id,\n url: pageURL(),\n navigationType: nt ?? navType(),\n ts: now(),\n details: attribution ? { attribution } : undefined,\n });\n }, { reportAllChanges: true });\n }\n });\n } catch {\n // `web-vitals` not available — silently skip.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Navigation timing & paint timings\n// ---------------------------------------------------------------------------\nfunction reportNavigationTiming(track: PerfTracker) {\n try {\n const nav = performance.getEntriesByType(\"navigation\")[0] as PerformanceNavigationTiming | undefined;\n if (!nav) return;\n const base = {\n category: \"nav-timing\" as const,\n url: pageURL(),\n navigationType: nav.type,\n ts: now(),\n };\n const push = (name: string, value: number | undefined, details?: any) => {\n if (value == null || Number.isNaN(value)) return;\n track({ ...base, name, value, details });\n };\n\n // Common derived timings (ms)\n push(\"TTFB\", nav.responseStart);\n push(\"DNS\", nav.domainLookupEnd - nav.domainLookupStart);\n push(\"TCP\", nav.connectEnd - nav.connectStart);\n push(\"TLS\", nav.secureConnectionStart > 0 ? nav.connectEnd - nav.secureConnectionStart : 0);\n push(\"Request\", nav.responseStart - nav.requestStart);\n push(\"Response\", nav.responseEnd - nav.responseStart);\n push(\"DOMInteractive\", nav.domInteractive);\n push(\"DOMComplete\", nav.domComplete);\n push(\"DOMContentLoaded\", nav.domContentLoadedEventEnd);\n push(\"LoadEvent\", nav.loadEventEnd - nav.loadEventStart);\n push(\"FirstByteToInteractive\", nav.domInteractive - nav.responseStart);\n } catch {\n // ignore\n }\n}\n\nfunction reportPaintTimings(track: PerfTracker) {\n try {\n const paints = performance.getEntriesByType(\"paint\") as PerformanceEntry[];\n const base = { category: \"paint\" as const, url: pageURL(), navigationType: navType(), ts: now() };\n for (const p of paints) {\n track({ ...base, name: p.name, value: p.startTime });\n }\n } catch {\n // ignore\n }\n}\n\n// ---------------------------------------------------------------------------\n// Long tasks\n// ---------------------------------------------------------------------------\nfunction observeLongTasks(track: PerfTracker): () => void {\n if (!(\"PerformanceObserver\" in window)) return () => {};\n let obs: PerformanceObserver | undefined;\n try {\n obs = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n const anyEntry: any = entry as any;\n track({\n category: \"longtask\",\n name: \"longtask\",\n value: entry.duration,\n ts: now(),\n url: pageURL(),\n details: {\n startTime: entry.startTime,\n duration: entry.duration,\n attribution: anyEntry.attribution ?? anyEntry.attribution ?? undefined,\n },\n });\n }\n });\n obs.observe({ type: \"longtask\", buffered: true as any });\n } catch {\n // ignore\n }\n return () => {\n try { obs?.disconnect(); } catch {}\n };\n}\n\n// ---------------------------------------------------------------------------\n// Resource timings (sampled)\n// ---------------------------------------------------------------------------\nfunction observeResources(track: PerfTracker, sampleRate: number, filter?: (e: PerformanceResourceTiming) => boolean): () => void {\n if (!(\"PerformanceObserver\" in window)) return () => {};\n const shouldSample = (Math.max(0, Math.min(1, sampleRate)) || 0.25);\n const take = () => Math.random() < shouldSample;\n let obs: PerformanceObserver | undefined;\n\n const toEvent = (e: PerformanceResourceTiming): PerfEvent => ({\n category: \"resource\",\n name: `resource:${e.initiatorType || \"other\"}`,\n url: e.name,\n ts: now(),\n value: e.duration,\n details: {\n startTime: e.startTime,\n transferSize: (e as any).transferSize,\n encodedBodySize: (e as any).encodedBodySize,\n decodedBodySize: (e as any).decodedBodySize,\n nextHopProtocol: (e as any).nextHopProtocol,\n initiatorType: e.initiatorType,\n },\n });\n\n try {\n obs = new PerformanceObserver((list) => {\n for (const entry of list.getEntries() as PerformanceResourceTiming[]) {\n if (filter && !filter(entry)) continue;\n if (!take()) continue;\n track(toEvent(entry));\n }\n });\n obs.observe({ type: \"resource\", buffered: true as any });\n } catch {\n // ignore\n }\n return () => { try { obs?.disconnect(); } catch {} };\n}\n\n// ---------------------------------------------------------------------------\n// Visibility & lifecycle\n// ---------------------------------------------------------------------------\nfunction wireVisibility(track: PerfTracker): () => void {\n const onHidden = (type: string) => () => {\n track({ category: \"visibility\", name: type, ts: now(), url: pageURL() });\n };\n document.addEventListener(\"visibilitychange\", () => {\n if (document.visibilityState === \"hidden\") onHidden(\"hidden\")();\n });\n window.addEventListener(\"pagehide\", onHidden(\"pagehide\"));\n return () => {\n document.removeEventListener(\"visibilitychange\", onHidden(\"hidden\"));\n window.removeEventListener(\"pagehide\", onHidden(\"pagehide\"));\n };\n}\n\n// ---------------------------------------------------------------------------\n// Snapshots: Network / Memory\n// ---------------------------------------------------------------------------\nfunction snapshotNetwork(track: PerfTracker) {\n const nav: any = (navigator as any);\n const c = nav?.connection || nav?.mozConnection || nav?.webkitConnection;\n if (!c) return;\n track({\n category: \"snapshot\",\n name: \"network-info\",\n ts: now(),\n url: pageURL(),\n details: {\n effectiveType: c.effectiveType,\n downlink: c.downlink,\n rtt: c.rtt,\n saveData: c.saveData,\n },\n });\n}\n\nfunction snapshotMemory(track: PerfTracker) {\n const mem: any = (performance as any).memory;\n if (!mem) return;\n track({\n category: \"snapshot\",\n name: \"js-heap\",\n ts: now(),\n url: pageURL(),\n details: {\n jsHeapSizeLimit: mem.jsHeapSizeLimit,\n totalJSHeapSize: mem.totalJSHeapSize,\n usedJSHeapSize: mem.usedJSHeapSize,\n },\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\nexport function initPerformanceTracking(opts: InitOptions): () => void {\n const {\n track,\n resourceSampleRate = 0.25,\n resourceFilter,\n includeNetworkInfo = true,\n includeMemorySnapshot = false,\n } = opts;\n\n // Web Vitals (async)\n void wireWebVitals(track);\n\n // Navigation & paints (buffered entries are available after DOM ready)\n if (document.readyState === \"complete\") {\n reportNavigationTiming(track);\n reportPaintTimings(track);\n } else {\n window.addEventListener(\"load\", () => {\n reportNavigationTiming(track);\n reportPaintTimings(track);\n }, { once: true });\n }\n\n // Observers\n const offLong = observeLongTasks(track);\n const offRes = observeResources(track, resourceSampleRate, resourceFilter);\n const offVis = wireVisibility(track);\n\n // Snapshots\n if (includeNetworkInfo) snapshotNetwork(track);\n if (includeMemorySnapshot) snapshotMemory(track);\n\n // Teardown\n return () => {\n offLong();\n offRes();\n offVis();\n };\n}\n\nexport default {\n initPerformanceTracking,\n};\n"],"mappings":"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","names":["React","Wrapped","track"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/telemetry/analytics.ts","../src/telemetry/withInteractionTracking.tsx","../src/performance/webVitals.ts"],"sourcesContent":["export * from \"./telemetry/index.js\";\nexport * from \"./performance/index.js\";\n","import { PerfEvent } from \"../performance/index.js\";\n\ndeclare const __IMPORT_META_ENV__: { MODE?: string } | undefined;\n\nexport type AnalyticsEvent = {\n name: string;\n props?: Record<string, unknown>;\n ts?: number;\n};\n\nexport interface AnalyticsSink {\n track: (event: AnalyticsEvent) => void;\n page?: (name: string, props?: Record<string, unknown>) => void;\n}\n\n/**\n * Send a performance event through the active analytics sink.\n * We flatten the payload so `dataLayer`-style sinks can consume it directly.\n */\nexport const trackPerf = (e: PerfEvent) => {\n // Preserve provided timestamp if present; otherwise add one in the sink (or here)\n const { ts, ...rest } = e;\n activeSink.track({\n name: \"perf\",\n props: rest,\n ts: ts ?? Date.now(),\n });\n};\n\nconst metaEnv =\n typeof __IMPORT_META_ENV__ !== \"undefined\" ? __IMPORT_META_ENV__ : undefined;\nconst isProd =\n metaEnv?.MODE === \"production\" ||\n (typeof process !== \"undefined\" && process.env?.NODE_ENV === \"production\");\nconst hasWindow = typeof window !== \"undefined\";\n\n// Example sinks\nconst consoleSink: AnalyticsSink = {\n track: (event) => console.info(event.name, event.props),\n page: (name, props) => console.info(name, props),\n};\n\n// Replace with your real one later:\nconst dataLayerSink: AnalyticsSink = {\n track: (event) => {\n // Example: Google Tag Manager dataLayer\n (window as any).dataLayer = (window as any).dataLayer || [];\n (window as any).dataLayer.push({\n event: event.name,\n ...event.props,\n ts: event.ts ?? Date.now(),\n });\n },\n page: (name, props) => {\n (window as any).dataLayer = (window as any).dataLayer || [];\n (window as any).dataLayer.push({\n event: \"page_view\",\n page: name,\n ...props,\n });\n },\n};\n\nlet activeSink: AnalyticsSink = isProd && hasWindow ? dataLayerSink : consoleSink;\n\nexport const setAnalyticsSink = (sink: AnalyticsSink) => {\n activeSink = sink;\n};\n\nexport const track = (name: string, props?: Record<string, unknown>) =>\n activeSink.track({ name, props, ts: Date.now() });\n\nexport const page = (name: string, props?: Record<string, unknown>) =>\n activeSink.page?.(name, props);\n","import React from \"react\";\nimport { track } from \"./analytics.js\";\n\ntype WithInteractionOpts = {\n origin?: string; // e.g. story name\n};\n\nexport function withInteractionTracking<P extends object, R = unknown>(\n Component: React.ForwardRefExoticComponent<\n React.PropsWithoutRef<P> & React.RefAttributes<R>\n >,\n opts?: WithInteractionOpts\n): React.ForwardRefExoticComponent<\n React.PropsWithoutRef<P> & React.RefAttributes<R>\n>;\n\nexport function withInteractionTracking<P extends object>(\n Component: React.ComponentType<P>,\n opts?: WithInteractionOpts\n): React.FC<P>;\n\nexport function withInteractionTracking<P extends object, R = unknown>(\n Component:\n | React.ForwardRefExoticComponent<\n React.PropsWithoutRef<P> & React.RefAttributes<R>\n >\n | React.ComponentType<P>,\n { origin }: WithInteractionOpts = {}\n): any {\n const Wrapped = React.forwardRef<R, P>(function Wrapped(props, ref) {\n const onInteractionCapture = (\n e: React.PointerEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>\n ) => {\n // Normalize event type\n const isKey = (e as React.KeyboardEvent).key !== undefined;\n const isPointer = (e as React.PointerEvent).pointerType !== undefined;\n const type = isKey ? \"keydown\" : isPointer ? \"pointerup\" : e.type;\n\n // Only track meaningful keyboard activations (Enter/Space)\n let key: string | undefined;\n if (isKey) {\n key = (e as React.KeyboardEvent).key;\n if (key !== \"Enter\" && key !== \" \") return; // ignore other keys\n }\n\n // For pointers: only primary button releases\n if (isPointer) {\n const pe = e as React.PointerEvent;\n if (type === \"pointerup\" && pe.button !== 0) return;\n }\n\n const target = e.target as HTMLElement | null;\n const label =\n target?.getAttribute?.(\"aria-label\") ||\n target?.getAttribute?.(\"data-analytics-label\") ||\n target?.textContent?.trim() ||\n target?.tagName;\n\n track(\"ui.interaction\", {\n origin,\n label,\n type,\n key,\n pointerType: isPointer ? (e as React.PointerEvent).pointerType : undefined,\n });\n };\n\n const Comp = Component as any; // Allow ref passing for both ref-aware and plain components\n return (\n <div\n onPointerUpCapture={onInteractionCapture}\n onKeyDownCapture={onInteractionCapture}\n >\n <Comp ref={ref} {...(props as any)} />\n </div>\n );\n });\n\n Wrapped.displayName = `WithInteractionTracking(${Component.displayName || Component.name || \"Component\"})`;\n return Wrapped;\n}\n","/*\n * Performance & Web Vitals tracking\n * ---------------------------------\n * A lightweight collector that wires up:\n * - Core Web Vitals (LCP, INP, CLS, FCP, TTFB) via optional dynamic import of `web-vitals`\n * - Long tasks via PerformanceObserver('longtask')\n * - Navigation timing (TTFB, DOMContentLoaded, load, etc.)\n * - Paint timings (FP, FCP fallback)\n * - Resource timings (optionally sampled)\n * - Visibility lifecycle (hidden, pagehide)\n * - Network info (downlink, rtt, effectiveType) when available\n * - Memory snapshot (JS heap) when available (Chromium only)\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport type VitalName = \"LCP\" | \"INP\" | \"CLS\" | \"FCP\" | \"TTFB\";\nexport type PerfCategory =\n | \"web-vitals\"\n | \"nav-timing\"\n | \"paint\"\n | \"resource\"\n | \"longtask\"\n | \"visibility\"\n | \"snapshot\";\n\nexport type PerfEvent = {\n category: PerfCategory;\n name: string; // e.g., \"LCP\", \"DOMContentLoaded\", \"resource:script\"\n value?: number; // ms for most; CLS is unitless\n rating?: \"good\" | \"needs-improvement\" | \"poor\";\n delta?: number; // for web-vitals deltas\n id?: string; // web-vitals id\n url?: string; // current page\n navigationType?: string; // navigate, reload, back_forward, prerender\n ts?: number; // epoch ms when measured\n details?: Record<string, any>;\n};\n\nexport type PerfTracker = (event: PerfEvent) => void;\nexport type PerfTeardown = (() => void) & { ready: Promise<void> };\n\nexport type InitOptions = {\n track: PerfTracker;\n /** Sample a subset of resource entries to avoid high-volume beacons */\n resourceSampleRate?: number; // 0..1, default 0.25\n /** Predicate to include a resource entry */\n resourceFilter?: (e: PerformanceResourceTiming) => boolean;\n /** Include network information (effectiveType/downlink/rtt) snapshots */\n includeNetworkInfo?: boolean;\n /** Include JS heap memory snapshot where supported */\n includeMemorySnapshot?: boolean;\n};\n\nconst now = () => Date.now();\nconst pageURL = () => (typeof location !== \"undefined\" ? location.href : undefined);\nconst navType = () => {\n try {\n const nav =\n typeof performance !== \"undefined\"\n ? (performance.getEntriesByType(\"navigation\")[0] as PerformanceNavigationTiming | undefined)\n : undefined;\n return nav?.type ?? (performance as any).navigation?.type;\n } catch {\n return undefined;\n }\n};\n\n// ---------------------------------------------------------------------------\n// Web Vitals (via optional dynamic import of `web-vitals`)\n// ---------------------------------------------------------------------------\nasync function wireWebVitals(track: PerfTracker) {\n try {\n // @ts-ignore - optional dependency; we ignore TS resolution and rely on runtime try/catch\n const mod: any = await import(/* webpackChunkName: \"web-vitals\" */ \"web-vitals\");\n const handlers: [string, (onReport: any, opts?: any) => void, any?][] = [\n [\"LCP\", mod.onLCP],\n [\"INP\", mod.onINP],\n [\"CLS\", mod.onCLS],\n [\"FCP\", mod.onFCP],\n [\"TTFB\", mod.onTTFB],\n ];\n\n handlers.forEach(([name, fn]) => {\n if (typeof fn === \"function\") {\n fn((metric: any) => {\n const { value, rating, delta, id, navigationType: nt, attribution } = metric;\n track({\n category: \"web-vitals\",\n name,\n value,\n rating,\n delta,\n id,\n url: pageURL(),\n navigationType: nt ?? navType(),\n ts: now(),\n details: attribution ? { attribution } : undefined,\n });\n }, { reportAllChanges: true });\n }\n });\n } catch {\n // `web-vitals` not available — silently skip.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Navigation timing & paint timings\n// ---------------------------------------------------------------------------\nfunction reportNavigationTiming(track: PerfTracker) {\n try {\n const nav = performance.getEntriesByType(\"navigation\")[0] as PerformanceNavigationTiming | undefined;\n if (!nav) return;\n const base = {\n category: \"nav-timing\" as const,\n url: pageURL(),\n navigationType: nav.type,\n ts: now(),\n };\n const push = (name: string, value: number | undefined, details?: any) => {\n if (value == null || Number.isNaN(value)) return;\n track({ ...base, name, value, details });\n };\n\n // Common derived timings (ms)\n push(\"TTFB\", nav.responseStart);\n push(\"DNS\", nav.domainLookupEnd - nav.domainLookupStart);\n push(\"TCP\", nav.connectEnd - nav.connectStart);\n push(\"TLS\", nav.secureConnectionStart > 0 ? nav.connectEnd - nav.secureConnectionStart : 0);\n push(\"Request\", nav.responseStart - nav.requestStart);\n push(\"Response\", nav.responseEnd - nav.responseStart);\n push(\"DOMInteractive\", nav.domInteractive);\n push(\"DOMComplete\", nav.domComplete);\n push(\"DOMContentLoaded\", nav.domContentLoadedEventEnd);\n push(\"LoadEvent\", nav.loadEventEnd - nav.loadEventStart);\n push(\"FirstByteToInteractive\", nav.domInteractive - nav.responseStart);\n } catch {\n // ignore\n }\n}\n\nfunction reportPaintTimings(track: PerfTracker) {\n try {\n const paints = performance.getEntriesByType(\"paint\") as PerformanceEntry[];\n const base = { category: \"paint\" as const, url: pageURL(), navigationType: navType(), ts: now() };\n for (const p of paints) {\n track({ ...base, name: p.name, value: p.startTime });\n }\n } catch {\n // ignore\n }\n}\n\n// ---------------------------------------------------------------------------\n// Long tasks\n// ---------------------------------------------------------------------------\nfunction observeLongTasks(track: PerfTracker): () => void {\n if (!(\"PerformanceObserver\" in window)) return () => {};\n let obs: PerformanceObserver | undefined;\n try {\n obs = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n const anyEntry: any = entry as any;\n track({\n category: \"longtask\",\n name: \"longtask\",\n value: entry.duration,\n ts: now(),\n url: pageURL(),\n details: {\n startTime: entry.startTime,\n duration: entry.duration,\n attribution: anyEntry.attribution ?? anyEntry.attribution ?? undefined,\n },\n });\n }\n });\n obs.observe({ type: \"longtask\", buffered: true as any });\n } catch {\n // ignore\n }\n return () => {\n try { obs?.disconnect(); } catch {}\n };\n}\n\n// ---------------------------------------------------------------------------\n// Resource timings (sampled)\n// ---------------------------------------------------------------------------\nfunction observeResources(track: PerfTracker, sampleRate: number, filter?: (e: PerformanceResourceTiming) => boolean): () => void {\n if (typeof window === \"undefined\" || !(\"PerformanceObserver\" in window)) return () => {};\n const shouldSample = Math.min(1, Math.max(0, sampleRate ?? 0.25));\n const take = () => Math.random() < shouldSample;\n let obs: PerformanceObserver | undefined;\n\n const toEvent = (e: PerformanceResourceTiming): PerfEvent => ({\n category: \"resource\",\n name: `resource:${e.initiatorType || \"other\"}`,\n url: e.name,\n ts: now(),\n value: e.duration,\n details: {\n startTime: e.startTime,\n transferSize: (e as any).transferSize,\n encodedBodySize: (e as any).encodedBodySize,\n decodedBodySize: (e as any).decodedBodySize,\n nextHopProtocol: (e as any).nextHopProtocol,\n initiatorType: e.initiatorType,\n },\n });\n\n try {\n obs = new PerformanceObserver((list) => {\n for (const entry of list.getEntries() as PerformanceResourceTiming[]) {\n if (filter && !filter(entry)) continue;\n if (!take()) continue;\n track(toEvent(entry));\n }\n });\n obs.observe({ type: \"resource\", buffered: true as any });\n } catch {\n // ignore\n }\n return () => { try { obs?.disconnect(); } catch {} };\n}\n\n// ---------------------------------------------------------------------------\n// Visibility & lifecycle\n// ---------------------------------------------------------------------------\nfunction wireVisibility(track: PerfTracker): () => void {\n const onHidden = (type: string) => () => {\n track({ category: \"visibility\", name: type, ts: now(), url: pageURL() });\n };\n\n if (typeof document === \"undefined\" || typeof window === \"undefined\") {\n return () => {};\n }\n\n const handleHidden = onHidden(\"hidden\");\n const handleVisibilityChange = () => {\n if (document.visibilityState === \"hidden\") handleHidden();\n };\n const handlePagehide = onHidden(\"pagehide\");\n\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n window.addEventListener(\"pagehide\", handlePagehide);\n return () => {\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n window.removeEventListener(\"pagehide\", handlePagehide);\n };\n}\n\n// ---------------------------------------------------------------------------\n// Snapshots: Network / Memory\n// ---------------------------------------------------------------------------\nfunction snapshotNetwork(track: PerfTracker) {\n const nav: any = (navigator as any);\n const c = nav?.connection || nav?.mozConnection || nav?.webkitConnection;\n if (!c) return;\n track({\n category: \"snapshot\",\n name: \"network-info\",\n ts: now(),\n url: pageURL(),\n details: {\n effectiveType: c.effectiveType,\n downlink: c.downlink,\n rtt: c.rtt,\n saveData: c.saveData,\n },\n });\n}\n\nfunction snapshotMemory(track: PerfTracker) {\n const mem: any = (performance as any).memory;\n if (!mem) return;\n track({\n category: \"snapshot\",\n name: \"js-heap\",\n ts: now(),\n url: pageURL(),\n details: {\n jsHeapSizeLimit: mem.jsHeapSizeLimit,\n totalJSHeapSize: mem.totalJSHeapSize,\n usedJSHeapSize: mem.usedJSHeapSize,\n },\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\nexport function initPerformanceTracking(opts: InitOptions): PerfTeardown {\n if (typeof window === \"undefined\" || typeof document === \"undefined\" || typeof performance === \"undefined\") {\n // SSR / non-DOM environment: no-op to avoid crashes\n const noop = () => {};\n return Object.assign(noop, { ready: Promise.resolve() });\n }\n\n const {\n track,\n resourceSampleRate = 0.25,\n resourceFilter,\n includeNetworkInfo = true,\n includeMemorySnapshot = false,\n } = opts;\n\n // Web Vitals (async)\n const webVitalsReady = wireWebVitals(track);\n\n // Navigation & paints (buffered entries are available after DOM ready)\n if (document.readyState === \"complete\") {\n reportNavigationTiming(track);\n reportPaintTimings(track);\n } else {\n window.addEventListener(\"load\", () => {\n reportNavigationTiming(track);\n reportPaintTimings(track);\n }, { once: true });\n }\n\n // Observers\n const offLong = observeLongTasks(track);\n const offRes = observeResources(track, resourceSampleRate, resourceFilter);\n const offVis = wireVisibility(track);\n\n // Snapshots\n if (includeNetworkInfo) snapshotNetwork(track);\n if (includeMemorySnapshot) snapshotMemory(track);\n\n // Teardown\n const teardown = () => {\n offLong();\n offRes();\n offVis();\n };\n return Object.assign(teardown, { ready: webVitalsReady });\n}\n\nexport default {\n initPerformanceTracking,\n};\n"],"mappings":"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","names":["React","Wrapped","track"]}
package/dist/index.d.ts CHANGED
@@ -15,6 +15,9 @@ type PerfEvent = {
15
15
  details?: Record<string, any>;
16
16
  };
17
17
  type PerfTracker = (event: PerfEvent) => void;
18
+ type PerfTeardown = (() => void) & {
19
+ ready: Promise<void>;
20
+ };
18
21
  type InitOptions = {
19
22
  track: PerfTracker;
20
23
  /** Sample a subset of resource entries to avoid high-volume beacons */
@@ -26,7 +29,7 @@ type InitOptions = {
26
29
  /** Include JS heap memory snapshot where supported */
27
30
  includeMemorySnapshot?: boolean;
28
31
  };
29
- declare function initPerformanceTracking(opts: InitOptions): () => void;
32
+ declare function initPerformanceTracking(opts: InitOptions): PerfTeardown;
30
33
 
31
34
  type AnalyticsEvent = {
32
35
  name: string;
@@ -52,4 +55,4 @@ type WithInteractionOpts = {
52
55
  declare function withInteractionTracking<P extends object, R = unknown>(Component: React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<R>>, opts?: WithInteractionOpts): React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<R>>;
53
56
  declare function withInteractionTracking<P extends object>(Component: React.ComponentType<P>, opts?: WithInteractionOpts): React.FC<P>;
54
57
 
55
- export { type AnalyticsEvent, type AnalyticsSink, type InitOptions, type PerfCategory, type PerfEvent, type PerfTracker, type VitalName, initPerformanceTracking, page, setAnalyticsSink, track, trackPerf, withInteractionTracking };
58
+ export { type AnalyticsEvent, type AnalyticsSink, type InitOptions, type PerfCategory, type PerfEvent, type PerfTeardown, type PerfTracker, type VitalName, initPerformanceTracking, page, setAnalyticsSink, track, trackPerf, withInteractionTracking };
package/dist/index.js CHANGED
@@ -7,10 +7,12 @@ var trackPerf = (e) => {
7
7
  ts: ts ?? Date.now()
8
8
  });
9
9
  };
10
- var isProd = typeof import.meta !== "undefined" && import.meta.env?.MODE === "production" || process.env.NODE_ENV === "production";
10
+ var metaEnv = typeof import.meta.env !== "undefined" ? import.meta.env : void 0;
11
+ var isProd = metaEnv?.MODE === "production" || typeof process !== "undefined" && process.env?.NODE_ENV === "production";
12
+ var hasWindow = typeof window !== "undefined";
11
13
  var consoleSink = {
12
- track: (event) => console.info("[analytics:track]", event),
13
- page: (name, props) => console.info("[analytics:page]", { name, props })
14
+ track: (event) => console.info(event.name, event.props),
15
+ page: (name, props) => console.info(name, props)
14
16
  };
15
17
  var dataLayerSink = {
16
18
  track: (event) => {
@@ -30,7 +32,7 @@ var dataLayerSink = {
30
32
  });
31
33
  }
32
34
  };
33
- var activeSink = isProd ? dataLayerSink : consoleSink;
35
+ var activeSink = isProd && hasWindow ? dataLayerSink : consoleSink;
34
36
  var setAnalyticsSink = (sink) => {
35
37
  activeSink = sink;
36
38
  };
@@ -84,7 +86,7 @@ var now = () => Date.now();
84
86
  var pageURL = () => typeof location !== "undefined" ? location.href : void 0;
85
87
  var navType = () => {
86
88
  try {
87
- const nav = performance.getEntriesByType("navigation")[0];
89
+ const nav = typeof performance !== "undefined" ? performance.getEntriesByType("navigation")[0] : void 0;
88
90
  return nav?.type ?? performance.navigation?.type;
89
91
  } catch {
90
92
  return void 0;
@@ -196,9 +198,9 @@ function observeLongTasks(track2) {
196
198
  };
197
199
  }
198
200
  function observeResources(track2, sampleRate, filter) {
199
- if (!("PerformanceObserver" in window)) return () => {
201
+ if (typeof window === "undefined" || !("PerformanceObserver" in window)) return () => {
200
202
  };
201
- const shouldSample = Math.max(0, Math.min(1, sampleRate)) || 0.25;
203
+ const shouldSample = Math.min(1, Math.max(0, sampleRate ?? 0.25));
202
204
  const take = () => Math.random() < shouldSample;
203
205
  let obs;
204
206
  const toEvent = (e) => ({
@@ -238,13 +240,20 @@ function wireVisibility(track2) {
238
240
  const onHidden = (type) => () => {
239
241
  track2({ category: "visibility", name: type, ts: now(), url: pageURL() });
240
242
  };
241
- document.addEventListener("visibilitychange", () => {
242
- if (document.visibilityState === "hidden") onHidden("hidden")();
243
- });
244
- window.addEventListener("pagehide", onHidden("pagehide"));
243
+ if (typeof document === "undefined" || typeof window === "undefined") {
244
+ return () => {
245
+ };
246
+ }
247
+ const handleHidden = onHidden("hidden");
248
+ const handleVisibilityChange = () => {
249
+ if (document.visibilityState === "hidden") handleHidden();
250
+ };
251
+ const handlePagehide = onHidden("pagehide");
252
+ document.addEventListener("visibilitychange", handleVisibilityChange);
253
+ window.addEventListener("pagehide", handlePagehide);
245
254
  return () => {
246
- document.removeEventListener("visibilitychange", onHidden("hidden"));
247
- window.removeEventListener("pagehide", onHidden("pagehide"));
255
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
256
+ window.removeEventListener("pagehide", handlePagehide);
248
257
  };
249
258
  }
250
259
  function snapshotNetwork(track2) {
@@ -280,6 +289,11 @@ function snapshotMemory(track2) {
280
289
  });
281
290
  }
282
291
  function initPerformanceTracking(opts) {
292
+ if (typeof window === "undefined" || typeof document === "undefined" || typeof performance === "undefined") {
293
+ const noop = () => {
294
+ };
295
+ return Object.assign(noop, { ready: Promise.resolve() });
296
+ }
283
297
  const {
284
298
  track: track2,
285
299
  resourceSampleRate = 0.25,
@@ -287,7 +301,7 @@ function initPerformanceTracking(opts) {
287
301
  includeNetworkInfo = true,
288
302
  includeMemorySnapshot = false
289
303
  } = opts;
290
- void wireWebVitals(track2);
304
+ const webVitalsReady = wireWebVitals(track2);
291
305
  if (document.readyState === "complete") {
292
306
  reportNavigationTiming(track2);
293
307
  reportPaintTimings(track2);
@@ -302,11 +316,12 @@ function initPerformanceTracking(opts) {
302
316
  const offVis = wireVisibility(track2);
303
317
  if (includeNetworkInfo) snapshotNetwork(track2);
304
318
  if (includeMemorySnapshot) snapshotMemory(track2);
305
- return () => {
319
+ const teardown = () => {
306
320
  offLong();
307
321
  offRes();
308
322
  offVis();
309
323
  };
324
+ return Object.assign(teardown, { ready: webVitalsReady });
310
325
  }
311
326
  export {
312
327
  initPerformanceTracking,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/telemetry/analytics.ts","../src/telemetry/withInteractionTracking.tsx","../src/performance/webVitals.ts"],"sourcesContent":["import { PerfEvent } from \"../performance/index.js\";\n\nexport type AnalyticsEvent = {\n name: string;\n props?: Record<string, unknown>;\n ts?: number;\n};\n\nexport interface AnalyticsSink {\n track: (event: AnalyticsEvent) => void;\n page?: (name: string, props?: Record<string, unknown>) => void;\n}\n\n/**\n * Send a performance event through the active analytics sink.\n * We flatten the payload so `dataLayer`-style sinks can consume it directly.\n */\nexport const trackPerf = (e: PerfEvent) => {\n // Preserve provided timestamp if present; otherwise add one in the sink (or here)\n const { ts, ...rest } = e;\n activeSink.track({\n name: \"perf\",\n props: rest,\n ts: ts ?? Date.now(),\n });\n};\n\nconst isProd =\n (typeof import.meta !== \"undefined\" &&\n (import.meta as any).env?.MODE === \"production\") ||\n process.env.NODE_ENV === \"production\";\n\n// Example sinks\nconst consoleSink: AnalyticsSink = {\n track: (event) => console.info(\"[analytics:track]\", event),\n page: (name, props) => console.info(\"[analytics:page]\", { name, props }),\n};\n\n// Replace with your real one later:\nconst dataLayerSink: AnalyticsSink = {\n track: (event) => {\n // Example: Google Tag Manager dataLayer\n (window as any).dataLayer = (window as any).dataLayer || [];\n (window as any).dataLayer.push({\n event: event.name,\n ...event.props,\n ts: event.ts ?? Date.now(),\n });\n },\n page: (name, props) => {\n (window as any).dataLayer = (window as any).dataLayer || [];\n (window as any).dataLayer.push({\n event: \"page_view\",\n page: name,\n ...props,\n });\n },\n};\n\nlet activeSink: AnalyticsSink = isProd ? dataLayerSink : consoleSink;\n\nexport const setAnalyticsSink = (sink: AnalyticsSink) => {\n activeSink = sink;\n};\n\nexport const track = (name: string, props?: Record<string, unknown>) =>\n activeSink.track({ name, props, ts: Date.now() });\n\nexport const page = (name: string, props?: Record<string, unknown>) =>\n activeSink.page?.(name, props);\n","import React from \"react\";\nimport { track } from \"./analytics.js\";\n\ntype WithInteractionOpts = {\n origin?: string; // e.g. story name\n};\n\nexport function withInteractionTracking<P extends object, R = unknown>(\n Component: React.ForwardRefExoticComponent<\n React.PropsWithoutRef<P> & React.RefAttributes<R>\n >,\n opts?: WithInteractionOpts\n): React.ForwardRefExoticComponent<\n React.PropsWithoutRef<P> & React.RefAttributes<R>\n>;\n\nexport function withInteractionTracking<P extends object>(\n Component: React.ComponentType<P>,\n opts?: WithInteractionOpts\n): React.FC<P>;\n\nexport function withInteractionTracking<P extends object, R = unknown>(\n Component:\n | React.ForwardRefExoticComponent<\n React.PropsWithoutRef<P> & React.RefAttributes<R>\n >\n | React.ComponentType<P>,\n { origin }: WithInteractionOpts = {}\n): any {\n const Wrapped = React.forwardRef<R, P>(function Wrapped(props, ref) {\n const onInteractionCapture = (\n e: React.PointerEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>\n ) => {\n // Normalize event type\n const isKey = (e as React.KeyboardEvent).key !== undefined;\n const isPointer = (e as React.PointerEvent).pointerType !== undefined;\n const type = isKey ? \"keydown\" : isPointer ? \"pointerup\" : e.type;\n\n // Only track meaningful keyboard activations (Enter/Space)\n let key: string | undefined;\n if (isKey) {\n key = (e as React.KeyboardEvent).key;\n if (key !== \"Enter\" && key !== \" \") return; // ignore other keys\n }\n\n // For pointers: only primary button releases\n if (isPointer) {\n const pe = e as React.PointerEvent;\n if (type === \"pointerup\" && pe.button !== 0) return;\n }\n\n const target = e.target as HTMLElement | null;\n const label =\n target?.getAttribute?.(\"aria-label\") ||\n target?.getAttribute?.(\"data-analytics-label\") ||\n target?.textContent?.trim() ||\n target?.tagName;\n\n track(\"ui.interaction\", {\n origin,\n label,\n type,\n key,\n pointerType: isPointer ? (e as React.PointerEvent).pointerType : undefined,\n });\n };\n\n const Comp = Component as any; // Allow ref passing for both ref-aware and plain components\n return (\n <div\n onPointerUpCapture={onInteractionCapture}\n onKeyDownCapture={onInteractionCapture}\n >\n <Comp ref={ref} {...(props as any)} />\n </div>\n );\n });\n\n Wrapped.displayName = `WithInteractionTracking(${Component.displayName || Component.name || \"Component\"})`;\n return Wrapped;\n}\n","/*\n * Performance & Web Vitals tracking\n * ---------------------------------\n * A lightweight collector that wires up:\n * - Core Web Vitals (LCP, INP, CLS, FCP, TTFB) via optional dynamic import of `web-vitals`\n * - Long tasks via PerformanceObserver('longtask')\n * - Navigation timing (TTFB, DOMContentLoaded, load, etc.)\n * - Paint timings (FP, FCP fallback)\n * - Resource timings (optionally sampled)\n * - Visibility lifecycle (hidden, pagehide)\n * - Network info (downlink, rtt, effectiveType) when available\n * - Memory snapshot (JS heap) when available (Chromium only)\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport type VitalName = \"LCP\" | \"INP\" | \"CLS\" | \"FCP\" | \"TTFB\";\nexport type PerfCategory =\n | \"web-vitals\"\n | \"nav-timing\"\n | \"paint\"\n | \"resource\"\n | \"longtask\"\n | \"visibility\"\n | \"snapshot\";\n\nexport type PerfEvent = {\n category: PerfCategory;\n name: string; // e.g., \"LCP\", \"DOMContentLoaded\", \"resource:script\"\n value?: number; // ms for most; CLS is unitless\n rating?: \"good\" | \"needs-improvement\" | \"poor\";\n delta?: number; // for web-vitals deltas\n id?: string; // web-vitals id\n url?: string; // current page\n navigationType?: string; // navigate, reload, back_forward, prerender\n ts?: number; // epoch ms when measured\n details?: Record<string, any>;\n};\n\nexport type PerfTracker = (event: PerfEvent) => void;\n\nexport type InitOptions = {\n track: PerfTracker;\n /** Sample a subset of resource entries to avoid high-volume beacons */\n resourceSampleRate?: number; // 0..1, default 0.25\n /** Predicate to include a resource entry */\n resourceFilter?: (e: PerformanceResourceTiming) => boolean;\n /** Include network information (effectiveType/downlink/rtt) snapshots */\n includeNetworkInfo?: boolean;\n /** Include JS heap memory snapshot where supported */\n includeMemorySnapshot?: boolean;\n};\n\nconst now = () => Date.now();\nconst pageURL = () => (typeof location !== \"undefined\" ? location.href : undefined);\nconst navType = () => {\n try {\n const nav = performance.getEntriesByType(\"navigation\")[0] as PerformanceNavigationTiming | undefined;\n return nav?.type ?? (performance as any).navigation?.type;\n } catch {\n return undefined;\n }\n};\n\n// ---------------------------------------------------------------------------\n// Web Vitals (via optional dynamic import of `web-vitals`)\n// ---------------------------------------------------------------------------\nasync function wireWebVitals(track: PerfTracker) {\n try {\n // @ts-ignore - optional dependency; we ignore TS resolution and rely on runtime try/catch\n const mod: any = await import(/* webpackChunkName: \"web-vitals\" */ \"web-vitals\");\n const handlers: [string, (onReport: any, opts?: any) => void, any?][] = [\n [\"LCP\", mod.onLCP],\n [\"INP\", mod.onINP],\n [\"CLS\", mod.onCLS],\n [\"FCP\", mod.onFCP],\n [\"TTFB\", mod.onTTFB],\n ];\n\n handlers.forEach(([name, fn]) => {\n if (typeof fn === \"function\") {\n fn((metric: any) => {\n const { value, rating, delta, id, navigationType: nt, attribution } = metric;\n track({\n category: \"web-vitals\",\n name,\n value,\n rating,\n delta,\n id,\n url: pageURL(),\n navigationType: nt ?? navType(),\n ts: now(),\n details: attribution ? { attribution } : undefined,\n });\n }, { reportAllChanges: true });\n }\n });\n } catch {\n // `web-vitals` not available — silently skip.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Navigation timing & paint timings\n// ---------------------------------------------------------------------------\nfunction reportNavigationTiming(track: PerfTracker) {\n try {\n const nav = performance.getEntriesByType(\"navigation\")[0] as PerformanceNavigationTiming | undefined;\n if (!nav) return;\n const base = {\n category: \"nav-timing\" as const,\n url: pageURL(),\n navigationType: nav.type,\n ts: now(),\n };\n const push = (name: string, value: number | undefined, details?: any) => {\n if (value == null || Number.isNaN(value)) return;\n track({ ...base, name, value, details });\n };\n\n // Common derived timings (ms)\n push(\"TTFB\", nav.responseStart);\n push(\"DNS\", nav.domainLookupEnd - nav.domainLookupStart);\n push(\"TCP\", nav.connectEnd - nav.connectStart);\n push(\"TLS\", nav.secureConnectionStart > 0 ? nav.connectEnd - nav.secureConnectionStart : 0);\n push(\"Request\", nav.responseStart - nav.requestStart);\n push(\"Response\", nav.responseEnd - nav.responseStart);\n push(\"DOMInteractive\", nav.domInteractive);\n push(\"DOMComplete\", nav.domComplete);\n push(\"DOMContentLoaded\", nav.domContentLoadedEventEnd);\n push(\"LoadEvent\", nav.loadEventEnd - nav.loadEventStart);\n push(\"FirstByteToInteractive\", nav.domInteractive - nav.responseStart);\n } catch {\n // ignore\n }\n}\n\nfunction reportPaintTimings(track: PerfTracker) {\n try {\n const paints = performance.getEntriesByType(\"paint\") as PerformanceEntry[];\n const base = { category: \"paint\" as const, url: pageURL(), navigationType: navType(), ts: now() };\n for (const p of paints) {\n track({ ...base, name: p.name, value: p.startTime });\n }\n } catch {\n // ignore\n }\n}\n\n// ---------------------------------------------------------------------------\n// Long tasks\n// ---------------------------------------------------------------------------\nfunction observeLongTasks(track: PerfTracker): () => void {\n if (!(\"PerformanceObserver\" in window)) return () => {};\n let obs: PerformanceObserver | undefined;\n try {\n obs = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n const anyEntry: any = entry as any;\n track({\n category: \"longtask\",\n name: \"longtask\",\n value: entry.duration,\n ts: now(),\n url: pageURL(),\n details: {\n startTime: entry.startTime,\n duration: entry.duration,\n attribution: anyEntry.attribution ?? anyEntry.attribution ?? undefined,\n },\n });\n }\n });\n obs.observe({ type: \"longtask\", buffered: true as any });\n } catch {\n // ignore\n }\n return () => {\n try { obs?.disconnect(); } catch {}\n };\n}\n\n// ---------------------------------------------------------------------------\n// Resource timings (sampled)\n// ---------------------------------------------------------------------------\nfunction observeResources(track: PerfTracker, sampleRate: number, filter?: (e: PerformanceResourceTiming) => boolean): () => void {\n if (!(\"PerformanceObserver\" in window)) return () => {};\n const shouldSample = (Math.max(0, Math.min(1, sampleRate)) || 0.25);\n const take = () => Math.random() < shouldSample;\n let obs: PerformanceObserver | undefined;\n\n const toEvent = (e: PerformanceResourceTiming): PerfEvent => ({\n category: \"resource\",\n name: `resource:${e.initiatorType || \"other\"}`,\n url: e.name,\n ts: now(),\n value: e.duration,\n details: {\n startTime: e.startTime,\n transferSize: (e as any).transferSize,\n encodedBodySize: (e as any).encodedBodySize,\n decodedBodySize: (e as any).decodedBodySize,\n nextHopProtocol: (e as any).nextHopProtocol,\n initiatorType: e.initiatorType,\n },\n });\n\n try {\n obs = new PerformanceObserver((list) => {\n for (const entry of list.getEntries() as PerformanceResourceTiming[]) {\n if (filter && !filter(entry)) continue;\n if (!take()) continue;\n track(toEvent(entry));\n }\n });\n obs.observe({ type: \"resource\", buffered: true as any });\n } catch {\n // ignore\n }\n return () => { try { obs?.disconnect(); } catch {} };\n}\n\n// ---------------------------------------------------------------------------\n// Visibility & lifecycle\n// ---------------------------------------------------------------------------\nfunction wireVisibility(track: PerfTracker): () => void {\n const onHidden = (type: string) => () => {\n track({ category: \"visibility\", name: type, ts: now(), url: pageURL() });\n };\n document.addEventListener(\"visibilitychange\", () => {\n if (document.visibilityState === \"hidden\") onHidden(\"hidden\")();\n });\n window.addEventListener(\"pagehide\", onHidden(\"pagehide\"));\n return () => {\n document.removeEventListener(\"visibilitychange\", onHidden(\"hidden\"));\n window.removeEventListener(\"pagehide\", onHidden(\"pagehide\"));\n };\n}\n\n// ---------------------------------------------------------------------------\n// Snapshots: Network / Memory\n// ---------------------------------------------------------------------------\nfunction snapshotNetwork(track: PerfTracker) {\n const nav: any = (navigator as any);\n const c = nav?.connection || nav?.mozConnection || nav?.webkitConnection;\n if (!c) return;\n track({\n category: \"snapshot\",\n name: \"network-info\",\n ts: now(),\n url: pageURL(),\n details: {\n effectiveType: c.effectiveType,\n downlink: c.downlink,\n rtt: c.rtt,\n saveData: c.saveData,\n },\n });\n}\n\nfunction snapshotMemory(track: PerfTracker) {\n const mem: any = (performance as any).memory;\n if (!mem) return;\n track({\n category: \"snapshot\",\n name: \"js-heap\",\n ts: now(),\n url: pageURL(),\n details: {\n jsHeapSizeLimit: mem.jsHeapSizeLimit,\n totalJSHeapSize: mem.totalJSHeapSize,\n usedJSHeapSize: mem.usedJSHeapSize,\n },\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\nexport function initPerformanceTracking(opts: InitOptions): () => void {\n const {\n track,\n resourceSampleRate = 0.25,\n resourceFilter,\n includeNetworkInfo = true,\n includeMemorySnapshot = false,\n } = opts;\n\n // Web Vitals (async)\n void wireWebVitals(track);\n\n // Navigation & paints (buffered entries are available after DOM ready)\n if (document.readyState === \"complete\") {\n reportNavigationTiming(track);\n reportPaintTimings(track);\n } else {\n window.addEventListener(\"load\", () => {\n reportNavigationTiming(track);\n reportPaintTimings(track);\n }, { once: true });\n }\n\n // Observers\n const offLong = observeLongTasks(track);\n const offRes = observeResources(track, resourceSampleRate, resourceFilter);\n const offVis = wireVisibility(track);\n\n // Snapshots\n if (includeNetworkInfo) snapshotNetwork(track);\n if (includeMemorySnapshot) snapshotMemory(track);\n\n // Teardown\n return () => {\n offLong();\n offRes();\n offVis();\n };\n}\n\nexport default {\n initPerformanceTracking,\n};\n"],"mappings":";AAiBO,IAAM,YAAY,CAAC,MAAiB;AAEzC,QAAM,EAAE,IAAI,GAAG,KAAK,IAAI;AACxB,aAAW,MAAM;AAAA,IACf,MAAM;AAAA,IACN,OAAO;AAAA,IACP,IAAI,MAAM,KAAK,IAAI;AAAA,EACrB,CAAC;AACH;AAEA,IAAM,SACH,OAAO,gBAAgB,eACrB,YAAoB,KAAK,SAAS,gBACrC,QAAQ,IAAI,aAAa;AAG3B,IAAM,cAA6B;AAAA,EACjC,OAAO,CAAC,UAAU,QAAQ,KAAK,qBAAqB,KAAK;AAAA,EACzD,MAAM,CAAC,MAAM,UAAU,QAAQ,KAAK,oBAAoB,EAAE,MAAM,MAAM,CAAC;AACzE;AAGA,IAAM,gBAA+B;AAAA,EACnC,OAAO,CAAC,UAAU;AAEhB,IAAC,OAAe,YAAa,OAAe,aAAa,CAAC;AAC1D,IAAC,OAAe,UAAU,KAAK;AAAA,MAC7B,OAAO,MAAM;AAAA,MACb,GAAG,MAAM;AAAA,MACT,IAAI,MAAM,MAAM,KAAK,IAAI;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA,EACA,MAAM,CAAC,MAAM,UAAU;AACrB,IAAC,OAAe,YAAa,OAAe,aAAa,CAAC;AAC1D,IAAC,OAAe,UAAU,KAAK;AAAA,MAC7B,OAAO;AAAA,MACP,MAAM;AAAA,MACN,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AACF;AAEA,IAAI,aAA4B,SAAS,gBAAgB;AAElD,IAAM,mBAAmB,CAAC,SAAwB;AACvD,eAAa;AACf;AAEO,IAAM,QAAQ,CAAC,MAAc,UAClC,WAAW,MAAM,EAAE,MAAM,OAAO,IAAI,KAAK,IAAI,EAAE,CAAC;AAE3C,IAAM,OAAO,CAAC,MAAc,UACjC,WAAW,OAAO,MAAM,KAAK;;;ACrE/B,OAAO,WAAW;AAyEV;AApDD,SAAS,wBACd,WAKA,EAAE,OAAO,IAAyB,CAAC,GAC9B;AACL,QAAM,UAAU,MAAM,WAAiB,SAASA,SAAQ,OAAO,KAAK;AAClE,UAAM,uBAAuB,CAC3B,MACG;AAEH,YAAM,QAAS,EAA0B,QAAQ;AACjD,YAAM,YAAa,EAAyB,gBAAgB;AAC5D,YAAM,OAAO,QAAQ,YAAY,YAAY,cAAc,EAAE;AAG7D,UAAI;AACJ,UAAI,OAAO;AACT,cAAO,EAA0B;AACjC,YAAI,QAAQ,WAAW,QAAQ,IAAK;AAAA,MACtC;AAGA,UAAI,WAAW;AACb,cAAM,KAAK;AACX,YAAI,SAAS,eAAe,GAAG,WAAW,EAAG;AAAA,MAC/C;AAEA,YAAM,SAAS,EAAE;AACjB,YAAM,QACJ,QAAQ,eAAe,YAAY,KACnC,QAAQ,eAAe,sBAAsB,KAC7C,QAAQ,aAAa,KAAK,KAC1B,QAAQ;AAEV,YAAM,kBAAkB;AAAA,QACtB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,aAAa,YAAa,EAAyB,cAAc;AAAA,MACnE,CAAC;AAAA,IACH;AAEA,UAAM,OAAO;AACb,WACE;AAAA,MAAC;AAAA;AAAA,QACC,oBAAoB;AAAA,QACpB,kBAAkB;AAAA,QAElB,8BAAC,QAAK,KAAW,GAAI,OAAe;AAAA;AAAA,IACtC;AAAA,EAEJ,CAAC;AAED,UAAQ,cAAc,2BAA2B,UAAU,eAAe,UAAU,QAAQ,WAAW;AACvG,SAAO;AACT;;;AC3BA,IAAM,MAAM,MAAM,KAAK,IAAI;AAC3B,IAAM,UAAU,MAAO,OAAO,aAAa,cAAc,SAAS,OAAO;AACzE,IAAM,UAAU,MAAM;AACpB,MAAI;AACF,UAAM,MAAM,YAAY,iBAAiB,YAAY,EAAE,CAAC;AACxD,WAAO,KAAK,QAAS,YAAoB,YAAY;AAAA,EACvD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAe,cAAcC,QAAoB;AAC/C,MAAI;AAEF,UAAM,MAAW,MAAM;AAAA;AAAA,MAA4C;AAAA,IAAY;AAC/E,UAAM,WAAkE;AAAA,MACtE,CAAC,OAAO,IAAI,KAAK;AAAA,MACjB,CAAC,OAAO,IAAI,KAAK;AAAA,MACjB,CAAC,OAAO,IAAI,KAAK;AAAA,MACjB,CAAC,OAAO,IAAI,KAAK;AAAA,MACjB,CAAC,QAAQ,IAAI,MAAM;AAAA,IACrB;AAEA,aAAS,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM;AAC/B,UAAI,OAAO,OAAO,YAAY;AAC5B,WAAG,CAAC,WAAgB;AAClB,gBAAM,EAAE,OAAO,QAAQ,OAAO,IAAI,gBAAgB,IAAI,YAAY,IAAI;AACtE,UAAAA,OAAM;AAAA,YACJ,UAAU;AAAA,YACV;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,KAAK,QAAQ;AAAA,YACb,gBAAgB,MAAM,QAAQ;AAAA,YAC9B,IAAI,IAAI;AAAA,YACR,SAAS,cAAc,EAAE,YAAY,IAAI;AAAA,UAC3C,CAAC;AAAA,QACH,GAAG,EAAE,kBAAkB,KAAK,CAAC;AAAA,MAC/B;AAAA,IACF,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;AAKA,SAAS,uBAAuBA,QAAoB;AAClD,MAAI;AACF,UAAM,MAAM,YAAY,iBAAiB,YAAY,EAAE,CAAC;AACxD,QAAI,CAAC,IAAK;AACV,UAAM,OAAO;AAAA,MACX,UAAU;AAAA,MACV,KAAK,QAAQ;AAAA,MACb,gBAAgB,IAAI;AAAA,MACpB,IAAI,IAAI;AAAA,IACV;AACA,UAAM,OAAO,CAAC,MAAc,OAA2B,YAAkB;AACvE,UAAI,SAAS,QAAQ,OAAO,MAAM,KAAK,EAAG;AAC1C,MAAAA,OAAM,EAAE,GAAG,MAAM,MAAM,OAAO,QAAQ,CAAC;AAAA,IACzC;AAGA,SAAK,QAAQ,IAAI,aAAa;AAC9B,SAAK,OAAO,IAAI,kBAAkB,IAAI,iBAAiB;AACvD,SAAK,OAAO,IAAI,aAAa,IAAI,YAAY;AAC7C,SAAK,OAAO,IAAI,wBAAwB,IAAI,IAAI,aAAa,IAAI,wBAAwB,CAAC;AAC1F,SAAK,WAAW,IAAI,gBAAgB,IAAI,YAAY;AACpD,SAAK,YAAY,IAAI,cAAc,IAAI,aAAa;AACpD,SAAK,kBAAkB,IAAI,cAAc;AACzC,SAAK,eAAe,IAAI,WAAW;AACnC,SAAK,oBAAoB,IAAI,wBAAwB;AACrD,SAAK,aAAa,IAAI,eAAe,IAAI,cAAc;AACvD,SAAK,0BAA0B,IAAI,iBAAiB,IAAI,aAAa;AAAA,EACvE,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,mBAAmBA,QAAoB;AAC9C,MAAI;AACF,UAAM,SAAS,YAAY,iBAAiB,OAAO;AACnD,UAAM,OAAO,EAAE,UAAU,SAAkB,KAAK,QAAQ,GAAG,gBAAgB,QAAQ,GAAG,IAAI,IAAI,EAAE;AAChG,eAAW,KAAK,QAAQ;AACtB,MAAAA,OAAM,EAAE,GAAG,MAAM,MAAM,EAAE,MAAM,OAAO,EAAE,UAAU,CAAC;AAAA,IACrD;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAKA,SAAS,iBAAiBA,QAAgC;AACxD,MAAI,EAAE,yBAAyB,QAAS,QAAO,MAAM;AAAA,EAAC;AACtD,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,oBAAoB,CAAC,SAAS;AACtC,iBAAW,SAAS,KAAK,WAAW,GAAG;AACrC,cAAM,WAAgB;AACtB,QAAAA,OAAM;AAAA,UACJ,UAAU;AAAA,UACV,MAAM;AAAA,UACN,OAAO,MAAM;AAAA,UACb,IAAI,IAAI;AAAA,UACR,KAAK,QAAQ;AAAA,UACb,SAAS;AAAA,YACP,WAAW,MAAM;AAAA,YACjB,UAAU,MAAM;AAAA,YAChB,aAAa,SAAS,eAAe,SAAS,eAAe;AAAA,UAC/D;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AACD,QAAI,QAAQ,EAAE,MAAM,YAAY,UAAU,KAAY,CAAC;AAAA,EACzD,QAAQ;AAAA,EAER;AACA,SAAO,MAAM;AACX,QAAI;AAAE,WAAK,WAAW;AAAA,IAAG,QAAQ;AAAA,IAAC;AAAA,EACpC;AACF;AAKA,SAAS,iBAAiBA,QAAoB,YAAoB,QAAgE;AAChI,MAAI,EAAE,yBAAyB,QAAS,QAAO,MAAM;AAAA,EAAC;AACtD,QAAM,eAAgB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,UAAU,CAAC,KAAK;AAC9D,QAAM,OAAO,MAAM,KAAK,OAAO,IAAI;AACnC,MAAI;AAEJ,QAAM,UAAU,CAAC,OAA6C;AAAA,IAC5D,UAAU;AAAA,IACV,MAAM,YAAY,EAAE,iBAAiB,OAAO;AAAA,IAC5C,KAAK,EAAE;AAAA,IACP,IAAI,IAAI;AAAA,IACR,OAAO,EAAE;AAAA,IACT,SAAS;AAAA,MACP,WAAW,EAAE;AAAA,MACb,cAAe,EAAU;AAAA,MACzB,iBAAkB,EAAU;AAAA,MAC5B,iBAAkB,EAAU;AAAA,MAC5B,iBAAkB,EAAU;AAAA,MAC5B,eAAe,EAAE;AAAA,IACnB;AAAA,EACF;AAEA,MAAI;AACF,UAAM,IAAI,oBAAoB,CAAC,SAAS;AACtC,iBAAW,SAAS,KAAK,WAAW,GAAkC;AACpE,YAAI,UAAU,CAAC,OAAO,KAAK,EAAG;AAC9B,YAAI,CAAC,KAAK,EAAG;AACb,QAAAA,OAAM,QAAQ,KAAK,CAAC;AAAA,MACtB;AAAA,IACF,CAAC;AACD,QAAI,QAAQ,EAAE,MAAM,YAAY,UAAU,KAAY,CAAC;AAAA,EACzD,QAAQ;AAAA,EAER;AACA,SAAO,MAAM;AAAE,QAAI;AAAE,WAAK,WAAW;AAAA,IAAG,QAAQ;AAAA,IAAC;AAAA,EAAE;AACrD;AAKA,SAAS,eAAeA,QAAgC;AACtD,QAAM,WAAW,CAAC,SAAiB,MAAM;AACvC,IAAAA,OAAM,EAAE,UAAU,cAAc,MAAM,MAAM,IAAI,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;AAAA,EACzE;AACA,WAAS,iBAAiB,oBAAoB,MAAM;AAClD,QAAI,SAAS,oBAAoB,SAAU,UAAS,QAAQ,EAAE;AAAA,EAChE,CAAC;AACD,SAAO,iBAAiB,YAAY,SAAS,UAAU,CAAC;AACxD,SAAO,MAAM;AACX,aAAS,oBAAoB,oBAAoB,SAAS,QAAQ,CAAC;AACnE,WAAO,oBAAoB,YAAY,SAAS,UAAU,CAAC;AAAA,EAC7D;AACF;AAKA,SAAS,gBAAgBA,QAAoB;AAC3C,QAAM,MAAY;AAClB,QAAM,IAAI,KAAK,cAAc,KAAK,iBAAiB,KAAK;AACxD,MAAI,CAAC,EAAG;AACR,EAAAA,OAAM;AAAA,IACJ,UAAU;AAAA,IACV,MAAM;AAAA,IACN,IAAI,IAAI;AAAA,IACR,KAAK,QAAQ;AAAA,IACb,SAAS;AAAA,MACP,eAAe,EAAE;AAAA,MACjB,UAAU,EAAE;AAAA,MACZ,KAAK,EAAE;AAAA,MACP,UAAU,EAAE;AAAA,IACd;AAAA,EACF,CAAC;AACH;AAEA,SAAS,eAAeA,QAAoB;AAC1C,QAAM,MAAY,YAAoB;AACtC,MAAI,CAAC,IAAK;AACV,EAAAA,OAAM;AAAA,IACJ,UAAU;AAAA,IACV,MAAM;AAAA,IACN,IAAI,IAAI;AAAA,IACR,KAAK,QAAQ;AAAA,IACb,SAAS;AAAA,MACP,iBAAiB,IAAI;AAAA,MACrB,iBAAiB,IAAI;AAAA,MACrB,gBAAgB,IAAI;AAAA,IACtB;AAAA,EACF,CAAC;AACH;AAKO,SAAS,wBAAwB,MAA+B;AACrE,QAAM;AAAA,IACJ,OAAAA;AAAA,IACA,qBAAqB;AAAA,IACrB;AAAA,IACA,qBAAqB;AAAA,IACrB,wBAAwB;AAAA,EAC1B,IAAI;AAGJ,OAAK,cAAcA,MAAK;AAGxB,MAAI,SAAS,eAAe,YAAY;AACtC,2BAAuBA,MAAK;AAC5B,uBAAmBA,MAAK;AAAA,EAC1B,OAAO;AACL,WAAO,iBAAiB,QAAQ,MAAM;AACpC,6BAAuBA,MAAK;AAC5B,yBAAmBA,MAAK;AAAA,IAC1B,GAAG,EAAE,MAAM,KAAK,CAAC;AAAA,EACnB;AAGA,QAAM,UAAU,iBAAiBA,MAAK;AACtC,QAAM,SAAS,iBAAiBA,QAAO,oBAAoB,cAAc;AACzE,QAAM,SAAS,eAAeA,MAAK;AAGnC,MAAI,mBAAoB,iBAAgBA,MAAK;AAC7C,MAAI,sBAAuB,gBAAeA,MAAK;AAG/C,SAAO,MAAM;AACX,YAAQ;AACR,WAAO;AACP,WAAO;AAAA,EACT;AACF;","names":["Wrapped","track"]}
1
+ {"version":3,"sources":["../src/telemetry/analytics.ts","../src/telemetry/withInteractionTracking.tsx","../src/performance/webVitals.ts"],"sourcesContent":["import { PerfEvent } from \"../performance/index.js\";\n\ndeclare const __IMPORT_META_ENV__: { MODE?: string } | undefined;\n\nexport type AnalyticsEvent = {\n name: string;\n props?: Record<string, unknown>;\n ts?: number;\n};\n\nexport interface AnalyticsSink {\n track: (event: AnalyticsEvent) => void;\n page?: (name: string, props?: Record<string, unknown>) => void;\n}\n\n/**\n * Send a performance event through the active analytics sink.\n * We flatten the payload so `dataLayer`-style sinks can consume it directly.\n */\nexport const trackPerf = (e: PerfEvent) => {\n // Preserve provided timestamp if present; otherwise add one in the sink (or here)\n const { ts, ...rest } = e;\n activeSink.track({\n name: \"perf\",\n props: rest,\n ts: ts ?? Date.now(),\n });\n};\n\nconst metaEnv =\n typeof __IMPORT_META_ENV__ !== \"undefined\" ? __IMPORT_META_ENV__ : undefined;\nconst isProd =\n metaEnv?.MODE === \"production\" ||\n (typeof process !== \"undefined\" && process.env?.NODE_ENV === \"production\");\nconst hasWindow = typeof window !== \"undefined\";\n\n// Example sinks\nconst consoleSink: AnalyticsSink = {\n track: (event) => console.info(event.name, event.props),\n page: (name, props) => console.info(name, props),\n};\n\n// Replace with your real one later:\nconst dataLayerSink: AnalyticsSink = {\n track: (event) => {\n // Example: Google Tag Manager dataLayer\n (window as any).dataLayer = (window as any).dataLayer || [];\n (window as any).dataLayer.push({\n event: event.name,\n ...event.props,\n ts: event.ts ?? Date.now(),\n });\n },\n page: (name, props) => {\n (window as any).dataLayer = (window as any).dataLayer || [];\n (window as any).dataLayer.push({\n event: \"page_view\",\n page: name,\n ...props,\n });\n },\n};\n\nlet activeSink: AnalyticsSink = isProd && hasWindow ? dataLayerSink : consoleSink;\n\nexport const setAnalyticsSink = (sink: AnalyticsSink) => {\n activeSink = sink;\n};\n\nexport const track = (name: string, props?: Record<string, unknown>) =>\n activeSink.track({ name, props, ts: Date.now() });\n\nexport const page = (name: string, props?: Record<string, unknown>) =>\n activeSink.page?.(name, props);\n","import React from \"react\";\nimport { track } from \"./analytics.js\";\n\ntype WithInteractionOpts = {\n origin?: string; // e.g. story name\n};\n\nexport function withInteractionTracking<P extends object, R = unknown>(\n Component: React.ForwardRefExoticComponent<\n React.PropsWithoutRef<P> & React.RefAttributes<R>\n >,\n opts?: WithInteractionOpts\n): React.ForwardRefExoticComponent<\n React.PropsWithoutRef<P> & React.RefAttributes<R>\n>;\n\nexport function withInteractionTracking<P extends object>(\n Component: React.ComponentType<P>,\n opts?: WithInteractionOpts\n): React.FC<P>;\n\nexport function withInteractionTracking<P extends object, R = unknown>(\n Component:\n | React.ForwardRefExoticComponent<\n React.PropsWithoutRef<P> & React.RefAttributes<R>\n >\n | React.ComponentType<P>,\n { origin }: WithInteractionOpts = {}\n): any {\n const Wrapped = React.forwardRef<R, P>(function Wrapped(props, ref) {\n const onInteractionCapture = (\n e: React.PointerEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>\n ) => {\n // Normalize event type\n const isKey = (e as React.KeyboardEvent).key !== undefined;\n const isPointer = (e as React.PointerEvent).pointerType !== undefined;\n const type = isKey ? \"keydown\" : isPointer ? \"pointerup\" : e.type;\n\n // Only track meaningful keyboard activations (Enter/Space)\n let key: string | undefined;\n if (isKey) {\n key = (e as React.KeyboardEvent).key;\n if (key !== \"Enter\" && key !== \" \") return; // ignore other keys\n }\n\n // For pointers: only primary button releases\n if (isPointer) {\n const pe = e as React.PointerEvent;\n if (type === \"pointerup\" && pe.button !== 0) return;\n }\n\n const target = e.target as HTMLElement | null;\n const label =\n target?.getAttribute?.(\"aria-label\") ||\n target?.getAttribute?.(\"data-analytics-label\") ||\n target?.textContent?.trim() ||\n target?.tagName;\n\n track(\"ui.interaction\", {\n origin,\n label,\n type,\n key,\n pointerType: isPointer ? (e as React.PointerEvent).pointerType : undefined,\n });\n };\n\n const Comp = Component as any; // Allow ref passing for both ref-aware and plain components\n return (\n <div\n onPointerUpCapture={onInteractionCapture}\n onKeyDownCapture={onInteractionCapture}\n >\n <Comp ref={ref} {...(props as any)} />\n </div>\n );\n });\n\n Wrapped.displayName = `WithInteractionTracking(${Component.displayName || Component.name || \"Component\"})`;\n return Wrapped;\n}\n","/*\n * Performance & Web Vitals tracking\n * ---------------------------------\n * A lightweight collector that wires up:\n * - Core Web Vitals (LCP, INP, CLS, FCP, TTFB) via optional dynamic import of `web-vitals`\n * - Long tasks via PerformanceObserver('longtask')\n * - Navigation timing (TTFB, DOMContentLoaded, load, etc.)\n * - Paint timings (FP, FCP fallback)\n * - Resource timings (optionally sampled)\n * - Visibility lifecycle (hidden, pagehide)\n * - Network info (downlink, rtt, effectiveType) when available\n * - Memory snapshot (JS heap) when available (Chromium only)\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport type VitalName = \"LCP\" | \"INP\" | \"CLS\" | \"FCP\" | \"TTFB\";\nexport type PerfCategory =\n | \"web-vitals\"\n | \"nav-timing\"\n | \"paint\"\n | \"resource\"\n | \"longtask\"\n | \"visibility\"\n | \"snapshot\";\n\nexport type PerfEvent = {\n category: PerfCategory;\n name: string; // e.g., \"LCP\", \"DOMContentLoaded\", \"resource:script\"\n value?: number; // ms for most; CLS is unitless\n rating?: \"good\" | \"needs-improvement\" | \"poor\";\n delta?: number; // for web-vitals deltas\n id?: string; // web-vitals id\n url?: string; // current page\n navigationType?: string; // navigate, reload, back_forward, prerender\n ts?: number; // epoch ms when measured\n details?: Record<string, any>;\n};\n\nexport type PerfTracker = (event: PerfEvent) => void;\nexport type PerfTeardown = (() => void) & { ready: Promise<void> };\n\nexport type InitOptions = {\n track: PerfTracker;\n /** Sample a subset of resource entries to avoid high-volume beacons */\n resourceSampleRate?: number; // 0..1, default 0.25\n /** Predicate to include a resource entry */\n resourceFilter?: (e: PerformanceResourceTiming) => boolean;\n /** Include network information (effectiveType/downlink/rtt) snapshots */\n includeNetworkInfo?: boolean;\n /** Include JS heap memory snapshot where supported */\n includeMemorySnapshot?: boolean;\n};\n\nconst now = () => Date.now();\nconst pageURL = () => (typeof location !== \"undefined\" ? location.href : undefined);\nconst navType = () => {\n try {\n const nav =\n typeof performance !== \"undefined\"\n ? (performance.getEntriesByType(\"navigation\")[0] as PerformanceNavigationTiming | undefined)\n : undefined;\n return nav?.type ?? (performance as any).navigation?.type;\n } catch {\n return undefined;\n }\n};\n\n// ---------------------------------------------------------------------------\n// Web Vitals (via optional dynamic import of `web-vitals`)\n// ---------------------------------------------------------------------------\nasync function wireWebVitals(track: PerfTracker) {\n try {\n // @ts-ignore - optional dependency; we ignore TS resolution and rely on runtime try/catch\n const mod: any = await import(/* webpackChunkName: \"web-vitals\" */ \"web-vitals\");\n const handlers: [string, (onReport: any, opts?: any) => void, any?][] = [\n [\"LCP\", mod.onLCP],\n [\"INP\", mod.onINP],\n [\"CLS\", mod.onCLS],\n [\"FCP\", mod.onFCP],\n [\"TTFB\", mod.onTTFB],\n ];\n\n handlers.forEach(([name, fn]) => {\n if (typeof fn === \"function\") {\n fn((metric: any) => {\n const { value, rating, delta, id, navigationType: nt, attribution } = metric;\n track({\n category: \"web-vitals\",\n name,\n value,\n rating,\n delta,\n id,\n url: pageURL(),\n navigationType: nt ?? navType(),\n ts: now(),\n details: attribution ? { attribution } : undefined,\n });\n }, { reportAllChanges: true });\n }\n });\n } catch {\n // `web-vitals` not available — silently skip.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Navigation timing & paint timings\n// ---------------------------------------------------------------------------\nfunction reportNavigationTiming(track: PerfTracker) {\n try {\n const nav = performance.getEntriesByType(\"navigation\")[0] as PerformanceNavigationTiming | undefined;\n if (!nav) return;\n const base = {\n category: \"nav-timing\" as const,\n url: pageURL(),\n navigationType: nav.type,\n ts: now(),\n };\n const push = (name: string, value: number | undefined, details?: any) => {\n if (value == null || Number.isNaN(value)) return;\n track({ ...base, name, value, details });\n };\n\n // Common derived timings (ms)\n push(\"TTFB\", nav.responseStart);\n push(\"DNS\", nav.domainLookupEnd - nav.domainLookupStart);\n push(\"TCP\", nav.connectEnd - nav.connectStart);\n push(\"TLS\", nav.secureConnectionStart > 0 ? nav.connectEnd - nav.secureConnectionStart : 0);\n push(\"Request\", nav.responseStart - nav.requestStart);\n push(\"Response\", nav.responseEnd - nav.responseStart);\n push(\"DOMInteractive\", nav.domInteractive);\n push(\"DOMComplete\", nav.domComplete);\n push(\"DOMContentLoaded\", nav.domContentLoadedEventEnd);\n push(\"LoadEvent\", nav.loadEventEnd - nav.loadEventStart);\n push(\"FirstByteToInteractive\", nav.domInteractive - nav.responseStart);\n } catch {\n // ignore\n }\n}\n\nfunction reportPaintTimings(track: PerfTracker) {\n try {\n const paints = performance.getEntriesByType(\"paint\") as PerformanceEntry[];\n const base = { category: \"paint\" as const, url: pageURL(), navigationType: navType(), ts: now() };\n for (const p of paints) {\n track({ ...base, name: p.name, value: p.startTime });\n }\n } catch {\n // ignore\n }\n}\n\n// ---------------------------------------------------------------------------\n// Long tasks\n// ---------------------------------------------------------------------------\nfunction observeLongTasks(track: PerfTracker): () => void {\n if (!(\"PerformanceObserver\" in window)) return () => {};\n let obs: PerformanceObserver | undefined;\n try {\n obs = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n const anyEntry: any = entry as any;\n track({\n category: \"longtask\",\n name: \"longtask\",\n value: entry.duration,\n ts: now(),\n url: pageURL(),\n details: {\n startTime: entry.startTime,\n duration: entry.duration,\n attribution: anyEntry.attribution ?? anyEntry.attribution ?? undefined,\n },\n });\n }\n });\n obs.observe({ type: \"longtask\", buffered: true as any });\n } catch {\n // ignore\n }\n return () => {\n try { obs?.disconnect(); } catch {}\n };\n}\n\n// ---------------------------------------------------------------------------\n// Resource timings (sampled)\n// ---------------------------------------------------------------------------\nfunction observeResources(track: PerfTracker, sampleRate: number, filter?: (e: PerformanceResourceTiming) => boolean): () => void {\n if (typeof window === \"undefined\" || !(\"PerformanceObserver\" in window)) return () => {};\n const shouldSample = Math.min(1, Math.max(0, sampleRate ?? 0.25));\n const take = () => Math.random() < shouldSample;\n let obs: PerformanceObserver | undefined;\n\n const toEvent = (e: PerformanceResourceTiming): PerfEvent => ({\n category: \"resource\",\n name: `resource:${e.initiatorType || \"other\"}`,\n url: e.name,\n ts: now(),\n value: e.duration,\n details: {\n startTime: e.startTime,\n transferSize: (e as any).transferSize,\n encodedBodySize: (e as any).encodedBodySize,\n decodedBodySize: (e as any).decodedBodySize,\n nextHopProtocol: (e as any).nextHopProtocol,\n initiatorType: e.initiatorType,\n },\n });\n\n try {\n obs = new PerformanceObserver((list) => {\n for (const entry of list.getEntries() as PerformanceResourceTiming[]) {\n if (filter && !filter(entry)) continue;\n if (!take()) continue;\n track(toEvent(entry));\n }\n });\n obs.observe({ type: \"resource\", buffered: true as any });\n } catch {\n // ignore\n }\n return () => { try { obs?.disconnect(); } catch {} };\n}\n\n// ---------------------------------------------------------------------------\n// Visibility & lifecycle\n// ---------------------------------------------------------------------------\nfunction wireVisibility(track: PerfTracker): () => void {\n const onHidden = (type: string) => () => {\n track({ category: \"visibility\", name: type, ts: now(), url: pageURL() });\n };\n\n if (typeof document === \"undefined\" || typeof window === \"undefined\") {\n return () => {};\n }\n\n const handleHidden = onHidden(\"hidden\");\n const handleVisibilityChange = () => {\n if (document.visibilityState === \"hidden\") handleHidden();\n };\n const handlePagehide = onHidden(\"pagehide\");\n\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n window.addEventListener(\"pagehide\", handlePagehide);\n return () => {\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n window.removeEventListener(\"pagehide\", handlePagehide);\n };\n}\n\n// ---------------------------------------------------------------------------\n// Snapshots: Network / Memory\n// ---------------------------------------------------------------------------\nfunction snapshotNetwork(track: PerfTracker) {\n const nav: any = (navigator as any);\n const c = nav?.connection || nav?.mozConnection || nav?.webkitConnection;\n if (!c) return;\n track({\n category: \"snapshot\",\n name: \"network-info\",\n ts: now(),\n url: pageURL(),\n details: {\n effectiveType: c.effectiveType,\n downlink: c.downlink,\n rtt: c.rtt,\n saveData: c.saveData,\n },\n });\n}\n\nfunction snapshotMemory(track: PerfTracker) {\n const mem: any = (performance as any).memory;\n if (!mem) return;\n track({\n category: \"snapshot\",\n name: \"js-heap\",\n ts: now(),\n url: pageURL(),\n details: {\n jsHeapSizeLimit: mem.jsHeapSizeLimit,\n totalJSHeapSize: mem.totalJSHeapSize,\n usedJSHeapSize: mem.usedJSHeapSize,\n },\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\nexport function initPerformanceTracking(opts: InitOptions): PerfTeardown {\n if (typeof window === \"undefined\" || typeof document === \"undefined\" || typeof performance === \"undefined\") {\n // SSR / non-DOM environment: no-op to avoid crashes\n const noop = () => {};\n return Object.assign(noop, { ready: Promise.resolve() });\n }\n\n const {\n track,\n resourceSampleRate = 0.25,\n resourceFilter,\n includeNetworkInfo = true,\n includeMemorySnapshot = false,\n } = opts;\n\n // Web Vitals (async)\n const webVitalsReady = wireWebVitals(track);\n\n // Navigation & paints (buffered entries are available after DOM ready)\n if (document.readyState === \"complete\") {\n reportNavigationTiming(track);\n reportPaintTimings(track);\n } else {\n window.addEventListener(\"load\", () => {\n reportNavigationTiming(track);\n reportPaintTimings(track);\n }, { once: true });\n }\n\n // Observers\n const offLong = observeLongTasks(track);\n const offRes = observeResources(track, resourceSampleRate, resourceFilter);\n const offVis = wireVisibility(track);\n\n // Snapshots\n if (includeNetworkInfo) snapshotNetwork(track);\n if (includeMemorySnapshot) snapshotMemory(track);\n\n // Teardown\n const teardown = () => {\n offLong();\n offRes();\n offVis();\n };\n return Object.assign(teardown, { ready: webVitalsReady });\n}\n\nexport default {\n initPerformanceTracking,\n};\n"],"mappings":"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","names":["Wrapped","track"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plasius/nfr",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "a small, typed Non-Functional-Requirement (NFR) framework that cleanly adapts to different cloud vendors (GA4, PostHog, Azure Application Insights, etc.) and different concerns (analytics/events, metrics, tracing, logs, feature flags, performance).",
5
5
  "homepage": "https://github.com/Plasius-LTD/nfr#readme",
6
6
  "bugs": {
@@ -14,6 +14,9 @@
14
14
  "author": "Plasius LTD",
15
15
  "type": "module",
16
16
  "sideEffects": false,
17
+ "main": "./dist/index.cjs",
18
+ "module": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
17
20
  "private": false,
18
21
  "files": [
19
22
  "dist"
package/dist/index.d.cts DELETED
@@ -1,55 +0,0 @@
1
- import React from 'react';
2
-
3
- type VitalName = "LCP" | "INP" | "CLS" | "FCP" | "TTFB";
4
- type PerfCategory = "web-vitals" | "nav-timing" | "paint" | "resource" | "longtask" | "visibility" | "snapshot";
5
- type PerfEvent = {
6
- category: PerfCategory;
7
- name: string;
8
- value?: number;
9
- rating?: "good" | "needs-improvement" | "poor";
10
- delta?: number;
11
- id?: string;
12
- url?: string;
13
- navigationType?: string;
14
- ts?: number;
15
- details?: Record<string, any>;
16
- };
17
- type PerfTracker = (event: PerfEvent) => void;
18
- type InitOptions = {
19
- track: PerfTracker;
20
- /** Sample a subset of resource entries to avoid high-volume beacons */
21
- resourceSampleRate?: number;
22
- /** Predicate to include a resource entry */
23
- resourceFilter?: (e: PerformanceResourceTiming) => boolean;
24
- /** Include network information (effectiveType/downlink/rtt) snapshots */
25
- includeNetworkInfo?: boolean;
26
- /** Include JS heap memory snapshot where supported */
27
- includeMemorySnapshot?: boolean;
28
- };
29
- declare function initPerformanceTracking(opts: InitOptions): () => void;
30
-
31
- type AnalyticsEvent = {
32
- name: string;
33
- props?: Record<string, unknown>;
34
- ts?: number;
35
- };
36
- interface AnalyticsSink {
37
- track: (event: AnalyticsEvent) => void;
38
- page?: (name: string, props?: Record<string, unknown>) => void;
39
- }
40
- /**
41
- * Send a performance event through the active analytics sink.
42
- * We flatten the payload so `dataLayer`-style sinks can consume it directly.
43
- */
44
- declare const trackPerf: (e: PerfEvent) => void;
45
- declare const setAnalyticsSink: (sink: AnalyticsSink) => void;
46
- declare const track: (name: string, props?: Record<string, unknown>) => void;
47
- declare const page: (name: string, props?: Record<string, unknown>) => void | undefined;
48
-
49
- type WithInteractionOpts = {
50
- origin?: string;
51
- };
52
- declare function withInteractionTracking<P extends object, R = unknown>(Component: React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<R>>, opts?: WithInteractionOpts): React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<R>>;
53
- declare function withInteractionTracking<P extends object>(Component: React.ComponentType<P>, opts?: WithInteractionOpts): React.FC<P>;
54
-
55
- export { type AnalyticsEvent, type AnalyticsSink, type InitOptions, type PerfCategory, type PerfEvent, type PerfTracker, type VitalName, initPerformanceTracking, page, setAnalyticsSink, track, trackPerf, withInteractionTracking };