@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 +26 -11
- package/dist/index.cjs +30 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +30 -15
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
- package/dist/index.d.cts +0 -55
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @plasius/nfr
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@plasius/nfr)
|
|
4
|
-
[](https://github.com/
|
|
4
|
+
[](https://github.com/Plasius-LTD/nfr/actions/workflows/ci.yml)
|
|
5
5
|
[](https://codecov.io/gh/Plasius-LTD/nfr)
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
[](./CODE_OF_CONDUCT.md)
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
## Overview
|
|
14
14
|
|
|
15
|
-
`@plasius/nfr` provides
|
|
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
|
-
```
|
|
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
|
|
36
|
-
|
|
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: "
|
|
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
|
|
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(
|
|
55
|
-
page: (name, props) => console.info(
|
|
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.
|
|
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
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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",
|
|
289
|
-
window.removeEventListener("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
|
-
|
|
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
|
-
|
|
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 = {
|
package/dist/index.cjs.map
CHANGED
|
@@ -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):
|
|
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
|
|
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(
|
|
13
|
-
page: (name, props) => console.info(
|
|
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.
|
|
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
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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",
|
|
247
|
-
window.removeEventListener("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
|
-
|
|
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
|
-
|
|
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":"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","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.
|
|
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 };
|