@interfere/react 10.0.0 → 10.0.1-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.mts.map +1 -1
- package/dist/api.mjs +1 -68
- package/dist/api.mjs.map +1 -1
- package/dist/error-boundary.d.mts +1 -2
- package/dist/error-boundary.d.mts.map +1 -1
- package/dist/error-boundary.mjs +1 -42
- package/dist/error-boundary.mjs.map +1 -1
- package/dist/internal/browser-context.d.mts.map +1 -1
- package/dist/internal/browser-context.mjs +1 -59
- package/dist/internal/browser-context.mjs.map +1 -1
- package/dist/internal/capture-boundary.d.mts +1 -2
- package/dist/internal/capture-boundary.d.mts.map +1 -1
- package/dist/internal/capture-boundary.mjs +1 -48
- package/dist/internal/capture-boundary.mjs.map +1 -1
- package/dist/internal/capture.mjs +1 -27
- package/dist/internal/capture.mjs.map +1 -1
- package/dist/internal/config.d.mts +3 -1
- package/dist/internal/config.d.mts.map +1 -1
- package/dist/internal/config.mjs +1 -33
- package/dist/internal/config.mjs.map +1 -1
- package/dist/internal/consent.d.mts.map +1 -1
- package/dist/internal/consent.mjs +1 -27
- package/dist/internal/consent.mjs.map +1 -1
- package/dist/internal/console-patch.d.mts.map +1 -1
- package/dist/internal/console-patch.mjs +1 -62
- package/dist/internal/console-patch.mjs.map +1 -1
- package/dist/internal/dom/actionable.d.mts.map +1 -1
- package/dist/internal/dom/actionable.mjs +1 -62
- package/dist/internal/dom/actionable.mjs.map +1 -1
- package/dist/internal/kernel-registry.d.mts.map +1 -1
- package/dist/internal/kernel-registry.mjs +1 -31
- package/dist/internal/kernel-registry.mjs.map +1 -1
- package/dist/internal/kernel.d.mts +1 -1
- package/dist/internal/kernel.d.mts.map +1 -1
- package/dist/internal/kernel.mjs +1 -322
- package/dist/internal/kernel.mjs.map +1 -1
- package/dist/internal/otel/exporter.d.mts +4 -12
- package/dist/internal/otel/exporter.d.mts.map +1 -1
- package/dist/internal/otel/exporter.mjs +1 -212
- package/dist/internal/otel/exporter.mjs.map +1 -1
- package/dist/internal/otel/index.mjs +1 -6
- package/dist/internal/otel/instrumentations.d.mts.map +1 -1
- package/dist/internal/otel/instrumentations.mjs +1 -150
- package/dist/internal/otel/instrumentations.mjs.map +1 -1
- package/dist/internal/otel/page-scope-context-manager.d.mts.map +1 -1
- package/dist/internal/otel/page-scope-context-manager.mjs +1 -36
- package/dist/internal/otel/page-scope-context-manager.mjs.map +1 -1
- package/dist/internal/otel/propagation.d.mts.map +1 -1
- package/dist/internal/otel/propagation.mjs +1 -40
- package/dist/internal/otel/propagation.mjs.map +1 -1
- package/dist/internal/otel/provider.d.mts +1 -2
- package/dist/internal/otel/provider.d.mts.map +1 -1
- package/dist/internal/otel/provider.mjs +1 -151
- package/dist/internal/otel/provider.mjs.map +1 -1
- package/dist/internal/otel/web-vitals.d.mts.map +1 -1
- package/dist/internal/otel/web-vitals.mjs +1 -162
- package/dist/internal/otel/web-vitals.mjs.map +1 -1
- package/dist/internal/page-lifecycle.d.mts.map +1 -1
- package/dist/internal/page-lifecycle.mjs +1 -33
- package/dist/internal/page-lifecycle.mjs.map +1 -1
- package/dist/internal/plugin-runtime.mjs +1 -101
- package/dist/internal/plugin-runtime.mjs.map +1 -1
- package/dist/internal/react-context.d.mts +1 -2
- package/dist/internal/react-context.d.mts.map +1 -1
- package/dist/internal/react-context.mjs +1 -34
- package/dist/internal/react-context.mjs.map +1 -1
- package/dist/internal/sw.d.mts.map +1 -1
- package/dist/internal/sw.mjs +1 -37
- package/dist/internal/sw.mjs.map +1 -1
- package/dist/internal/version.mjs +1 -7
- package/dist/internal/version.mjs.map +1 -1
- package/dist/internal/wrapper-singleton.d.mts.map +1 -1
- package/dist/internal/wrapper-singleton.mjs +1 -73
- package/dist/internal/wrapper-singleton.mjs.map +1 -1
- package/dist/package.mjs +1 -5
- package/dist/plugins/errors.d.mts.map +1 -1
- package/dist/plugins/errors.mjs +1 -84
- package/dist/plugins/errors.mjs.map +1 -1
- package/dist/plugins/lib/loader.mjs +1 -34
- package/dist/plugins/lib/loader.mjs.map +1 -1
- package/dist/plugins/lib/types.d.mts.map +1 -1
- package/dist/plugins/lib/types.mjs +1 -1
- package/dist/plugins/logs.d.mts.map +1 -1
- package/dist/plugins/logs.mjs +1 -53
- package/dist/plugins/logs.mjs.map +1 -1
- package/dist/plugins/rage-clicks.d.mts.map +1 -1
- package/dist/plugins/rage-clicks.mjs +1 -55
- package/dist/plugins/rage-clicks.mjs.map +1 -1
- package/dist/plugins/replay.d.mts.map +1 -1
- package/dist/plugins/replay.mjs +1 -101
- package/dist/plugins/replay.mjs.map +1 -1
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +1 -31
- package/dist/provider.mjs.map +1 -1
- package/dist/react-error-handler.d.mts.map +1 -1
- package/dist/react-error-handler.mjs +1 -62
- package/dist/react-error-handler.mjs.map +1 -1
- package/dist/tracking/api.d.mts.map +1 -1
- package/dist/tracking/api.mjs +1 -152
- package/dist/tracking/api.mjs.map +1 -1
- package/dist/tracking/device.d.mts.map +1 -1
- package/dist/tracking/device.mjs +1 -104
- package/dist/tracking/device.mjs.map +1 -1
- package/dist/tracking/geo.d.mts.map +1 -1
- package/dist/tracking/geo.mjs +2 -48
- package/dist/tracking/geo.mjs.map +1 -1
- package/dist/tracking/session.d.mts.map +1 -1
- package/dist/tracking/session.mjs +1 -75
- package/dist/tracking/session.mjs.map +1 -1
- package/dist/util/bot.d.mts.map +1 -1
- package/dist/util/bot.mjs +1 -14
- package/dist/util/bot.mjs.map +1 -1
- package/dist/util/global.d.mts.map +1 -1
- package/dist/util/global.mjs +1 -12
- package/dist/util/global.mjs.map +1 -1
- package/dist/util/log.d.mts.map +1 -1
- package/dist/util/log.mjs +1 -44
- package/dist/util/log.mjs.map +1 -1
- package/dist/util/stringify.d.mts.map +1 -1
- package/dist/util/stringify.mjs +1 -16
- package/dist/util/stringify.mjs.map +1 -1
- package/package.json +34 -33
|
@@ -1,162 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
//#region src/internal/otel/web-vitals.ts
|
|
3
|
-
const ATTR_URL_PATH = "url.path";
|
|
4
|
-
const MS_TO_S = 1 / 1e3;
|
|
5
|
-
/**
|
|
6
|
-
* Vitals that fire during the page-load sequence — once we've received
|
|
7
|
-
* all three we issue a one-shot flush so short visits still export
|
|
8
|
-
* data. CLS and INP fire on `visibilitychange` which the kernel
|
|
9
|
-
* already auto-flushes on, so they don't need their own trigger.
|
|
10
|
-
*/
|
|
11
|
-
const LOAD_VITALS = new Set([
|
|
12
|
-
"TTFB",
|
|
13
|
-
"FCP",
|
|
14
|
-
"LCP"
|
|
15
|
-
]);
|
|
16
|
-
/**
|
|
17
|
-
* Subscribes to Core Web Vitals (LCP/FCP/TTFB/INP/CLS) and records OTel
|
|
18
|
-
* histograms. Time-based vitals are converted to seconds per
|
|
19
|
-
* observability convention; CLS is dimensionless and recorded as-is.
|
|
20
|
-
*
|
|
21
|
-
* Each record carries `web_vital.rating`, `web_vital.navigation_type`,
|
|
22
|
-
* and `url.path` (templated when `resolveRoute` is provided). Browser
|
|
23
|
-
* and device dimensions come from the resource attached to the
|
|
24
|
-
* MeterProvider, so every histogram datapoint already carries them.
|
|
25
|
-
*
|
|
26
|
-
* Bucket boundaries match the rum.ts SDK — same dashboards, same
|
|
27
|
-
* histograms, same percentile shape.
|
|
28
|
-
*/
|
|
29
|
-
function captureWebVitals(input) {
|
|
30
|
-
if (typeof window === "undefined") return;
|
|
31
|
-
const { meter, resolveRoute, flush } = input;
|
|
32
|
-
const lcp = meter.createHistogram("web_vitals.lcp", {
|
|
33
|
-
description: "Largest Contentful Paint",
|
|
34
|
-
unit: "s",
|
|
35
|
-
advice: { explicitBucketBoundaries: [
|
|
36
|
-
.5,
|
|
37
|
-
1,
|
|
38
|
-
1.5,
|
|
39
|
-
2,
|
|
40
|
-
2.5,
|
|
41
|
-
3,
|
|
42
|
-
4,
|
|
43
|
-
5,
|
|
44
|
-
7.5,
|
|
45
|
-
10
|
|
46
|
-
] }
|
|
47
|
-
});
|
|
48
|
-
const fcp = meter.createHistogram("web_vitals.fcp", {
|
|
49
|
-
description: "First Contentful Paint",
|
|
50
|
-
unit: "s",
|
|
51
|
-
advice: { explicitBucketBoundaries: [
|
|
52
|
-
.5,
|
|
53
|
-
1,
|
|
54
|
-
1.5,
|
|
55
|
-
1.8,
|
|
56
|
-
2,
|
|
57
|
-
2.5,
|
|
58
|
-
3,
|
|
59
|
-
4,
|
|
60
|
-
5,
|
|
61
|
-
7.5
|
|
62
|
-
] }
|
|
63
|
-
});
|
|
64
|
-
const ttfb = meter.createHistogram("web_vitals.ttfb", {
|
|
65
|
-
description: "Time to First Byte",
|
|
66
|
-
unit: "s",
|
|
67
|
-
advice: { explicitBucketBoundaries: [
|
|
68
|
-
.1,
|
|
69
|
-
.2,
|
|
70
|
-
.3,
|
|
71
|
-
.5,
|
|
72
|
-
.8,
|
|
73
|
-
1,
|
|
74
|
-
1.5,
|
|
75
|
-
2,
|
|
76
|
-
3,
|
|
77
|
-
5
|
|
78
|
-
] }
|
|
79
|
-
});
|
|
80
|
-
const inp = meter.createHistogram("web_vitals.inp", {
|
|
81
|
-
description: "Interaction to Next Paint",
|
|
82
|
-
unit: "s",
|
|
83
|
-
advice: { explicitBucketBoundaries: [
|
|
84
|
-
.05,
|
|
85
|
-
.1,
|
|
86
|
-
.15,
|
|
87
|
-
.2,
|
|
88
|
-
.3,
|
|
89
|
-
.4,
|
|
90
|
-
.5,
|
|
91
|
-
.75,
|
|
92
|
-
1,
|
|
93
|
-
2
|
|
94
|
-
] }
|
|
95
|
-
});
|
|
96
|
-
const cls = meter.createHistogram("web_vitals.cls", {
|
|
97
|
-
description: "Cumulative Layout Shift",
|
|
98
|
-
unit: "1",
|
|
99
|
-
advice: { explicitBucketBoundaries: [
|
|
100
|
-
.01,
|
|
101
|
-
.025,
|
|
102
|
-
.05,
|
|
103
|
-
.075,
|
|
104
|
-
.1,
|
|
105
|
-
.15,
|
|
106
|
-
.2,
|
|
107
|
-
.25,
|
|
108
|
-
.5,
|
|
109
|
-
1
|
|
110
|
-
] }
|
|
111
|
-
});
|
|
112
|
-
const vitals = {
|
|
113
|
-
LCP: {
|
|
114
|
-
histogram: lcp,
|
|
115
|
-
toSeconds: true
|
|
116
|
-
},
|
|
117
|
-
FCP: {
|
|
118
|
-
histogram: fcp,
|
|
119
|
-
toSeconds: true
|
|
120
|
-
},
|
|
121
|
-
TTFB: {
|
|
122
|
-
histogram: ttfb,
|
|
123
|
-
toSeconds: true
|
|
124
|
-
},
|
|
125
|
-
INP: {
|
|
126
|
-
histogram: inp,
|
|
127
|
-
toSeconds: true
|
|
128
|
-
},
|
|
129
|
-
CLS: {
|
|
130
|
-
histogram: cls,
|
|
131
|
-
toSeconds: false
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
const received = /* @__PURE__ */ new Set();
|
|
135
|
-
let flushed = false;
|
|
136
|
-
function report(metric) {
|
|
137
|
-
const entry = vitals[metric.name];
|
|
138
|
-
if (!entry) return;
|
|
139
|
-
const value = entry.toSeconds ? metric.value * MS_TO_S : metric.value;
|
|
140
|
-
const pathname = window.location.pathname;
|
|
141
|
-
const route = resolveRoute?.(pathname) ?? pathname;
|
|
142
|
-
entry.histogram.record(value, {
|
|
143
|
-
...metric.rating ? { "web_vital.rating": metric.rating } : {},
|
|
144
|
-
...metric.navigationType ? { "web_vital.navigation_type": metric.navigationType } : {},
|
|
145
|
-
[ATTR_URL_PATH]: route
|
|
146
|
-
});
|
|
147
|
-
if (!flushed && LOAD_VITALS.has(metric.name)) {
|
|
148
|
-
received.add(metric.name);
|
|
149
|
-
if (received.size === LOAD_VITALS.size) {
|
|
150
|
-
flushed = true;
|
|
151
|
-
flush().catch(() => void 0);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
onLCP(report);
|
|
156
|
-
onCLS(report);
|
|
157
|
-
onINP(report);
|
|
158
|
-
onTTFB(report);
|
|
159
|
-
onFCP(report);
|
|
160
|
-
}
|
|
161
|
-
//#endregion
|
|
162
|
-
export { captureWebVitals };
|
|
1
|
+
import{onCLS,onFCP,onINP,onLCP,onTTFB}from"web-vitals";const LOAD_VITALS=new Set([`TTFB`,`FCP`,`LCP`]);function captureWebVitals(input){if(typeof window>`u`)return;let{meter,resolveRoute,flush}=input,lcp=meter.createHistogram(`web_vitals.lcp`,{description:`Largest Contentful Paint`,unit:`s`,advice:{explicitBucketBoundaries:[.5,1,1.5,2,2.5,3,4,5,7.5,10]}}),fcp=meter.createHistogram(`web_vitals.fcp`,{description:`First Contentful Paint`,unit:`s`,advice:{explicitBucketBoundaries:[.5,1,1.5,1.8,2,2.5,3,4,5,7.5]}}),ttfb=meter.createHistogram(`web_vitals.ttfb`,{description:`Time to First Byte`,unit:`s`,advice:{explicitBucketBoundaries:[.1,.2,.3,.5,.8,1,1.5,2,3,5]}}),inp=meter.createHistogram(`web_vitals.inp`,{description:`Interaction to Next Paint`,unit:`s`,advice:{explicitBucketBoundaries:[.05,.1,.15,.2,.3,.4,.5,.75,1,2]}}),cls=meter.createHistogram(`web_vitals.cls`,{description:`Cumulative Layout Shift`,unit:`1`,advice:{explicitBucketBoundaries:[.01,.025,.05,.075,.1,.15,.2,.25,.5,1]}}),vitals={LCP:{histogram:lcp,toSeconds:!0},FCP:{histogram:fcp,toSeconds:!0},TTFB:{histogram:ttfb,toSeconds:!0},INP:{histogram:inp,toSeconds:!0},CLS:{histogram:cls,toSeconds:!1}},received=new Set,flushed=!1;function report(metric){let entry=vitals[metric.name];if(!entry)return;let value=entry.toSeconds?metric.value*.001:metric.value,pathname=window.location.pathname,route=resolveRoute?.(pathname)??pathname;entry.histogram.record(value,{...metric.rating?{"web_vital.rating":metric.rating}:{},...metric.navigationType?{"web_vital.navigation_type":metric.navigationType}:{},"url.path":route}),!flushed&&LOAD_VITALS.has(metric.name)&&(received.add(metric.name),received.size===LOAD_VITALS.size&&(flushed=!0,flush().catch(()=>void 0)))}onLCP(report),onCLS(report),onINP(report),onTTFB(report),onFCP(report)}export{captureWebVitals};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-vitals.mjs","names":[],"sources":["../../../src/internal/otel/web-vitals.ts"],"sourcesContent":["import type { Histogram, Meter } from \"@opentelemetry/api\";\nimport { onCLS, onFCP, onINP, onLCP, onTTFB } from \"web-vitals\";\n\nconst ATTR_URL_PATH = \"url.path\" as const;\nconst MS_TO_S = 1 / 1000;\n\n/**\n * Vitals that fire during the page-load sequence — once we've received\n * all three we issue a one-shot flush so short visits still export\n * data. CLS and INP fire on `visibilitychange` which the kernel\n * already auto-flushes on, so they don't need their own trigger.\n */\nconst LOAD_VITALS = new Set([\"TTFB\", \"FCP\", \"LCP\"]);\n\ninterface VitalEntry {\n histogram: Histogram;\n toSeconds: boolean;\n}\n\nexport interface WebVitalsInput {\n /**\n * Called after the page-load vitals have all fired. The kernel passes\n * its own `flush()` so short-visit exports don't depend on the\n * `visibilitychange` listener firing.\n */\n flush: () => Promise<void>;\n /** OTel meter from the kernel's private provider. */\n meter: Meter;\n /**\n * Pathname → low-cardinality route template (e.g. `/blog/[slug]`).\n * Without this, dynamic routes produce one unique `url.path` label\n * per visited slug — a metric cardinality hazard.\n */\n resolveRoute?: (pathname: string) => string | undefined;\n}\n\n/**\n * Subscribes to Core Web Vitals (LCP/FCP/TTFB/INP/CLS) and records OTel\n * histograms. Time-based vitals are converted to seconds per\n * observability convention; CLS is dimensionless and recorded as-is.\n *\n * Each record carries `web_vital.rating`, `web_vital.navigation_type`,\n * and `url.path` (templated when `resolveRoute` is provided). Browser\n * and device dimensions come from the resource attached to the\n * MeterProvider, so every histogram datapoint already carries them.\n *\n * Bucket boundaries match the rum.ts SDK — same dashboards, same\n * histograms, same percentile shape.\n */\nexport function captureWebVitals(input: WebVitalsInput): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n const { meter, resolveRoute, flush } = input;\n\n const lcp = meter.createHistogram(\"web_vitals.lcp\", {\n description: \"Largest Contentful Paint\",\n unit: \"s\",\n advice: {\n explicitBucketBoundaries: [0.5, 1, 1.5, 2, 2.5, 3, 4, 5, 7.5, 10],\n },\n });\n const fcp = meter.createHistogram(\"web_vitals.fcp\", {\n description: \"First Contentful Paint\",\n unit: \"s\",\n advice: {\n explicitBucketBoundaries: [0.5, 1, 1.5, 1.8, 2, 2.5, 3, 4, 5, 7.5],\n },\n });\n const ttfb = meter.createHistogram(\"web_vitals.ttfb\", {\n description: \"Time to First Byte\",\n unit: \"s\",\n advice: {\n explicitBucketBoundaries: [0.1, 0.2, 0.3, 0.5, 0.8, 1, 1.5, 2, 3, 5],\n },\n });\n const inp = meter.createHistogram(\"web_vitals.inp\", {\n description: \"Interaction to Next Paint\",\n unit: \"s\",\n advice: {\n explicitBucketBoundaries: [\n 0.05, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.75, 1, 2,\n ],\n },\n });\n const cls = meter.createHistogram(\"web_vitals.cls\", {\n description: \"Cumulative Layout Shift\",\n unit: \"1\",\n advice: {\n explicitBucketBoundaries: [\n 0.01, 0.025, 0.05, 0.075, 0.1, 0.15, 0.2, 0.25, 0.5, 1,\n ],\n },\n });\n\n const vitals: Record<string, VitalEntry> = {\n LCP: { histogram: lcp, toSeconds: true },\n FCP: { histogram: fcp, toSeconds: true },\n TTFB: { histogram: ttfb, toSeconds: true },\n INP: { histogram: inp, toSeconds: true },\n CLS: { histogram: cls, toSeconds: false },\n };\n\n const received = new Set<string>();\n let flushed = false;\n\n function report(metric: {\n name: string;\n value: number;\n rating?: string;\n navigationType?: string;\n }): void {\n const entry = vitals[metric.name];\n if (!entry) {\n return;\n }\n const value = entry.toSeconds ? metric.value * MS_TO_S : metric.value;\n const pathname = window.location.pathname;\n const route = resolveRoute?.(pathname) ?? pathname;\n\n entry.histogram.record(value, {\n ...(metric.rating ? { \"web_vital.rating\": metric.rating } : {}),\n ...(metric.navigationType\n ? { \"web_vital.navigation_type\": metric.navigationType }\n : {}),\n [ATTR_URL_PATH]: route,\n });\n\n if (!flushed && LOAD_VITALS.has(metric.name)) {\n received.add(metric.name);\n if (received.size === LOAD_VITALS.size) {\n flushed = true;\n flush().catch(() => undefined);\n }\n }\n }\n\n onLCP(report);\n onCLS(report);\n onINP(report);\n onTTFB(report);\n onFCP(report);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"web-vitals.mjs","names":[],"sources":["../../../src/internal/otel/web-vitals.ts"],"sourcesContent":["import type { Histogram, Meter } from \"@opentelemetry/api\";\nimport { onCLS, onFCP, onINP, onLCP, onTTFB } from \"web-vitals\";\n\nconst ATTR_URL_PATH = \"url.path\" as const;\nconst MS_TO_S = 1 / 1000;\n\n/**\n * Vitals that fire during the page-load sequence — once we've received\n * all three we issue a one-shot flush so short visits still export\n * data. CLS and INP fire on `visibilitychange` which the kernel\n * already auto-flushes on, so they don't need their own trigger.\n */\nconst LOAD_VITALS = new Set([\"TTFB\", \"FCP\", \"LCP\"]);\n\ninterface VitalEntry {\n histogram: Histogram;\n toSeconds: boolean;\n}\n\nexport interface WebVitalsInput {\n /**\n * Called after the page-load vitals have all fired. The kernel passes\n * its own `flush()` so short-visit exports don't depend on the\n * `visibilitychange` listener firing.\n */\n flush: () => Promise<void>;\n /** OTel meter from the kernel's private provider. */\n meter: Meter;\n /**\n * Pathname → low-cardinality route template (e.g. `/blog/[slug]`).\n * Without this, dynamic routes produce one unique `url.path` label\n * per visited slug — a metric cardinality hazard.\n */\n resolveRoute?: (pathname: string) => string | undefined;\n}\n\n/**\n * Subscribes to Core Web Vitals (LCP/FCP/TTFB/INP/CLS) and records OTel\n * histograms. Time-based vitals are converted to seconds per\n * observability convention; CLS is dimensionless and recorded as-is.\n *\n * Each record carries `web_vital.rating`, `web_vital.navigation_type`,\n * and `url.path` (templated when `resolveRoute` is provided). Browser\n * and device dimensions come from the resource attached to the\n * MeterProvider, so every histogram datapoint already carries them.\n *\n * Bucket boundaries match the rum.ts SDK — same dashboards, same\n * histograms, same percentile shape.\n */\nexport function captureWebVitals(input: WebVitalsInput): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n const { meter, resolveRoute, flush } = input;\n\n const lcp = meter.createHistogram(\"web_vitals.lcp\", {\n description: \"Largest Contentful Paint\",\n unit: \"s\",\n advice: {\n explicitBucketBoundaries: [0.5, 1, 1.5, 2, 2.5, 3, 4, 5, 7.5, 10],\n },\n });\n const fcp = meter.createHistogram(\"web_vitals.fcp\", {\n description: \"First Contentful Paint\",\n unit: \"s\",\n advice: {\n explicitBucketBoundaries: [0.5, 1, 1.5, 1.8, 2, 2.5, 3, 4, 5, 7.5],\n },\n });\n const ttfb = meter.createHistogram(\"web_vitals.ttfb\", {\n description: \"Time to First Byte\",\n unit: \"s\",\n advice: {\n explicitBucketBoundaries: [0.1, 0.2, 0.3, 0.5, 0.8, 1, 1.5, 2, 3, 5],\n },\n });\n const inp = meter.createHistogram(\"web_vitals.inp\", {\n description: \"Interaction to Next Paint\",\n unit: \"s\",\n advice: {\n explicitBucketBoundaries: [\n 0.05, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.75, 1, 2,\n ],\n },\n });\n const cls = meter.createHistogram(\"web_vitals.cls\", {\n description: \"Cumulative Layout Shift\",\n unit: \"1\",\n advice: {\n explicitBucketBoundaries: [\n 0.01, 0.025, 0.05, 0.075, 0.1, 0.15, 0.2, 0.25, 0.5, 1,\n ],\n },\n });\n\n const vitals: Record<string, VitalEntry> = {\n LCP: { histogram: lcp, toSeconds: true },\n FCP: { histogram: fcp, toSeconds: true },\n TTFB: { histogram: ttfb, toSeconds: true },\n INP: { histogram: inp, toSeconds: true },\n CLS: { histogram: cls, toSeconds: false },\n };\n\n const received = new Set<string>();\n let flushed = false;\n\n function report(metric: {\n name: string;\n value: number;\n rating?: string;\n navigationType?: string;\n }): void {\n const entry = vitals[metric.name];\n if (!entry) {\n return;\n }\n const value = entry.toSeconds ? metric.value * MS_TO_S : metric.value;\n const pathname = window.location.pathname;\n const route = resolveRoute?.(pathname) ?? pathname;\n\n entry.histogram.record(value, {\n ...(metric.rating ? { \"web_vital.rating\": metric.rating } : {}),\n ...(metric.navigationType\n ? { \"web_vital.navigation_type\": metric.navigationType }\n : {}),\n [ATTR_URL_PATH]: route,\n });\n\n if (!flushed && LOAD_VITALS.has(metric.name)) {\n received.add(metric.name);\n if (received.size === LOAD_VITALS.size) {\n flushed = true;\n flush().catch(() => undefined);\n }\n }\n }\n\n onLCP(report);\n onCLS(report);\n onINP(report);\n onTTFB(report);\n onFCP(report);\n}\n"],"mappings":"uDAGA,MASM,YAAc,IAAI,IAAI,CAAC,OAAQ,MAAO,KAAK,CAAC,EAqClD,SAAgB,iBAAiB,MAA6B,CAC5D,GAAI,OAAO,OAAW,IACpB,OAGF,GAAM,CAAE,MAAO,aAAc,OAAU,MAEjC,IAAM,MAAM,gBAAgB,iBAAkB,CAClD,YAAa,2BACb,KAAM,IACN,OAAQ,CACN,yBAA0B,CAAC,GAAK,EAAG,IAAK,EAAG,IAAK,EAAG,EAAG,EAAG,IAAK,EAAE,CAClE,CACF,CAAC,EACK,IAAM,MAAM,gBAAgB,iBAAkB,CAClD,YAAa,yBACb,KAAM,IACN,OAAQ,CACN,yBAA0B,CAAC,GAAK,EAAG,IAAK,IAAK,EAAG,IAAK,EAAG,EAAG,EAAG,GAAG,CACnE,CACF,CAAC,EACK,KAAO,MAAM,gBAAgB,kBAAmB,CACpD,YAAa,qBACb,KAAM,IACN,OAAQ,CACN,yBAA0B,CAAC,GAAK,GAAK,GAAK,GAAK,GAAK,EAAG,IAAK,EAAG,EAAG,CAAC,CACrE,CACF,CAAC,EACK,IAAM,MAAM,gBAAgB,iBAAkB,CAClD,YAAa,4BACb,KAAM,IACN,OAAQ,CACN,yBAA0B,CACxB,IAAM,GAAK,IAAM,GAAK,GAAK,GAAK,GAAK,IAAM,EAAG,CAChD,CACF,CACF,CAAC,EACK,IAAM,MAAM,gBAAgB,iBAAkB,CAClD,YAAa,0BACb,KAAM,IACN,OAAQ,CACN,yBAA0B,CACxB,IAAM,KAAO,IAAM,KAAO,GAAK,IAAM,GAAK,IAAM,GAAK,CACvD,CACF,CACF,CAAC,EAEK,OAAqC,CACzC,IAAK,CAAE,UAAW,IAAK,UAAW,EAAK,EACvC,IAAK,CAAE,UAAW,IAAK,UAAW,EAAK,EACvC,KAAM,CAAE,UAAW,KAAM,UAAW,EAAK,EACzC,IAAK,CAAE,UAAW,IAAK,UAAW,EAAK,EACvC,IAAK,CAAE,UAAW,IAAK,UAAW,EAAM,CAC1C,EAEM,SAAW,IAAI,IACjB,QAAU,GAEd,SAAS,OAAO,OAKP,CACP,IAAM,MAAQ,OAAO,OAAO,MAC5B,GAAI,CAAC,MACH,OAEF,IAAM,MAAQ,MAAM,UAAY,OAAO,MAAQ,KAAU,OAAO,MAC1D,SAAW,OAAO,SAAS,SAC3B,MAAQ,eAAe,QAAQ,GAAK,SAE1C,MAAM,UAAU,OAAO,MAAO,CAC5B,GAAI,OAAO,OAAS,CAAE,mBAAoB,OAAO,MAAO,EAAI,CAAC,EAC7D,GAAI,OAAO,eACP,CAAE,4BAA6B,OAAO,cAAe,EACrD,CAAC,EACJ,WAAgB,KACnB,CAAC,EAEG,CAAC,SAAW,YAAY,IAAI,OAAO,IAAI,IACzC,SAAS,IAAI,OAAO,IAAI,EACpB,SAAS,OAAS,YAAY,OAChC,QAAU,GACV,MAAM,EAAE,UAAY,IAAA,EAAS,GAGnC,CAEA,MAAM,MAAM,EACZ,MAAM,MAAM,EACZ,MAAM,MAAM,EACZ,OAAO,MAAM,EACb,MAAM,MAAM,CACd"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-lifecycle.d.mts","names":[],"sources":["../../src/internal/page-lifecycle.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"page-lifecycle.d.mts","names":[],"sources":["../../src/internal/page-lifecycle.ts"],"mappings":";;;;;;AAcY;AA6BZ;;;;AAA6C;AAS7C;;;KAtCK,OAAA;AAAA,iBA6BW,YAAA,CAAa,OAAgB,EAAP,OAAO;;iBAS7B,2BAAA,CAAA"}
|
|
@@ -1,33 +1 @@
|
|
|
1
|
-
|
|
2
|
-
const handlers = /* @__PURE__ */ new Set();
|
|
3
|
-
let installed = false;
|
|
4
|
-
function fire() {
|
|
5
|
-
for (const handler of handlers) handler();
|
|
6
|
-
}
|
|
7
|
-
function onVisibilityChange() {
|
|
8
|
-
if (typeof document !== "undefined" && document.visibilityState === "hidden") fire();
|
|
9
|
-
}
|
|
10
|
-
function install() {
|
|
11
|
-
if (installed || typeof globalThis.addEventListener !== "function") return;
|
|
12
|
-
installed = true;
|
|
13
|
-
globalThis.addEventListener("visibilitychange", onVisibilityChange);
|
|
14
|
-
globalThis.addEventListener("beforeunload", fire);
|
|
15
|
-
}
|
|
16
|
-
function onPageHidden(handler) {
|
|
17
|
-
install();
|
|
18
|
-
handlers.add(handler);
|
|
19
|
-
return () => {
|
|
20
|
-
handlers.delete(handler);
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
/** Test hook — drops every handler and unbinds the listeners. */
|
|
24
|
-
function _resetPageLifecycleForTests() {
|
|
25
|
-
handlers.clear();
|
|
26
|
-
if (installed && typeof globalThis.removeEventListener === "function") {
|
|
27
|
-
globalThis.removeEventListener("visibilitychange", onVisibilityChange);
|
|
28
|
-
globalThis.removeEventListener("beforeunload", fire);
|
|
29
|
-
}
|
|
30
|
-
installed = false;
|
|
31
|
-
}
|
|
32
|
-
//#endregion
|
|
33
|
-
export { _resetPageLifecycleForTests, onPageHidden };
|
|
1
|
+
const handlers=new Set;let installed=!1;function fire(){for(let handler of handlers)handler()}function onVisibilityChange(){typeof document<`u`&&document.visibilityState===`hidden`&&fire()}function install(){installed||typeof globalThis.addEventListener!=`function`||(installed=!0,globalThis.addEventListener(`visibilitychange`,onVisibilityChange),globalThis.addEventListener(`beforeunload`,fire))}function onPageHidden(handler){return install(),handlers.add(handler),()=>{handlers.delete(handler)}}function _resetPageLifecycleForTests(){handlers.clear(),installed&&typeof globalThis.removeEventListener==`function`&&(globalThis.removeEventListener(`visibilitychange`,onVisibilityChange),globalThis.removeEventListener(`beforeunload`,fire)),installed=!1}export{_resetPageLifecycleForTests,onPageHidden};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-lifecycle.mjs","names":[],"sources":["../../src/internal/page-lifecycle.ts"],"sourcesContent":["/**\n * Single subscription point for `visibilitychange` (hidden) and\n * `beforeunload`. Modules call `onPageHidden(handler)` and the handler\n * fires once per transition into hidden / unload.\n *\n * Replaces the previous shape where the kernel, the queue, the pages\n * plugin, and the replay plugin each attached their own listeners — four\n * subscribers racing to flush / drain / emit on the same boundary, with\n * no shared lifecycle. One subscription, many handlers, no coordination\n * via the global event subsystem.\n *\n * Both events can fire (visibilitychange first when the user navigates\n * away, then sometimes beforeunload). Handlers must be idempotent.\n */\ntype Handler = () => void;\n\nconst handlers = new Set<Handler>();\nlet installed = false;\n\nfunction fire(): void {\n for (const handler of handlers) {\n handler();\n }\n}\n\nfunction onVisibilityChange(): void {\n if (\n typeof document !== \"undefined\" &&\n document.visibilityState === \"hidden\"\n ) {\n fire();\n }\n}\n\nfunction install(): void {\n if (installed || typeof globalThis.addEventListener !== \"function\") {\n return;\n }\n installed = true;\n globalThis.addEventListener(\"visibilitychange\", onVisibilityChange);\n globalThis.addEventListener(\"beforeunload\", fire);\n}\n\nexport function onPageHidden(handler: Handler): () => void {\n install();\n handlers.add(handler);\n return () => {\n handlers.delete(handler);\n };\n}\n\n/** Test hook — drops every handler and unbinds the listeners. */\nexport function _resetPageLifecycleForTests(): void {\n handlers.clear();\n if (installed && typeof globalThis.removeEventListener === \"function\") {\n globalThis.removeEventListener(\"visibilitychange\", onVisibilityChange);\n globalThis.removeEventListener(\"beforeunload\", fire);\n }\n installed = false;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"page-lifecycle.mjs","names":[],"sources":["../../src/internal/page-lifecycle.ts"],"sourcesContent":["/**\n * Single subscription point for `visibilitychange` (hidden) and\n * `beforeunload`. Modules call `onPageHidden(handler)` and the handler\n * fires once per transition into hidden / unload.\n *\n * Replaces the previous shape where the kernel, the queue, the pages\n * plugin, and the replay plugin each attached their own listeners — four\n * subscribers racing to flush / drain / emit on the same boundary, with\n * no shared lifecycle. One subscription, many handlers, no coordination\n * via the global event subsystem.\n *\n * Both events can fire (visibilitychange first when the user navigates\n * away, then sometimes beforeunload). Handlers must be idempotent.\n */\ntype Handler = () => void;\n\nconst handlers = new Set<Handler>();\nlet installed = false;\n\nfunction fire(): void {\n for (const handler of handlers) {\n handler();\n }\n}\n\nfunction onVisibilityChange(): void {\n if (\n typeof document !== \"undefined\" &&\n document.visibilityState === \"hidden\"\n ) {\n fire();\n }\n}\n\nfunction install(): void {\n if (installed || typeof globalThis.addEventListener !== \"function\") {\n return;\n }\n installed = true;\n globalThis.addEventListener(\"visibilitychange\", onVisibilityChange);\n globalThis.addEventListener(\"beforeunload\", fire);\n}\n\nexport function onPageHidden(handler: Handler): () => void {\n install();\n handlers.add(handler);\n return () => {\n handlers.delete(handler);\n };\n}\n\n/** Test hook — drops every handler and unbinds the listeners. */\nexport function _resetPageLifecycleForTests(): void {\n handlers.clear();\n if (installed && typeof globalThis.removeEventListener === \"function\") {\n globalThis.removeEventListener(\"visibilitychange\", onVisibilityChange);\n globalThis.removeEventListener(\"beforeunload\", fire);\n }\n installed = false;\n}\n"],"mappings":"AAgBA,MAAM,SAAW,IAAI,IACrB,IAAI,UAAY,GAEhB,SAAS,MAAa,CACpB,IAAK,IAAM,WAAW,SACpB,QAAQ,CAEZ,CAEA,SAAS,oBAA2B,CAEhC,OAAO,SAAa,KACpB,SAAS,kBAAoB,UAE7B,KAAK,CAET,CAEA,SAAS,SAAgB,CACnB,WAAa,OAAO,WAAW,kBAAqB,aAGxD,UAAY,GACZ,WAAW,iBAAiB,mBAAoB,kBAAkB,EAClE,WAAW,iBAAiB,eAAgB,IAAI,EAClD,CAEA,SAAgB,aAAa,QAA8B,CAGzD,OAFA,QAAQ,EACR,SAAS,IAAI,OAAO,MACP,CACX,SAAS,OAAO,OAAO,CACzB,CACF,CAGA,SAAgB,6BAAoC,CAClD,SAAS,MAAM,EACX,WAAa,OAAO,WAAW,qBAAwB,aACzD,WAAW,oBAAoB,mBAAoB,kBAAkB,EACrE,WAAW,oBAAoB,eAAgB,IAAI,GAErD,UAAY,EACd"}
|
|
@@ -1,101 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { getPluginConsentCategory, hasConsentChanged, isConsentAllowed, resolveGrantedConsent } from "./consent.mjs";
|
|
3
|
-
import errorsPlugin from "../plugins/errors.mjs";
|
|
4
|
-
import { loadPlugin, resolveFeatures } from "../plugins/lib/loader.mjs";
|
|
5
|
-
import { PLUGIN_MANIFEST } from "@interfere/types/sdk/plugins/manifest";
|
|
6
|
-
//#region src/internal/plugin-runtime.ts
|
|
7
|
-
const log = createLogger("plugin-runtime");
|
|
8
|
-
var PluginRuntime = class {
|
|
9
|
-
activeCleanups = /* @__PURE__ */ new Map();
|
|
10
|
-
pending = /* @__PURE__ */ new Set();
|
|
11
|
-
context;
|
|
12
|
-
features;
|
|
13
|
-
remoteConfig = {};
|
|
14
|
-
consentState;
|
|
15
|
-
syncVersion = 0;
|
|
16
|
-
disposed = false;
|
|
17
|
-
constructor(context, overrides, initialConsent) {
|
|
18
|
-
this.context = context;
|
|
19
|
-
this.features = resolveFeatures(overrides);
|
|
20
|
-
this.consentState = initialConsent ?? null;
|
|
21
|
-
}
|
|
22
|
-
getConsent() {
|
|
23
|
-
return this.consentState;
|
|
24
|
-
}
|
|
25
|
-
setConsent(nextConsent) {
|
|
26
|
-
const nextState = resolveGrantedConsent(nextConsent);
|
|
27
|
-
if (!hasConsentChanged(this.consentState, nextState)) return;
|
|
28
|
-
this.consentState = nextState;
|
|
29
|
-
this.sync();
|
|
30
|
-
}
|
|
31
|
-
resetConsent() {
|
|
32
|
-
if (!hasConsentChanged(this.consentState, null)) return;
|
|
33
|
-
this.consentState = null;
|
|
34
|
-
this.sync();
|
|
35
|
-
}
|
|
36
|
-
applyRemoteConfig(config) {
|
|
37
|
-
this.remoteConfig = config;
|
|
38
|
-
this.sync();
|
|
39
|
-
}
|
|
40
|
-
start() {
|
|
41
|
-
if (this.shouldEnablePlugin("errors")) {
|
|
42
|
-
const cleanup = errorsPlugin.setup(this.context);
|
|
43
|
-
if (cleanup) this.activeCleanups.set("errors", cleanup);
|
|
44
|
-
}
|
|
45
|
-
this.sync();
|
|
46
|
-
}
|
|
47
|
-
async dispose() {
|
|
48
|
-
this.disposed = true;
|
|
49
|
-
this.syncVersion += 1;
|
|
50
|
-
for (const key of this.activeCleanups.keys()) this.deactivate(key);
|
|
51
|
-
await Promise.allSettled(this.pending);
|
|
52
|
-
}
|
|
53
|
-
shouldEnablePlugin(key) {
|
|
54
|
-
return this.features[key] && this.remoteConfig[key] !== false && isConsentAllowed(getPluginConsentCategory(key), this.consentState);
|
|
55
|
-
}
|
|
56
|
-
deactivate(key) {
|
|
57
|
-
const cleanup = this.activeCleanups.get(key);
|
|
58
|
-
if (!cleanup) return;
|
|
59
|
-
try {
|
|
60
|
-
cleanup();
|
|
61
|
-
} catch {
|
|
62
|
-
log.warn("cleanup failed for %s", key);
|
|
63
|
-
}
|
|
64
|
-
this.activeCleanups.delete(key);
|
|
65
|
-
}
|
|
66
|
-
async activate(key) {
|
|
67
|
-
if (this.activeCleanups.has(key) || !this.shouldEnablePlugin(key)) return;
|
|
68
|
-
const version = this.syncVersion;
|
|
69
|
-
const cleanup = await loadPlugin(key, this.context);
|
|
70
|
-
if (!cleanup) return;
|
|
71
|
-
if (version !== this.syncVersion || !this.shouldEnablePlugin(key)) {
|
|
72
|
-
cleanup();
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
this.activeCleanups.set(key, cleanup);
|
|
76
|
-
}
|
|
77
|
-
sync() {
|
|
78
|
-
if (this.disposed) return;
|
|
79
|
-
this.syncVersion += 1;
|
|
80
|
-
for (const plugin of PLUGIN_MANIFEST) {
|
|
81
|
-
if (this.shouldEnablePlugin(plugin.name)) {
|
|
82
|
-
if (plugin.name === "errors") {
|
|
83
|
-
if (!this.activeCleanups.has("errors")) {
|
|
84
|
-
const cleanup = errorsPlugin.setup(this.context);
|
|
85
|
-
if (cleanup) this.activeCleanups.set("errors", cleanup);
|
|
86
|
-
}
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
const p = this.activate(plugin.name).catch(() => {
|
|
90
|
-
log.warn("non-critical plugin loading failed");
|
|
91
|
-
});
|
|
92
|
-
this.pending.add(p);
|
|
93
|
-
p.finally(() => this.pending.delete(p));
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
this.deactivate(plugin.name);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
//#endregion
|
|
101
|
-
export { PluginRuntime };
|
|
1
|
+
import{createLogger}from"../util/log.mjs";import{getPluginConsentCategory,hasConsentChanged,isConsentAllowed,resolveGrantedConsent}from"./consent.mjs";import errorsPlugin from"../plugins/errors.mjs";import{loadPlugin,resolveFeatures}from"../plugins/lib/loader.mjs";import{PLUGIN_MANIFEST}from"@interfere/types/sdk/plugins/manifest";const log=createLogger(`plugin-runtime`);var PluginRuntime=class{activeCleanups=new Map;pending=new Set;context;features;remoteConfig={};consentState;syncVersion=0;disposed=!1;constructor(context,overrides,initialConsent){this.context=context,this.features=resolveFeatures(overrides),this.consentState=initialConsent??null}getConsent(){return this.consentState}setConsent(nextConsent){let nextState=resolveGrantedConsent(nextConsent);hasConsentChanged(this.consentState,nextState)&&(this.consentState=nextState,this.sync())}resetConsent(){hasConsentChanged(this.consentState,null)&&(this.consentState=null,this.sync())}applyRemoteConfig(config){this.remoteConfig=config,this.sync()}start(){if(this.shouldEnablePlugin(`errors`)){let cleanup=errorsPlugin.setup(this.context);cleanup&&this.activeCleanups.set(`errors`,cleanup)}this.sync()}async dispose(){this.disposed=!0,this.syncVersion+=1;for(let key of this.activeCleanups.keys())this.deactivate(key);await Promise.allSettled(this.pending)}shouldEnablePlugin(key){return this.features[key]&&this.remoteConfig[key]!==!1&&isConsentAllowed(getPluginConsentCategory(key),this.consentState)}deactivate(key){let cleanup=this.activeCleanups.get(key);if(cleanup){try{cleanup()}catch{log.warn(`cleanup failed for %s`,key)}this.activeCleanups.delete(key)}}async activate(key){if(this.activeCleanups.has(key)||!this.shouldEnablePlugin(key))return;let version=this.syncVersion,cleanup=await loadPlugin(key,this.context);if(cleanup){if(version!==this.syncVersion||!this.shouldEnablePlugin(key)){cleanup();return}this.activeCleanups.set(key,cleanup)}}sync(){if(!this.disposed){this.syncVersion+=1;for(let plugin of PLUGIN_MANIFEST){if(this.shouldEnablePlugin(plugin.name)){if(plugin.name===`errors`){if(!this.activeCleanups.has(`errors`)){let cleanup=errorsPlugin.setup(this.context);cleanup&&this.activeCleanups.set(`errors`,cleanup)}continue}let p=this.activate(plugin.name).catch(()=>{log.warn(`non-critical plugin loading failed`)});this.pending.add(p),p.finally(()=>this.pending.delete(p));continue}this.deactivate(plugin.name)}}}};export{PluginRuntime};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-runtime.mjs","names":[],"sources":["../../src/internal/plugin-runtime.ts"],"sourcesContent":["import {\n type ConsentState,\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\nimport type { RemotePluginConfig } from \"@interfere/types/sdk/remote-config\";\n\nimport errorsPlugin from \"../plugins/errors.js\";\nimport {\n loadPlugin,\n type PluginOverrides,\n resolveFeatures,\n} from \"../plugins/lib/loader.js\";\nimport type { PluginCleanup, PluginContext } from \"../plugins/lib/types.js\";\nimport { createLogger } from \"../util/log.js\";\nimport {\n getPluginConsentCategory,\n hasConsentChanged,\n isConsentAllowed,\n resolveGrantedConsent,\n} from \"./consent.js\";\n\nconst log = createLogger(\"plugin-runtime\");\n\nexport class PluginRuntime {\n private readonly activeCleanups = new Map<PluginKey, PluginCleanup>();\n private readonly pending = new Set<Promise<void>>();\n private readonly context: PluginContext;\n private readonly features: Record<PluginKey, boolean>;\n private remoteConfig: RemotePluginConfig = {};\n private consentState: ConsentState | null;\n private syncVersion = 0;\n private disposed = false;\n\n constructor(\n context: PluginContext,\n overrides: PluginOverrides | undefined,\n initialConsent: ConsentState | undefined\n ) {\n this.context = context;\n this.features = resolveFeatures(overrides);\n this.consentState = initialConsent ?? null;\n }\n\n getConsent(): ConsentState | null {\n return this.consentState;\n }\n\n setConsent(nextConsent?: ConsentState): void {\n const nextState = resolveGrantedConsent(nextConsent);\n if (!hasConsentChanged(this.consentState, nextState)) {\n return;\n }\n\n this.consentState = nextState;\n this.sync();\n }\n\n resetConsent(): void {\n if (!hasConsentChanged(this.consentState, null)) {\n return;\n }\n\n this.consentState = null;\n this.sync();\n }\n\n applyRemoteConfig(config: RemotePluginConfig): void {\n this.remoteConfig = config;\n this.sync();\n }\n\n start(): void {\n if (this.shouldEnablePlugin(\"errors\")) {\n const cleanup = errorsPlugin.setup(this.context);\n if (cleanup) {\n this.activeCleanups.set(\"errors\", cleanup);\n }\n }\n\n this.sync();\n }\n\n async dispose(): Promise<void> {\n this.disposed = true;\n this.syncVersion += 1;\n\n for (const key of this.activeCleanups.keys()) {\n this.deactivate(key);\n }\n\n await Promise.allSettled(this.pending);\n }\n\n private shouldEnablePlugin(key: PluginKey): boolean {\n return (\n this.features[key] &&\n this.remoteConfig[key] !== false &&\n isConsentAllowed(getPluginConsentCategory(key), this.consentState)\n );\n }\n\n private deactivate(key: PluginKey): void {\n const cleanup = this.activeCleanups.get(key);\n if (!cleanup) {\n return;\n }\n\n try {\n cleanup();\n } catch {\n log.warn(\"cleanup failed for %s\", key);\n }\n\n this.activeCleanups.delete(key);\n }\n\n private async activate(key: PluginKey): Promise<void> {\n if (this.activeCleanups.has(key) || !this.shouldEnablePlugin(key)) {\n return;\n }\n\n const version = this.syncVersion;\n const cleanup = await loadPlugin(key, this.context);\n if (!cleanup) {\n return;\n }\n\n const staleSync = version !== this.syncVersion;\n if (staleSync || !this.shouldEnablePlugin(key)) {\n cleanup();\n return;\n }\n\n this.activeCleanups.set(key, cleanup);\n }\n\n private sync(): void {\n if (this.disposed) {\n return;\n }\n\n this.syncVersion += 1;\n\n for (const plugin of PLUGIN_MANIFEST) {\n if (this.shouldEnablePlugin(plugin.name)) {\n if (plugin.name === \"errors\") {\n if (!this.activeCleanups.has(\"errors\")) {\n const cleanup = errorsPlugin.setup(this.context);\n if (cleanup) {\n this.activeCleanups.set(\"errors\", cleanup);\n }\n }\n continue;\n }\n\n const p = this.activate(plugin.name).catch(() => {\n log.warn(\"non-critical plugin loading failed\");\n });\n this.pending.add(p);\n p.finally(() => this.pending.delete(p));\n continue;\n }\n\n this.deactivate(plugin.name);\n }\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin-runtime.mjs","names":[],"sources":["../../src/internal/plugin-runtime.ts"],"sourcesContent":["import {\n type ConsentState,\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\nimport type { RemotePluginConfig } from \"@interfere/types/sdk/remote-config\";\n\nimport errorsPlugin from \"../plugins/errors.js\";\nimport {\n loadPlugin,\n type PluginOverrides,\n resolveFeatures,\n} from \"../plugins/lib/loader.js\";\nimport type { PluginCleanup, PluginContext } from \"../plugins/lib/types.js\";\nimport { createLogger } from \"../util/log.js\";\nimport {\n getPluginConsentCategory,\n hasConsentChanged,\n isConsentAllowed,\n resolveGrantedConsent,\n} from \"./consent.js\";\n\nconst log = createLogger(\"plugin-runtime\");\n\nexport class PluginRuntime {\n private readonly activeCleanups = new Map<PluginKey, PluginCleanup>();\n private readonly pending = new Set<Promise<void>>();\n private readonly context: PluginContext;\n private readonly features: Record<PluginKey, boolean>;\n private remoteConfig: RemotePluginConfig = {};\n private consentState: ConsentState | null;\n private syncVersion = 0;\n private disposed = false;\n\n constructor(\n context: PluginContext,\n overrides: PluginOverrides | undefined,\n initialConsent: ConsentState | undefined\n ) {\n this.context = context;\n this.features = resolveFeatures(overrides);\n this.consentState = initialConsent ?? null;\n }\n\n getConsent(): ConsentState | null {\n return this.consentState;\n }\n\n setConsent(nextConsent?: ConsentState): void {\n const nextState = resolveGrantedConsent(nextConsent);\n if (!hasConsentChanged(this.consentState, nextState)) {\n return;\n }\n\n this.consentState = nextState;\n this.sync();\n }\n\n resetConsent(): void {\n if (!hasConsentChanged(this.consentState, null)) {\n return;\n }\n\n this.consentState = null;\n this.sync();\n }\n\n applyRemoteConfig(config: RemotePluginConfig): void {\n this.remoteConfig = config;\n this.sync();\n }\n\n start(): void {\n if (this.shouldEnablePlugin(\"errors\")) {\n const cleanup = errorsPlugin.setup(this.context);\n if (cleanup) {\n this.activeCleanups.set(\"errors\", cleanup);\n }\n }\n\n this.sync();\n }\n\n async dispose(): Promise<void> {\n this.disposed = true;\n this.syncVersion += 1;\n\n for (const key of this.activeCleanups.keys()) {\n this.deactivate(key);\n }\n\n await Promise.allSettled(this.pending);\n }\n\n private shouldEnablePlugin(key: PluginKey): boolean {\n return (\n this.features[key] &&\n this.remoteConfig[key] !== false &&\n isConsentAllowed(getPluginConsentCategory(key), this.consentState)\n );\n }\n\n private deactivate(key: PluginKey): void {\n const cleanup = this.activeCleanups.get(key);\n if (!cleanup) {\n return;\n }\n\n try {\n cleanup();\n } catch {\n log.warn(\"cleanup failed for %s\", key);\n }\n\n this.activeCleanups.delete(key);\n }\n\n private async activate(key: PluginKey): Promise<void> {\n if (this.activeCleanups.has(key) || !this.shouldEnablePlugin(key)) {\n return;\n }\n\n const version = this.syncVersion;\n const cleanup = await loadPlugin(key, this.context);\n if (!cleanup) {\n return;\n }\n\n const staleSync = version !== this.syncVersion;\n if (staleSync || !this.shouldEnablePlugin(key)) {\n cleanup();\n return;\n }\n\n this.activeCleanups.set(key, cleanup);\n }\n\n private sync(): void {\n if (this.disposed) {\n return;\n }\n\n this.syncVersion += 1;\n\n for (const plugin of PLUGIN_MANIFEST) {\n if (this.shouldEnablePlugin(plugin.name)) {\n if (plugin.name === \"errors\") {\n if (!this.activeCleanups.has(\"errors\")) {\n const cleanup = errorsPlugin.setup(this.context);\n if (cleanup) {\n this.activeCleanups.set(\"errors\", cleanup);\n }\n }\n continue;\n }\n\n const p = this.activate(plugin.name).catch(() => {\n log.warn(\"non-critical plugin loading failed\");\n });\n this.pending.add(p);\n p.finally(() => this.pending.delete(p));\n continue;\n }\n\n this.deactivate(plugin.name);\n }\n }\n}\n"],"mappings":"4UAsBA,MAAM,IAAM,aAAa,gBAAgB,EAEzC,IAAa,cAAb,KAA2B,CACzB,eAAkC,IAAI,IACtC,QAA2B,IAAI,IAC/B,QACA,SACA,aAA2C,CAAC,EAC5C,aACA,YAAsB,EACtB,SAAmB,GAEnB,YACE,QACA,UACA,eACA,CACA,KAAK,QAAU,QACf,KAAK,SAAW,gBAAgB,SAAS,EACzC,KAAK,aAAe,gBAAkB,IACxC,CAEA,YAAkC,CAChC,OAAO,KAAK,YACd,CAEA,WAAW,YAAkC,CAC3C,IAAM,UAAY,sBAAsB,WAAW,EAC9C,kBAAkB,KAAK,aAAc,SAAS,IAInD,KAAK,aAAe,UACpB,KAAK,KAAK,EACZ,CAEA,cAAqB,CACd,kBAAkB,KAAK,aAAc,IAAI,IAI9C,KAAK,aAAe,KACpB,KAAK,KAAK,EACZ,CAEA,kBAAkB,OAAkC,CAClD,KAAK,aAAe,OACpB,KAAK,KAAK,CACZ,CAEA,OAAc,CACZ,GAAI,KAAK,mBAAmB,QAAQ,EAAG,CACrC,IAAM,QAAU,aAAa,MAAM,KAAK,OAAO,EAC3C,SACF,KAAK,eAAe,IAAI,SAAU,OAAO,CAE7C,CAEA,KAAK,KAAK,CACZ,CAEA,MAAM,SAAyB,CAC7B,KAAK,SAAW,GAChB,KAAK,aAAe,EAEpB,IAAK,IAAM,OAAO,KAAK,eAAe,KAAK,EACzC,KAAK,WAAW,GAAG,EAGrB,MAAM,QAAQ,WAAW,KAAK,OAAO,CACvC,CAEA,mBAA2B,IAAyB,CAClD,OACE,KAAK,SAAS,MACd,KAAK,aAAa,OAAS,IAC3B,iBAAiB,yBAAyB,GAAG,EAAG,KAAK,YAAY,CAErE,CAEA,WAAmB,IAAsB,CACvC,IAAM,QAAU,KAAK,eAAe,IAAI,GAAG,EACtC,WAIL,IAAI,CACF,QAAQ,CACV,MAAQ,CACN,IAAI,KAAK,wBAAyB,GAAG,CACvC,CAEA,KAAK,eAAe,OAAO,GAAG,CAF9B,CAGF,CAEA,MAAc,SAAS,IAA+B,CACpD,GAAI,KAAK,eAAe,IAAI,GAAG,GAAK,CAAC,KAAK,mBAAmB,GAAG,EAC9D,OAGF,IAAM,QAAU,KAAK,YACf,QAAU,MAAM,WAAW,IAAK,KAAK,OAAO,EAC7C,WAKL,IADkB,UAAY,KAAK,aAClB,CAAC,KAAK,mBAAmB,GAAG,EAAG,CAC9C,QAAQ,EACR,MACF,CAEA,KAAK,eAAe,IAAI,IAAK,OAAO,CAFpC,CAGF,CAEA,MAAqB,CACf,SAAK,SAIT,MAAK,aAAe,EAEpB,IAAK,IAAM,UAAU,gBAAiB,CACpC,GAAI,KAAK,mBAAmB,OAAO,IAAI,EAAG,CACxC,GAAI,OAAO,OAAS,SAAU,CAC5B,GAAI,CAAC,KAAK,eAAe,IAAI,QAAQ,EAAG,CACtC,IAAM,QAAU,aAAa,MAAM,KAAK,OAAO,EAC3C,SACF,KAAK,eAAe,IAAI,SAAU,OAAO,CAE7C,CACA,QACF,CAEA,IAAM,EAAI,KAAK,SAAS,OAAO,IAAI,EAAE,UAAY,CAC/C,IAAI,KAAK,oCAAoC,CAC/C,CAAC,EACD,KAAK,QAAQ,IAAI,CAAC,EAClB,EAAE,YAAc,KAAK,QAAQ,OAAO,CAAC,CAAC,EACtC,QACF,CAEA,KAAK,WAAW,OAAO,IAAI,CAC7B,CAvBoB,CAwBtB,CACF"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Kernel } from "./kernel.mjs";
|
|
2
|
-
import * as _$react from "react";
|
|
3
2
|
import { ConsentState } from "@interfere/types/sdk/plugins/manifest";
|
|
4
3
|
import { IdentifyParams } from "@interfere/types/sdk/identify";
|
|
5
4
|
|
|
@@ -40,6 +39,6 @@ declare const NULL_CONTEXT_VALUE: InterfereContextValue;
|
|
|
40
39
|
* Lives in its own module so `provider.tsx` and `internal/capture-boundary.tsx`
|
|
41
40
|
* can both import it without forming an import cycle.
|
|
42
41
|
*/
|
|
43
|
-
declare const InterfereContext:
|
|
42
|
+
declare const InterfereContext: import("react").Context<InterfereContextValue | null>;
|
|
44
43
|
//#endregion
|
|
45
44
|
export { InterfereContext, InterfereContextValue, NULL_CONTEXT_VALUE };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-context.d.mts","names":[],"sources":["../../src/internal/react-context.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"react-context.d.mts","names":[],"sources":["../../src/internal/react-context.ts"],"mappings":";;;;;UASiB,qBAAA;EACf,OAAA;IACE,GAAA,IAAO,YAAA;IACP,GAAA,CAAI,KAAA,GAAQ,YAAA;EAAA;EAEd,MAAA;IACE,WAAA;IACA,SAAA;EAAA;EAEF,QAAA;IACE,GAAA,IAAO,cAAA;IACP,GAAA,CAAI,MAAA,EAAQ,cAAA,GAAiB,OAAA;EAAA;EAV/B;;;;;;EAkBA,MAAA,EAAQ,MAAA;EACR,OAAA;IACE,KAAA;IACA,WAAA;EAAA;AAAA;;;;;;;cAkBS,kBAAA,EAAoB,qBAMhC;;;;AAxBc;cA8BF,gBAAA,kBAAgB,OAAA,CAAA,qBAAA"}
|
|
@@ -1,34 +1 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { createContext } from "react";
|
|
3
|
-
/**
|
|
4
|
-
* Context value used when the provider is mounted without a kernel — every
|
|
5
|
-
* accessor returns `null` and mutations are no-ops. Lets `useInterfere()`
|
|
6
|
-
* work seamlessly across SSR/CSR boundaries (server gets nulls, client
|
|
7
|
-
* gets real values once the kernel resolves).
|
|
8
|
-
*/
|
|
9
|
-
const NULL_CONTEXT_VALUE = {
|
|
10
|
-
consent: {
|
|
11
|
-
get: () => null,
|
|
12
|
-
set: () => void 0
|
|
13
|
-
},
|
|
14
|
-
device: {
|
|
15
|
-
getDeviceId: () => null,
|
|
16
|
-
getFpHash: () => null
|
|
17
|
-
},
|
|
18
|
-
identity: {
|
|
19
|
-
get: () => null,
|
|
20
|
-
set: () => Promise.resolve()
|
|
21
|
-
},
|
|
22
|
-
kernel: null,
|
|
23
|
-
session: {
|
|
24
|
-
getId: () => null,
|
|
25
|
-
getWindowId: () => null
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* Lives in its own module so `provider.tsx` and `internal/capture-boundary.tsx`
|
|
30
|
-
* can both import it without forming an import cycle.
|
|
31
|
-
*/
|
|
32
|
-
const InterfereContext = createContext(null);
|
|
33
|
-
//#endregion
|
|
34
|
-
export { InterfereContext, NULL_CONTEXT_VALUE };
|
|
1
|
+
"use client";import{createContext}from"react";const NULL_CONTEXT_VALUE={consent:{get:()=>null,set:()=>void 0},device:{getDeviceId:()=>null,getFpHash:()=>null},identity:{get:()=>null,set:()=>Promise.resolve()},kernel:null,session:{getId:()=>null,getWindowId:()=>null}},InterfereContext=createContext(null);export{InterfereContext,NULL_CONTEXT_VALUE};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-context.mjs","names":[],"sources":["../../src/internal/react-context.ts"],"sourcesContent":["\"use client\";\n\nimport type { IdentifyParams } from \"@interfere/types/sdk/identify\";\nimport type { ConsentState } from \"@interfere/types/sdk/plugins/manifest\";\n\nimport { createContext } from \"react\";\n\nimport type { Kernel } from \"./kernel.js\";\n\nexport interface InterfereContextValue {\n consent: {\n get(): ConsentState | null;\n set(state?: ConsentState): void;\n };\n device: {\n getDeviceId(): string | null;\n getFpHash(): string | null;\n };\n identity: {\n get(): IdentifyParams | null;\n set(params: IdentifyParams): Promise<void>;\n };\n /**\n * The active kernel, or `null` when the SDK isn't initialized yet — for\n * example during server-side prerender, when `init()` is client-only.\n * Accessor objects (consent/device/identity/session) above stay safe to\n * call in either state; they no-op when the kernel is null.\n */\n kernel: Kernel | null;\n session: {\n getId(): string | null;\n getWindowId(): string | null;\n };\n}\n\nconst NULL_CONSENT = { get: () => null, set: () => undefined };\nconst NULL_DEVICE = { getDeviceId: () => null, getFpHash: () => null };\nconst NULL_IDENTITY = {\n get: () => null,\n set: () => Promise.resolve(),\n};\nconst NULL_SESSION = { getId: () => null, getWindowId: () => null };\n\n/**\n * Context value used when the provider is mounted without a kernel — every\n * accessor returns `null` and mutations are no-ops. Lets `useInterfere()`\n * work seamlessly across SSR/CSR boundaries (server gets nulls, client\n * gets real values once the kernel resolves).\n */\nexport const NULL_CONTEXT_VALUE: InterfereContextValue = {\n consent: NULL_CONSENT,\n device: NULL_DEVICE,\n identity: NULL_IDENTITY,\n kernel: null,\n session: NULL_SESSION,\n};\n\n/**\n * Lives in its own module so `provider.tsx` and `internal/capture-boundary.tsx`\n * can both import it without forming an import cycle.\n */\nexport const InterfereContext = createContext<InterfereContextValue | null>(\n null\n);\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"react-context.mjs","names":[],"sources":["../../src/internal/react-context.ts"],"sourcesContent":["\"use client\";\n\nimport type { IdentifyParams } from \"@interfere/types/sdk/identify\";\nimport type { ConsentState } from \"@interfere/types/sdk/plugins/manifest\";\n\nimport { createContext } from \"react\";\n\nimport type { Kernel } from \"./kernel.js\";\n\nexport interface InterfereContextValue {\n consent: {\n get(): ConsentState | null;\n set(state?: ConsentState): void;\n };\n device: {\n getDeviceId(): string | null;\n getFpHash(): string | null;\n };\n identity: {\n get(): IdentifyParams | null;\n set(params: IdentifyParams): Promise<void>;\n };\n /**\n * The active kernel, or `null` when the SDK isn't initialized yet — for\n * example during server-side prerender, when `init()` is client-only.\n * Accessor objects (consent/device/identity/session) above stay safe to\n * call in either state; they no-op when the kernel is null.\n */\n kernel: Kernel | null;\n session: {\n getId(): string | null;\n getWindowId(): string | null;\n };\n}\n\nconst NULL_CONSENT = { get: () => null, set: () => undefined };\nconst NULL_DEVICE = { getDeviceId: () => null, getFpHash: () => null };\nconst NULL_IDENTITY = {\n get: () => null,\n set: () => Promise.resolve(),\n};\nconst NULL_SESSION = { getId: () => null, getWindowId: () => null };\n\n/**\n * Context value used when the provider is mounted without a kernel — every\n * accessor returns `null` and mutations are no-ops. Lets `useInterfere()`\n * work seamlessly across SSR/CSR boundaries (server gets nulls, client\n * gets real values once the kernel resolves).\n */\nexport const NULL_CONTEXT_VALUE: InterfereContextValue = {\n consent: NULL_CONSENT,\n device: NULL_DEVICE,\n identity: NULL_IDENTITY,\n kernel: null,\n session: NULL_SESSION,\n};\n\n/**\n * Lives in its own module so `provider.tsx` and `internal/capture-boundary.tsx`\n * can both import it without forming an import cycle.\n */\nexport const InterfereContext = createContext<InterfereContextValue | null>(\n null\n);\n"],"mappings":"8CAiDA,MAAa,mBAA4C,CACvD,QAAS,CAfY,QAAW,KAAM,QAAW,IAAA,EAexC,EACT,OAAQ,CAfY,gBAAmB,KAAM,cAAiB,IAetD,EACR,SAAU,CAdV,QAAW,KACX,QAAW,QAAQ,QAAQ,CAajB,EACV,OAAQ,KACR,QAAS,CAbY,UAAa,KAAM,gBAAmB,IAalD,CACX,EAMa,iBAAmB,cAC9B,IACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sw.d.mts","names":[],"sources":["../../src/internal/sw.ts"],"mappings":";UAMiB,qBAAA;EAAA;;;;;;;EAQf,SAAA,IAAa,OAAA,EAAS,
|
|
1
|
+
{"version":3,"file":"sw.d.mts","names":[],"sources":["../../src/internal/sw.ts"],"mappings":";UAMiB,qBAAA;EAAA;;;;;;;EAQf,SAAA,IAAa,OAAA,EAAS,SAAS;AAAA;AAAA,UAGhB,SAAA;EACf,MAAA;EACA,IAAA;EAIA,GAAA;AAAA;;;;AAAG;AAQL;iBAAsB,qBAAA,CACpB,IAAA,GAAM,qBAAA,GACL,OAAO"}
|
package/dist/internal/sw.mjs
CHANGED
|
@@ -1,37 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { resolveRoutePrefix } from "@interfere/constants/route-prefix";
|
|
3
|
-
//#region src/internal/sw.ts
|
|
4
|
-
const log = createLogger("sw");
|
|
5
|
-
/**
|
|
6
|
-
* Best-effort: never throws, never blocks. SW registration is a
|
|
7
|
-
* page-load-time durability bonus; if it fails, the SDK falls back to
|
|
8
|
-
* direct fetch and behaves exactly like 9.x did.
|
|
9
|
-
*/
|
|
10
|
-
async function registerServiceWorker(opts = {}) {
|
|
11
|
-
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return;
|
|
12
|
-
const prefix = resolveRoutePrefix();
|
|
13
|
-
const swPath = `${prefix}/sw`;
|
|
14
|
-
const swScope = `${prefix}/`;
|
|
15
|
-
try {
|
|
16
|
-
await navigator.serviceWorker.register(swPath, { scope: swScope });
|
|
17
|
-
log.debug("registered at %s (scope %s)", swPath, swScope);
|
|
18
|
-
} catch (err) {
|
|
19
|
-
log.warn("registration failed, falling back to direct fetch", err);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
if (opts.onMessage) {
|
|
23
|
-
const handler = (event) => {
|
|
24
|
-
const data = event.data;
|
|
25
|
-
if (!isSwMessage(data)) return;
|
|
26
|
-
opts.onMessage?.(data);
|
|
27
|
-
};
|
|
28
|
-
navigator.serviceWorker.addEventListener("message", handler);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function isSwMessage(value) {
|
|
32
|
-
if (!value || typeof value !== "object") return false;
|
|
33
|
-
const v = value;
|
|
34
|
-
return (v["type"] === "interfere.sw.queued" || v["type"] === "interfere.sw.replayed" || v["type"] === "interfere.sw.dropped") && typeof v["url"] === "string";
|
|
35
|
-
}
|
|
36
|
-
//#endregion
|
|
37
|
-
export { registerServiceWorker };
|
|
1
|
+
import{createLogger}from"../util/log.mjs";import{resolveRoutePrefix}from"@interfere/constants/route-prefix";const log=createLogger(`sw`);async function registerServiceWorker(opts={}){if(typeof navigator>`u`||!(`serviceWorker`in navigator))return;let prefix=resolveRoutePrefix(),swPath=`${prefix}/sw`,swScope=`${prefix}/`;try{await navigator.serviceWorker.register(swPath,{scope:swScope}),log.debug(`registered at %s (scope %s)`,swPath,swScope)}catch(err){log.warn(`registration failed, falling back to direct fetch`,err);return}opts.onMessage&&navigator.serviceWorker.addEventListener(`message`,event=>{let data=event.data;isSwMessage(data)&&opts.onMessage?.(data)})}function isSwMessage(value){if(!value||typeof value!=`object`)return!1;let v=value;return(v.type===`interfere.sw.queued`||v.type===`interfere.sw.replayed`||v.type===`interfere.sw.dropped`)&&typeof v.url==`string`}export{registerServiceWorker};
|
package/dist/internal/sw.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sw.mjs","names":[],"sources":["../../src/internal/sw.ts"],"sourcesContent":["import { resolveRoutePrefix } from \"@interfere/constants/route-prefix\";\n\nimport { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"sw\");\n\nexport interface SwRegistrationOptions {\n /**\n * Optional hook to bridge SW → kernel telemetry. The SW posts\n * `{ type: \"interfere.sw.queued\" | ... }` messages back to the\n * page when it queues, replays, or drops a request; if provided,\n * this fires for each one so the kernel can record an OTel span\n * event.\n */\n onMessage?: (message: SwMessage) => void;\n}\n\nexport interface SwMessage {\n reason?: string;\n type:\n | \"interfere.sw.queued\"\n | \"interfere.sw.replayed\"\n | \"interfere.sw.dropped\";\n url: string;\n}\n\n/**\n * Best-effort: never throws, never blocks. SW registration is a\n * page-load-time durability bonus; if it fails, the SDK falls back to\n * direct fetch and behaves exactly like 9.x did.\n */\nexport async function registerServiceWorker(\n opts: SwRegistrationOptions = {}\n): Promise<void> {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return;\n }\n\n // Customers can override the proxy mount point via\n // `NEXT_PUBLIC_INTERFERE_ROUTE_PREFIX` (homepage / dashboard both do\n // this). Hardcoding `/api/interfere/sw` would 404 on those surfaces\n // and Chrome stalls the install handler when the SW script can't be\n // fetched, so we resolve the same prefix the rest of the SDK uses.\n const prefix = resolveRoutePrefix();\n const swPath = `${prefix}/sw`;\n const swScope = `${prefix}/`;\n\n try {\n await navigator.serviceWorker.register(swPath, { scope: swScope });\n log.debug(\"registered at %s (scope %s)\", swPath, swScope);\n } catch (err) {\n log.warn(\"registration failed, falling back to direct fetch\", err);\n return;\n }\n\n if (opts.onMessage) {\n const handler = (event: MessageEvent<unknown>) => {\n const data = event.data;\n if (!isSwMessage(data)) {\n return;\n }\n opts.onMessage?.(data);\n };\n navigator.serviceWorker.addEventListener(\"message\", handler);\n }\n}\n\nfunction isSwMessage(value: unknown): value is SwMessage {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const v = value as Record<string, unknown>;\n return (\n (v[\"type\"] === \"interfere.sw.queued\" ||\n v[\"type\"] === \"interfere.sw.replayed\" ||\n v[\"type\"] === \"interfere.sw.dropped\") &&\n typeof v[\"url\"] === \"string\"\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"sw.mjs","names":[],"sources":["../../src/internal/sw.ts"],"sourcesContent":["import { resolveRoutePrefix } from \"@interfere/constants/route-prefix\";\n\nimport { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"sw\");\n\nexport interface SwRegistrationOptions {\n /**\n * Optional hook to bridge SW → kernel telemetry. The SW posts\n * `{ type: \"interfere.sw.queued\" | ... }` messages back to the\n * page when it queues, replays, or drops a request; if provided,\n * this fires for each one so the kernel can record an OTel span\n * event.\n */\n onMessage?: (message: SwMessage) => void;\n}\n\nexport interface SwMessage {\n reason?: string;\n type:\n | \"interfere.sw.queued\"\n | \"interfere.sw.replayed\"\n | \"interfere.sw.dropped\";\n url: string;\n}\n\n/**\n * Best-effort: never throws, never blocks. SW registration is a\n * page-load-time durability bonus; if it fails, the SDK falls back to\n * direct fetch and behaves exactly like 9.x did.\n */\nexport async function registerServiceWorker(\n opts: SwRegistrationOptions = {}\n): Promise<void> {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return;\n }\n\n // Customers can override the proxy mount point via\n // `NEXT_PUBLIC_INTERFERE_ROUTE_PREFIX` (homepage / dashboard both do\n // this). Hardcoding `/api/interfere/sw` would 404 on those surfaces\n // and Chrome stalls the install handler when the SW script can't be\n // fetched, so we resolve the same prefix the rest of the SDK uses.\n const prefix = resolveRoutePrefix();\n const swPath = `${prefix}/sw`;\n const swScope = `${prefix}/`;\n\n try {\n await navigator.serviceWorker.register(swPath, { scope: swScope });\n log.debug(\"registered at %s (scope %s)\", swPath, swScope);\n } catch (err) {\n log.warn(\"registration failed, falling back to direct fetch\", err);\n return;\n }\n\n if (opts.onMessage) {\n const handler = (event: MessageEvent<unknown>) => {\n const data = event.data;\n if (!isSwMessage(data)) {\n return;\n }\n opts.onMessage?.(data);\n };\n navigator.serviceWorker.addEventListener(\"message\", handler);\n }\n}\n\nfunction isSwMessage(value: unknown): value is SwMessage {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const v = value as Record<string, unknown>;\n return (\n (v[\"type\"] === \"interfere.sw.queued\" ||\n v[\"type\"] === \"interfere.sw.replayed\" ||\n v[\"type\"] === \"interfere.sw.dropped\") &&\n typeof v[\"url\"] === \"string\"\n );\n}\n"],"mappings":"4GAIA,MAAM,IAAM,aAAa,IAAI,EA2B7B,eAAsB,sBACpB,KAA8B,CAAC,EAChB,CACf,GAAI,OAAO,UAAc,KAAe,EAAE,kBAAmB,WAC3D,OAQF,IAAM,OAAS,mBAAmB,EAC5B,OAAS,GAAG,OAAO,KACnB,QAAU,GAAG,OAAO,GAE1B,GAAI,CACF,MAAM,UAAU,cAAc,SAAS,OAAQ,CAAE,MAAO,OAAQ,CAAC,EACjE,IAAI,MAAM,8BAA+B,OAAQ,OAAO,CAC1D,OAAS,IAAK,CACZ,IAAI,KAAK,oDAAqD,GAAG,EACjE,MACF,CAEI,KAAK,WAQP,UAAU,cAAc,iBAAiB,UAPxB,OAAiC,CAChD,IAAM,KAAO,MAAM,KACd,YAAY,IAAI,GAGrB,KAAK,YAAY,IAAI,CACvB,CAC2D,CAE/D,CAEA,SAAS,YAAY,MAAoC,CACvD,GAAI,CAAC,OAAS,OAAO,OAAU,SAC7B,MAAO,GAET,IAAM,EAAI,MACV,OACG,EAAE,OAAY,uBACb,EAAE,OAAY,yBACd,EAAE,OAAY,yBAChB,OAAO,EAAE,KAAW,QAExB"}
|
|
@@ -1,7 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
//#region src/internal/version.ts
|
|
3
|
-
const SDK_NAME = name;
|
|
4
|
-
const SDK_VERSION = version;
|
|
5
|
-
const PRODUCER_VERSION = `${SDK_NAME}@${SDK_VERSION}`;
|
|
6
|
-
//#endregion
|
|
7
|
-
export { PRODUCER_VERSION, SDK_NAME, SDK_VERSION };
|
|
1
|
+
import{name,version}from"../package.mjs";const SDK_NAME=name,SDK_VERSION=version,PRODUCER_VERSION=`${SDK_NAME}@${SDK_VERSION}`;export{PRODUCER_VERSION,SDK_NAME,SDK_VERSION};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.mjs","names":["pkg.name","pkg.version"],"sources":["../../src/internal/version.ts"],"sourcesContent":["import pkg from \"../../package.json\" with { type: \"json\" };\n\nexport const SDK_NAME = pkg.name;\nexport const SDK_VERSION = pkg.version;\nexport const PRODUCER_VERSION = `${SDK_NAME}@${SDK_VERSION}`;\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"version.mjs","names":["pkg.name","pkg.version"],"sources":["../../src/internal/version.ts"],"sourcesContent":["import pkg from \"../../package.json\" with { type: \"json\" };\n\nexport const SDK_NAME = pkg.name;\nexport const SDK_VERSION = pkg.version;\nexport const PRODUCER_VERSION = `${SDK_NAME}@${SDK_VERSION}`;\n"],"mappings":"yCAEA,MAAa,SAAWA,KACX,YAAcC,QACd,iBAAmB,GAAG,SAAS,GAAG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrapper-singleton.d.mts","names":[],"sources":["../../src/internal/wrapper-singleton.ts"],"mappings":";;;;;UAaiB,gBAAA;EACf,KAAA,IAAS,OAAA;EACT,OAAA;IACE,GAAA,IAAO,YAAA;IACP,GAAA,CAAI,KAAA,GAAQ,YAAA;EAAA;EAEd,SAAA,IAAa,MAAA;EACb,eAAA,IAAmB,MAAA;EACnB,QAAA;IACE,GAAA,IAAO,cAAA;IACP,GAAA,CAAI,MAAA,EAAQ,cAAA,GAAiB,OAAA;EAAA;EAE/B,IAAA,CAAK,IAAA,GAAO,aAAA,GAAgB,OAAA,CAAQ,MAAA;EACpC,iBAAA,CAAkB,QAAA;AAAA;AAAA,UAGH,qBAAA;EAJoB;;;;;EAUnC,aAAA;EAnBS;;;;;EAyBT,
|
|
1
|
+
{"version":3,"file":"wrapper-singleton.d.mts","names":[],"sources":["../../src/internal/wrapper-singleton.ts"],"mappings":";;;;;UAaiB,gBAAA;EACf,KAAA,IAAS,OAAA;EACT,OAAA;IACE,GAAA,IAAO,YAAA;IACP,GAAA,CAAI,KAAA,GAAQ,YAAA;EAAA;EAEd,SAAA,IAAa,MAAA;EACb,eAAA,IAAmB,MAAA;EACnB,QAAA;IACE,GAAA,IAAO,cAAA;IACP,GAAA,CAAI,MAAA,EAAQ,cAAA,GAAiB,OAAA;EAAA;EAE/B,IAAA,CAAK,IAAA,GAAO,aAAA,GAAgB,OAAA,CAAQ,MAAA;EACpC,iBAAA,CAAkB,QAAA;AAAA;AAAA,UAGH,qBAAA;EAJoB;;;;;EAUnC,aAAA;EAnBS;;;;;EAyBT,eAAe;AAAA;;;;;;;;;;;iBAaD,sBAAA,CACd,KAAA,EAAO,qBAAA,GACN,gBAAgB"}
|