@interfere/react 9.0.2 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/api.d.mts +25 -0
- package/dist/api.d.mts.map +1 -0
- package/dist/api.mjs +68 -0
- package/dist/api.mjs.map +1 -0
- package/dist/error-boundary.d.mts +11 -4
- package/dist/error-boundary.d.mts.map +1 -1
- package/dist/error-boundary.mjs +6 -3
- package/dist/error-boundary.mjs.map +1 -1
- package/dist/internal/browser-context.d.mts +6 -0
- package/dist/internal/browser-context.d.mts.map +1 -0
- package/dist/internal/browser-context.mjs +59 -0
- package/dist/internal/browser-context.mjs.map +1 -0
- package/dist/internal/capture-boundary.d.mts +5 -1
- package/dist/internal/capture-boundary.d.mts.map +1 -1
- package/dist/internal/capture-boundary.mjs +9 -5
- package/dist/internal/capture-boundary.mjs.map +1 -1
- package/dist/internal/capture.d.mts +16 -5
- package/dist/internal/capture.d.mts.map +1 -1
- package/dist/internal/capture.mjs +20 -16
- package/dist/internal/capture.mjs.map +1 -1
- package/dist/internal/config.d.mts +20 -4
- package/dist/internal/config.d.mts.map +1 -1
- package/dist/internal/config.mjs +12 -12
- package/dist/internal/config.mjs.map +1 -1
- package/dist/internal/consent.d.mts.map +1 -1
- package/dist/internal/consent.mjs +3 -1
- package/dist/internal/consent.mjs.map +1 -1
- package/dist/internal/console-patch.d.mts +19 -0
- package/dist/internal/console-patch.d.mts.map +1 -0
- package/dist/internal/console-patch.mjs +62 -0
- package/dist/internal/console-patch.mjs.map +1 -0
- package/dist/internal/dom/actionable.d.mts +27 -0
- package/dist/internal/dom/actionable.d.mts.map +1 -0
- package/dist/internal/dom/actionable.mjs +62 -0
- package/dist/internal/dom/actionable.mjs.map +1 -0
- package/dist/internal/kernel-registry.d.mts +8 -0
- package/dist/internal/kernel-registry.d.mts.map +1 -0
- package/dist/internal/kernel-registry.mjs +31 -0
- package/dist/internal/kernel-registry.mjs.map +1 -0
- package/dist/internal/kernel.d.mts +267 -0
- package/dist/internal/kernel.d.mts.map +1 -0
- package/dist/internal/kernel.mjs +322 -0
- package/dist/internal/kernel.mjs.map +1 -0
- package/dist/internal/otel/exporter.d.mts +93 -0
- package/dist/internal/otel/exporter.d.mts.map +1 -0
- package/dist/internal/otel/exporter.mjs +212 -0
- package/dist/internal/otel/exporter.mjs.map +1 -0
- package/dist/internal/otel/index.d.mts +6 -0
- package/dist/internal/otel/index.mjs +6 -0
- package/dist/internal/otel/instrumentations.d.mts +42 -0
- package/dist/internal/otel/instrumentations.d.mts.map +1 -0
- package/dist/internal/otel/instrumentations.mjs +150 -0
- package/dist/internal/otel/instrumentations.mjs.map +1 -0
- package/dist/internal/otel/page-scope-context-manager.d.mts +32 -0
- package/dist/internal/otel/page-scope-context-manager.d.mts.map +1 -0
- package/dist/internal/otel/page-scope-context-manager.mjs +36 -0
- package/dist/internal/otel/page-scope-context-manager.mjs.map +1 -0
- package/dist/internal/otel/propagation.d.mts +21 -0
- package/dist/internal/otel/propagation.d.mts.map +1 -0
- package/dist/internal/otel/propagation.mjs +40 -0
- package/dist/internal/otel/propagation.mjs.map +1 -0
- package/dist/internal/otel/provider.d.mts +107 -0
- package/dist/internal/otel/provider.d.mts.map +1 -0
- package/dist/internal/otel/provider.mjs +151 -0
- package/dist/internal/otel/provider.mjs.map +1 -0
- package/dist/internal/otel/web-vitals.d.mts +35 -0
- package/dist/internal/otel/web-vitals.d.mts.map +1 -0
- package/dist/internal/otel/web-vitals.mjs +162 -0
- package/dist/internal/otel/web-vitals.mjs.map +1 -0
- package/dist/internal/page-lifecycle.d.mts +21 -0
- package/dist/internal/page-lifecycle.d.mts.map +1 -0
- package/dist/internal/page-lifecycle.mjs +33 -0
- package/dist/internal/page-lifecycle.mjs.map +1 -0
- package/dist/internal/plugin-runtime.d.mts +0 -2
- package/dist/internal/plugin-runtime.d.mts.map +1 -1
- package/dist/internal/plugin-runtime.mjs +1 -7
- package/dist/internal/plugin-runtime.mjs.map +1 -1
- package/dist/internal/react-context.d.mts +45 -0
- package/dist/internal/react-context.d.mts.map +1 -0
- package/dist/internal/react-context.mjs +34 -0
- package/dist/internal/react-context.mjs.map +1 -0
- package/dist/internal/sw.d.mts +22 -2
- package/dist/internal/sw.d.mts.map +1 -1
- package/dist/internal/sw.mjs +30 -3
- package/dist/internal/sw.mjs.map +1 -1
- package/dist/internal/version.d.mts +3 -1
- package/dist/internal/version.d.mts.map +1 -1
- package/dist/internal/version.mjs +4 -2
- package/dist/internal/version.mjs.map +1 -1
- package/dist/internal/wrapper-singleton.d.mts +47 -0
- package/dist/internal/wrapper-singleton.d.mts.map +1 -0
- package/dist/internal/wrapper-singleton.mjs +73 -0
- package/dist/internal/wrapper-singleton.mjs.map +1 -0
- package/dist/package.mjs +1 -1
- package/dist/plugins/errors.d.mts.map +1 -1
- package/dist/plugins/errors.mjs +18 -25
- package/dist/plugins/errors.mjs.map +1 -1
- package/dist/plugins/lib/loader.d.mts +1 -2
- package/dist/plugins/lib/loader.d.mts.map +1 -1
- package/dist/plugins/lib/loader.mjs +2 -11
- package/dist/plugins/lib/loader.mjs.map +1 -1
- package/dist/plugins/lib/types.d.mts +3 -2
- package/dist/plugins/lib/types.d.mts.map +1 -1
- package/dist/plugins/logs.d.mts +13 -0
- package/dist/plugins/logs.d.mts.map +1 -0
- package/dist/plugins/logs.mjs +53 -0
- package/dist/plugins/logs.mjs.map +1 -0
- package/dist/plugins/rage-clicks.d.mts.map +1 -1
- package/dist/plugins/rage-clicks.mjs +12 -10
- package/dist/plugins/rage-clicks.mjs.map +1 -1
- package/dist/plugins/replay.d.mts.map +1 -1
- package/dist/plugins/replay.mjs +58 -19
- package/dist/plugins/replay.mjs.map +1 -1
- package/dist/provider.d.mts +11 -20
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +13 -14
- package/dist/provider.mjs.map +1 -1
- package/dist/react-error-handler.d.mts +21 -5
- package/dist/react-error-handler.d.mts.map +1 -1
- package/dist/react-error-handler.mjs +15 -7
- package/dist/react-error-handler.mjs.map +1 -1
- package/dist/sw.d.mts +2 -0
- package/dist/sw.mjs +2 -0
- package/dist/tracking/api.d.mts +41 -15
- package/dist/tracking/api.d.mts.map +1 -1
- package/dist/tracking/api.mjs +122 -104
- package/dist/tracking/api.mjs.map +1 -1
- package/dist/tracking/device.d.mts +30 -7
- package/dist/tracking/device.d.mts.map +1 -1
- package/dist/tracking/device.mjs +70 -46
- package/dist/tracking/device.mjs.map +1 -1
- package/dist/tracking/geo.d.mts +11 -3
- package/dist/tracking/geo.d.mts.map +1 -1
- package/dist/tracking/geo.mjs +33 -29
- package/dist/tracking/geo.mjs.map +1 -1
- package/dist/tracking/session.d.mts +3 -1
- package/dist/tracking/session.d.mts.map +1 -1
- package/dist/tracking/session.mjs.map +1 -1
- package/dist/util/bot.d.mts +10 -0
- package/dist/util/bot.d.mts.map +1 -0
- package/dist/util/bot.mjs +14 -0
- package/dist/util/bot.mjs.map +1 -0
- package/dist/util/global.d.mts +10 -0
- package/dist/util/global.d.mts.map +1 -0
- package/dist/util/global.mjs +12 -0
- package/dist/util/global.mjs.map +1 -0
- package/dist/util/log.d.mts.map +1 -1
- package/dist/util/log.mjs +8 -1
- package/dist/util/log.mjs.map +1 -1
- package/dist/util/stringify.d.mts +9 -0
- package/dist/util/stringify.d.mts.map +1 -0
- package/dist/util/stringify.mjs +16 -0
- package/dist/util/stringify.mjs.map +1 -0
- package/package.json +73 -20
- package/dist/internal/client.d.mts +0 -48
- package/dist/internal/client.d.mts.map +0 -1
- package/dist/internal/client.mjs +0 -146
- package/dist/internal/client.mjs.map +0 -1
- package/dist/internal/context.d.mts +0 -6
- package/dist/internal/context.d.mts.map +0 -1
- package/dist/internal/context.mjs +0 -32
- package/dist/internal/context.mjs.map +0 -1
- package/dist/internal/envelope.d.mts +0 -15
- package/dist/internal/envelope.d.mts.map +0 -1
- package/dist/internal/envelope.mjs +0 -24
- package/dist/internal/envelope.mjs.map +0 -1
- package/dist/internal/errors.d.mts +0 -4
- package/dist/internal/errors.d.mts.map +0 -1
- package/dist/internal/errors.mjs +0 -4
- package/dist/internal/errors.mjs.map +0 -1
- package/dist/plugins/device.d.mts +0 -6
- package/dist/plugins/device.d.mts.map +0 -1
- package/dist/plugins/device.mjs +0 -13
- package/dist/plugins/device.mjs.map +0 -1
- package/dist/plugins/pages.d.mts +0 -6
- package/dist/plugins/pages.d.mts.map +0 -1
- package/dist/plugins/pages.mjs +0 -102
- package/dist/plugins/pages.mjs.map +0 -1
- package/dist/transport/http.d.mts +0 -25
- package/dist/transport/http.d.mts.map +0 -1
- package/dist/transport/http.mjs +0 -80
- package/dist/transport/http.mjs.map +0 -1
- package/dist/transport/queue.d.mts +0 -34
- package/dist/transport/queue.d.mts.map +0 -1
- package/dist/transport/queue.mjs +0 -100
- package/dist/transport/queue.mjs.map +0 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Plugin } from "./lib/types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/plugins/logs.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Subscribes to the central console patch so string-only console calls
|
|
6
|
+
* land as OTel `LogRecord`s. Error-bearing calls are skipped: the
|
|
7
|
+
* `errors` plugin's subscriber on the same patch handles those as
|
|
8
|
+
* exceptions. Class boundary stays firm: errors flow as exceptions,
|
|
9
|
+
* strings flow as logs.
|
|
10
|
+
*/
|
|
11
|
+
declare const logsPlugin: Plugin;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { logsPlugin as default, logsPlugin };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logs.d.mts","names":[],"sources":["../../src/plugins/logs.ts"],"mappings":";;;;;AAyBA;;;;;cAAa,UAAA,EAAY,MAAA"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { activeKernel } from "../internal/kernel-registry.mjs";
|
|
2
|
+
import { onConsoleCall } from "../internal/console-patch.mjs";
|
|
3
|
+
import { safeStringify } from "../util/stringify.mjs";
|
|
4
|
+
import { SeverityNumber } from "@opentelemetry/api-logs";
|
|
5
|
+
//#region src/plugins/logs.ts
|
|
6
|
+
const LEVEL_TO_SEVERITY = {
|
|
7
|
+
debug: {
|
|
8
|
+
number: SeverityNumber.DEBUG,
|
|
9
|
+
text: "DEBUG"
|
|
10
|
+
},
|
|
11
|
+
info: {
|
|
12
|
+
number: SeverityNumber.INFO,
|
|
13
|
+
text: "INFO"
|
|
14
|
+
},
|
|
15
|
+
log: {
|
|
16
|
+
number: SeverityNumber.INFO,
|
|
17
|
+
text: "INFO"
|
|
18
|
+
},
|
|
19
|
+
warn: {
|
|
20
|
+
number: SeverityNumber.WARN,
|
|
21
|
+
text: "WARN"
|
|
22
|
+
},
|
|
23
|
+
error: {
|
|
24
|
+
number: SeverityNumber.ERROR,
|
|
25
|
+
text: "ERROR"
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Subscribes to the central console patch so string-only console calls
|
|
30
|
+
* land as OTel `LogRecord`s. Error-bearing calls are skipped: the
|
|
31
|
+
* `errors` plugin's subscriber on the same patch handles those as
|
|
32
|
+
* exceptions. Class boundary stays firm: errors flow as exceptions,
|
|
33
|
+
* strings flow as logs.
|
|
34
|
+
*/
|
|
35
|
+
const logsPlugin = {
|
|
36
|
+
name: "logs",
|
|
37
|
+
setup() {
|
|
38
|
+
return onConsoleCall((level, args) => {
|
|
39
|
+
if (args.some((a) => a instanceof Error)) return;
|
|
40
|
+
const kernel = activeKernel();
|
|
41
|
+
if (!kernel) return;
|
|
42
|
+
const sev = LEVEL_TO_SEVERITY[level];
|
|
43
|
+
kernel.recordLog({
|
|
44
|
+
severityText: sev.text,
|
|
45
|
+
severityNumber: sev.number,
|
|
46
|
+
body: args.length === 1 ? safeStringify(args[0]) : args.map(safeStringify).join(" "),
|
|
47
|
+
attributes: { "console.level": level }
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
//#endregion
|
|
53
|
+
export { logsPlugin as default, logsPlugin };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logs.mjs","names":[],"sources":["../../src/plugins/logs.ts"],"sourcesContent":["import { SeverityNumber } from \"@opentelemetry/api-logs\";\n\nimport { type ConsoleLevel, onConsoleCall } from \"../internal/console-patch.js\";\nimport { activeKernel } from \"../internal/kernel-registry.js\";\nimport { safeStringify } from \"../util/stringify.js\";\nimport type { Plugin } from \"./lib/types.js\";\n\nconst LEVEL_TO_SEVERITY: Record<\n ConsoleLevel,\n { number: SeverityNumber; text: string }\n> = {\n debug: { number: SeverityNumber.DEBUG, text: \"DEBUG\" },\n info: { number: SeverityNumber.INFO, text: \"INFO\" },\n log: { number: SeverityNumber.INFO, text: \"INFO\" },\n warn: { number: SeverityNumber.WARN, text: \"WARN\" },\n error: { number: SeverityNumber.ERROR, text: \"ERROR\" },\n};\n\n/**\n * Subscribes to the central console patch so string-only console calls\n * land as OTel `LogRecord`s. Error-bearing calls are skipped: the\n * `errors` plugin's subscriber on the same patch handles those as\n * exceptions. Class boundary stays firm: errors flow as exceptions,\n * strings flow as logs.\n */\nexport const logsPlugin: Plugin = {\n name: \"logs\",\n\n setup() {\n const unsubscribe = onConsoleCall((level, args) => {\n if (args.some((a) => a instanceof Error)) {\n return;\n }\n const kernel = activeKernel();\n if (!kernel) {\n return;\n }\n const sev = LEVEL_TO_SEVERITY[level];\n kernel.recordLog({\n severityText: sev.text,\n severityNumber: sev.number,\n body:\n args.length === 1\n ? safeStringify(args[0])\n : args.map(safeStringify).join(\" \"),\n attributes: { \"console.level\": level },\n });\n });\n\n return unsubscribe;\n },\n};\n\nexport default logsPlugin;\n"],"mappings":";;;;;AAOA,MAAM,oBAGF;CACF,OAAO;EAAE,QAAQ,eAAe;EAAO,MAAM;EAAS;CACtD,MAAM;EAAE,QAAQ,eAAe;EAAM,MAAM;EAAQ;CACnD,KAAK;EAAE,QAAQ,eAAe;EAAM,MAAM;EAAQ;CAClD,MAAM;EAAE,QAAQ,eAAe;EAAM,MAAM;EAAQ;CACnD,OAAO;EAAE,QAAQ,eAAe;EAAO,MAAM;EAAS;CACvD;;;;;;;;AASD,MAAa,aAAqB;CAChC,MAAM;CAEN,QAAQ;EAqBN,OApBoB,eAAe,OAAO,SAAS;GACjD,IAAI,KAAK,MAAM,MAAM,aAAa,MAAM,EACtC;GAEF,MAAM,SAAS,cAAc;GAC7B,IAAI,CAAC,QACH;GAEF,MAAM,MAAM,kBAAkB;GAC9B,OAAO,UAAU;IACf,cAAc,IAAI;IAClB,gBAAgB,IAAI;IACpB,MACE,KAAK,WAAW,IACZ,cAAc,KAAK,GAAG,GACtB,KAAK,IAAI,cAAc,CAAC,KAAK,IAAI;IACvC,YAAY,EAAE,iBAAiB,OAAO;IACvC,CAAC;IAGc;;CAErB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rage-clicks.d.mts","names":[],"sources":["../../src/plugins/rage-clicks.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"rage-clicks.d.mts","names":[],"sources":["../../src/plugins/rage-clicks.ts"],"mappings":";;;cAgCa,gBAAA,EAAkB,MAAA"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { trace } from "@opentelemetry/api";
|
|
1
2
|
//#region src/plugins/rage-clicks.ts
|
|
2
3
|
const CLICK_THRESHOLD = 3;
|
|
3
4
|
const TIME_WINDOW_MS = 800;
|
|
4
5
|
const PROXIMITY_PX = 30;
|
|
6
|
+
const TRACER_NAME = "@interfere/react/rage-clicks";
|
|
5
7
|
function distance(a, b) {
|
|
6
8
|
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
|
|
7
9
|
}
|
|
@@ -14,7 +16,7 @@ function selectorFor(el) {
|
|
|
14
16
|
}
|
|
15
17
|
const rageClicksPlugin = {
|
|
16
18
|
name: "rage-clicks",
|
|
17
|
-
setup(
|
|
19
|
+
setup() {
|
|
18
20
|
const clicks = [];
|
|
19
21
|
const onClick = (event) => {
|
|
20
22
|
const now = Date.now();
|
|
@@ -32,15 +34,15 @@ const rageClicksPlugin = {
|
|
|
32
34
|
if (clustered.length < CLICK_THRESHOLD) return;
|
|
33
35
|
const last = clustered.at(-1);
|
|
34
36
|
if (!last) return;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
});
|
|
37
|
+
trace.getTracer(TRACER_NAME).startSpan("rage_click", { attributes: {
|
|
38
|
+
"ui.event_type": "rage_click",
|
|
39
|
+
"ui.rage_click.count": clustered.length,
|
|
40
|
+
"ui.rage_click.time_window_ms": last.ts - anchor.ts,
|
|
41
|
+
"ui.rage_click.selector": selectorFor(last.target),
|
|
42
|
+
"ui.rage_click.text": last.target?.textContent?.trim().slice(0, 120) ?? "",
|
|
43
|
+
"ui.rage_click.x": last.x,
|
|
44
|
+
"ui.rage_click.y": last.y
|
|
45
|
+
} }).end();
|
|
44
46
|
clicks.length = 0;
|
|
45
47
|
};
|
|
46
48
|
document.addEventListener("click", onClick, { capture: true });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rage-clicks.mjs","names":[],"sources":["../../src/plugins/rage-clicks.ts"],"sourcesContent":["import type { Plugin } from \"./lib/types.js\";\n\nconst CLICK_THRESHOLD = 3;\nconst TIME_WINDOW_MS = 800;\nconst PROXIMITY_PX = 30;\n\ninterface Click {\n target: Element | null;\n ts: number;\n x: number;\n y: number;\n}\n\nfunction distance(a: Click, b: Click): number {\n return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);\n}\n\nfunction selectorFor(el: Element | null): string {\n if (!el) {\n return \"unknown\";\n }\n if (el.id) {\n return `#${el.id}`;\n }\n const classes = [...el.classList].slice(0, 3).join(\".\");\n const tag = el.tagName.toLowerCase();\n return classes ? `${tag}.${classes}` : tag;\n}\n\nexport const rageClicksPlugin: Plugin = {\n name: \"rage-clicks\",\n\n setup(
|
|
1
|
+
{"version":3,"file":"rage-clicks.mjs","names":[],"sources":["../../src/plugins/rage-clicks.ts"],"sourcesContent":["import { trace } from \"@opentelemetry/api\";\n\nimport type { Plugin } from \"./lib/types.js\";\n\nconst CLICK_THRESHOLD = 3;\nconst TIME_WINDOW_MS = 800;\nconst PROXIMITY_PX = 30;\nconst TRACER_NAME = \"@interfere/react/rage-clicks\";\n\ninterface Click {\n target: Element | null;\n ts: number;\n x: number;\n y: number;\n}\n\nfunction distance(a: Click, b: Click): number {\n return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);\n}\n\nfunction selectorFor(el: Element | null): string {\n if (!el) {\n return \"unknown\";\n }\n if (el.id) {\n return `#${el.id}`;\n }\n const classes = [...el.classList].slice(0, 3).join(\".\");\n const tag = el.tagName.toLowerCase();\n return classes ? `${tag}.${classes}` : tag;\n}\n\nexport const rageClicksPlugin: Plugin = {\n name: \"rage-clicks\",\n\n setup() {\n const clicks: Click[] = [];\n\n const onClick = (event: MouseEvent) => {\n const now = Date.now();\n clicks.push({\n x: event.clientX,\n y: event.clientY,\n ts: now,\n target: event.target instanceof Element ? event.target : null,\n });\n\n // Prune stale clicks\n while (clicks.length > 0 && now - (clicks[0]?.ts ?? 0) > TIME_WINDOW_MS) {\n clicks.shift();\n }\n\n if (clicks.length < CLICK_THRESHOLD) {\n return;\n }\n\n // Check proximity — all clicks within PROXIMITY_PX of the first\n const anchor = clicks[0];\n if (!anchor) {\n return;\n }\n const clustered = clicks.filter(\n (c) => distance(anchor, c) <= PROXIMITY_PX\n );\n if (clustered.length < CLICK_THRESHOLD) {\n return;\n }\n\n const last = clustered.at(-1);\n if (!last) {\n return;\n }\n\n // Emit as a discrete OTel span. The cluster of clicks is a single\n // user-experience event; a span (rather than a span event on\n // whatever's currently active) keeps the rage-click correlatable\n // by `ui.event_type=rage_click` regardless of whether a navigation\n // / interaction span happens to be open at click time.\n const span = trace.getTracer(TRACER_NAME).startSpan(\"rage_click\", {\n attributes: {\n \"ui.event_type\": \"rage_click\",\n \"ui.rage_click.count\": clustered.length,\n \"ui.rage_click.time_window_ms\": last.ts - anchor.ts,\n \"ui.rage_click.selector\": selectorFor(last.target),\n \"ui.rage_click.text\":\n last.target?.textContent?.trim().slice(0, 120) ?? \"\",\n \"ui.rage_click.x\": last.x,\n \"ui.rage_click.y\": last.y,\n },\n });\n span.end();\n\n clicks.length = 0;\n };\n\n document.addEventListener(\"click\", onClick, { capture: true });\n\n return () => {\n document.removeEventListener(\"click\", onClick, { capture: true });\n };\n },\n};\n\nexport default rageClicksPlugin;\n"],"mappings":";;AAIA,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AACvB,MAAM,eAAe;AACrB,MAAM,cAAc;AASpB,SAAS,SAAS,GAAU,GAAkB;CAC5C,OAAO,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE;;AAGvD,SAAS,YAAY,IAA4B;CAC/C,IAAI,CAAC,IACH,OAAO;CAET,IAAI,GAAG,IACL,OAAO,IAAI,GAAG;CAEhB,MAAM,UAAU,CAAC,GAAG,GAAG,UAAU,CAAC,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI;CACvD,MAAM,MAAM,GAAG,QAAQ,aAAa;CACpC,OAAO,UAAU,GAAG,IAAI,GAAG,YAAY;;AAGzC,MAAa,mBAA2B;CACtC,MAAM;CAEN,QAAQ;EACN,MAAM,SAAkB,EAAE;EAE1B,MAAM,WAAW,UAAsB;GACrC,MAAM,MAAM,KAAK,KAAK;GACtB,OAAO,KAAK;IACV,GAAG,MAAM;IACT,GAAG,MAAM;IACT,IAAI;IACJ,QAAQ,MAAM,kBAAkB,UAAU,MAAM,SAAS;IAC1D,CAAC;GAGF,OAAO,OAAO,SAAS,KAAK,OAAO,OAAO,IAAI,MAAM,KAAK,gBACvD,OAAO,OAAO;GAGhB,IAAI,OAAO,SAAS,iBAClB;GAIF,MAAM,SAAS,OAAO;GACtB,IAAI,CAAC,QACH;GAEF,MAAM,YAAY,OAAO,QACtB,MAAM,SAAS,QAAQ,EAAE,IAAI,aAC/B;GACD,IAAI,UAAU,SAAS,iBACrB;GAGF,MAAM,OAAO,UAAU,GAAG,GAAG;GAC7B,IAAI,CAAC,MACH;GAoBF,MAZmB,UAAU,YAAY,CAAC,UAAU,cAAc,EAChE,YAAY;IACV,iBAAiB;IACjB,uBAAuB,UAAU;IACjC,gCAAgC,KAAK,KAAK,OAAO;IACjD,0BAA0B,YAAY,KAAK,OAAO;IAClD,sBACE,KAAK,QAAQ,aAAa,MAAM,CAAC,MAAM,GAAG,IAAI,IAAI;IACpD,mBAAmB,KAAK;IACxB,mBAAmB,KAAK;IACzB,EACF,CACG,CAAC,KAAK;GAEV,OAAO,SAAS;;EAGlB,SAAS,iBAAiB,SAAS,SAAS,EAAE,SAAS,MAAM,CAAC;EAE9D,aAAa;GACX,SAAS,oBAAoB,SAAS,SAAS,EAAE,SAAS,MAAM,CAAC;;;CAGtE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay.d.mts","names":[],"sources":["../../src/plugins/replay.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"replay.d.mts","names":[],"sources":["../../src/plugins/replay.ts"],"mappings":";;;cAoFa,YAAA,EAAc,MAAA"}
|
package/dist/plugins/replay.mjs
CHANGED
|
@@ -1,10 +1,52 @@
|
|
|
1
1
|
import { createLogger } from "../util/log.mjs";
|
|
2
|
+
import { resolveTargets } from "../internal/config.mjs";
|
|
3
|
+
import { onPageHidden } from "../internal/page-lifecycle.mjs";
|
|
4
|
+
import { trace } from "@opentelemetry/api";
|
|
2
5
|
//#region src/plugins/replay.ts
|
|
3
6
|
const log = createLogger("replay");
|
|
4
7
|
const FLUSH_INTERVAL_MS = 1e4;
|
|
8
|
+
const UPLOAD_PATH_PREFIX = "/v2/replay/upload/";
|
|
9
|
+
const TRACER_NAME = "@interfere/react/replay";
|
|
10
|
+
const TRAILING_SLASH_RE = /\/$/;
|
|
11
|
+
/**
|
|
12
|
+
* POSTs the chunk's events array to the collector. The collector writes
|
|
13
|
+
* to R2 server-side and returns the object key we stamp onto the
|
|
14
|
+
* `replay.chunk` span event. Single round trip; if the request fails,
|
|
15
|
+
* the chunk is dropped — replay is best-effort and the customer's next
|
|
16
|
+
* session will still record cleanly.
|
|
17
|
+
*/
|
|
18
|
+
async function deliverChunk({ ctx, uploadBaseUrl, authHeaders, payload }) {
|
|
19
|
+
const sessionId = ctx.getSessionId();
|
|
20
|
+
if (!sessionId) return;
|
|
21
|
+
const body = JSON.stringify(payload.events);
|
|
22
|
+
const sizeBytes = body.length;
|
|
23
|
+
const res = await fetch(`${uploadBaseUrl}${encodeURIComponent(sessionId)}`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: {
|
|
26
|
+
...authHeaders,
|
|
27
|
+
"content-type": "application/json"
|
|
28
|
+
},
|
|
29
|
+
body
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
log.warn("replay chunk upload rejected (%d)", res.status);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const { objectKey } = await res.json();
|
|
36
|
+
trace.getTracer(TRACER_NAME).startSpan("replay.chunk", { attributes: {
|
|
37
|
+
"replay.chunk.uri": objectKey,
|
|
38
|
+
"replay.chunk.size_bytes": sizeBytes,
|
|
39
|
+
"replay.chunk.event_count": payload.events.length,
|
|
40
|
+
...payload.firstTs === null ? {} : { "replay.chunk.first_event_ts": payload.firstTs },
|
|
41
|
+
...payload.lastTs === null ? {} : { "replay.chunk.last_event_ts": payload.lastTs }
|
|
42
|
+
} }).end();
|
|
43
|
+
}
|
|
5
44
|
const replayPlugin = {
|
|
6
45
|
name: "replay",
|
|
7
46
|
setup(ctx) {
|
|
47
|
+
const targets = resolveTargets();
|
|
48
|
+
const uploadBaseUrl = `${targets.collectorBaseUrl.replace(TRAILING_SLASH_RE, "")}${UPLOAD_PATH_PREFIX}`;
|
|
49
|
+
const authHeaders = Object.fromEntries(targets.ingest.headers.entries());
|
|
8
50
|
let stopFn = null;
|
|
9
51
|
let events = [];
|
|
10
52
|
let flushTimer = null;
|
|
@@ -14,24 +56,23 @@ const replayPlugin = {
|
|
|
14
56
|
if (events.length === 0) return;
|
|
15
57
|
const chunk = events;
|
|
16
58
|
events = [];
|
|
17
|
-
const
|
|
18
|
-
|
|
59
|
+
const payload = {
|
|
60
|
+
events: chunk,
|
|
61
|
+
firstTs,
|
|
62
|
+
lastTs
|
|
63
|
+
};
|
|
19
64
|
firstTs = null;
|
|
20
65
|
lastTs = null;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
66
|
+
deliverChunk({
|
|
67
|
+
ctx,
|
|
68
|
+
uploadBaseUrl,
|
|
69
|
+
authHeaders,
|
|
70
|
+
payload
|
|
71
|
+
}).catch((error) => {
|
|
72
|
+
log.warn("replay chunk dropped: %o", error);
|
|
27
73
|
});
|
|
28
74
|
};
|
|
29
|
-
|
|
30
|
-
if (document.visibilityState === "hidden") flush();
|
|
31
|
-
};
|
|
32
|
-
const onBeforeUnload = () => {
|
|
33
|
-
flush();
|
|
34
|
-
};
|
|
75
|
+
let unsubscribeHidden = null;
|
|
35
76
|
const init = async () => {
|
|
36
77
|
try {
|
|
37
78
|
stopFn = (await import("rrweb")).record({ emit(event) {
|
|
@@ -41,20 +82,18 @@ const replayPlugin = {
|
|
|
41
82
|
events.push(JSON.stringify(event));
|
|
42
83
|
} }) ?? null;
|
|
43
84
|
flushTimer = setInterval(flush, FLUSH_INTERVAL_MS);
|
|
44
|
-
|
|
45
|
-
globalThis.addEventListener("beforeunload", onBeforeUnload);
|
|
85
|
+
unsubscribeHidden = onPageHidden(flush);
|
|
46
86
|
log.debug("recording started");
|
|
47
87
|
} catch {
|
|
48
88
|
log.error("rrweb failed to load, replay disabled");
|
|
49
89
|
}
|
|
50
90
|
};
|
|
51
|
-
init()
|
|
91
|
+
init();
|
|
52
92
|
return () => {
|
|
53
93
|
flush();
|
|
54
94
|
stopFn?.();
|
|
55
95
|
if (flushTimer) clearInterval(flushTimer);
|
|
56
|
-
|
|
57
|
-
globalThis.removeEventListener("beforeunload", onBeforeUnload);
|
|
96
|
+
unsubscribeHidden?.();
|
|
58
97
|
};
|
|
59
98
|
}
|
|
60
99
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay.mjs","names":[],"sources":["../../src/plugins/replay.ts"],"sourcesContent":["import { createLogger } from \"../util/log.js\";\nimport type { Plugin } from \"./lib/types.js\";\n\nconst log = createLogger(\"replay\");\n\nconst FLUSH_INTERVAL_MS = 10_000;\n\nexport const replayPlugin: Plugin = {\n name: \"replay\",\n\n setup(ctx) {\n let stopFn: (() => void) | null = null;\n let events: string[] = [];\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n let firstTs: number | null = null;\n let lastTs: number | null = null;\n\n const flush = () => {\n if (events.length === 0) {\n return;\n }\n const chunk = events;\n events = [];\n const
|
|
1
|
+
{"version":3,"file":"replay.mjs","names":[],"sources":["../../src/plugins/replay.ts"],"sourcesContent":["import { trace } from \"@opentelemetry/api\";\n\nimport { resolveTargets } from \"../internal/config.js\";\nimport { onPageHidden } from \"../internal/page-lifecycle.js\";\nimport { createLogger } from \"../util/log.js\";\nimport type { Plugin, PluginContext } from \"./lib/types.js\";\n\nconst log = createLogger(\"replay\");\n\nconst FLUSH_INTERVAL_MS = 10_000;\nconst UPLOAD_PATH_PREFIX = \"/v2/replay/upload/\";\nconst TRACER_NAME = \"@interfere/react/replay\";\nconst TRAILING_SLASH_RE = /\\/$/;\n\ninterface UploadResponse {\n objectKey: string;\n}\n\ninterface ChunkPayload {\n events: string[];\n firstTs: number | null;\n lastTs: number | null;\n}\n\ninterface DeliverInput {\n authHeaders: Record<string, string>;\n ctx: PluginContext;\n payload: ChunkPayload;\n uploadBaseUrl: string;\n}\n\n/**\n * POSTs the chunk's events array to the collector. The collector writes\n * to R2 server-side and returns the object key we stamp onto the\n * `replay.chunk` span event. Single round trip; if the request fails,\n * the chunk is dropped — replay is best-effort and the customer's next\n * session will still record cleanly.\n */\nasync function deliverChunk({\n ctx,\n uploadBaseUrl,\n authHeaders,\n payload,\n}: DeliverInput): Promise<void> {\n const sessionId = ctx.getSessionId();\n if (!sessionId) {\n return;\n }\n\n const body = JSON.stringify(payload.events);\n const sizeBytes = body.length;\n\n const res = await fetch(`${uploadBaseUrl}${encodeURIComponent(sessionId)}`, {\n method: \"POST\",\n headers: { ...authHeaders, \"content-type\": \"application/json\" },\n body,\n });\n if (!res.ok) {\n log.warn(\"replay chunk upload rejected (%d)\", res.status);\n return;\n }\n\n const { objectKey } = (await res.json()) as UploadResponse;\n\n // Span-per-chunk is the cleanest OTel mapping: replay chunks are\n // events-in-time, not \"things that happened during a span\", so the\n // span itself carries the attrs. Mapper picks up by name later\n // (PRD 3 replay UI work).\n const span = trace.getTracer(TRACER_NAME).startSpan(\"replay.chunk\", {\n attributes: {\n \"replay.chunk.uri\": objectKey,\n \"replay.chunk.size_bytes\": sizeBytes,\n \"replay.chunk.event_count\": payload.events.length,\n ...(payload.firstTs === null\n ? {}\n : { \"replay.chunk.first_event_ts\": payload.firstTs }),\n ...(payload.lastTs === null\n ? {}\n : { \"replay.chunk.last_event_ts\": payload.lastTs }),\n },\n });\n span.end();\n}\n\nexport const replayPlugin: Plugin = {\n name: \"replay\",\n\n setup(ctx) {\n const targets = resolveTargets();\n const uploadBaseUrl = `${targets.collectorBaseUrl.replace(TRAILING_SLASH_RE, \"\")}${UPLOAD_PATH_PREFIX}`;\n const authHeaders = Object.fromEntries(targets.ingest.headers.entries());\n\n let stopFn: (() => void) | null = null;\n let events: string[] = [];\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n let firstTs: number | null = null;\n let lastTs: number | null = null;\n\n const flush = () => {\n if (events.length === 0) {\n return;\n }\n const chunk = events;\n events = [];\n const payload: ChunkPayload = {\n events: chunk,\n firstTs,\n lastTs,\n };\n firstTs = null;\n lastTs = null;\n\n // Fire-and-forget; replay capture must never block the rrweb event\n // loop. Failed chunks are dropped — replay is best-effort and the\n // next session will still record cleanly.\n deliverChunk({ ctx, uploadBaseUrl, authHeaders, payload }).catch(\n (error: unknown) => {\n log.warn(\"replay chunk dropped: %o\", error);\n }\n );\n };\n\n let unsubscribeHidden: (() => void) | null = null;\n\n const init = async () => {\n try {\n const rrweb = await import(\"rrweb\");\n stopFn =\n rrweb.record({\n emit(event) {\n const ts = Date.now();\n if (firstTs === null) {\n firstTs = ts;\n }\n lastTs = ts;\n events.push(JSON.stringify(event));\n },\n }) ?? null;\n\n flushTimer = setInterval(flush, FLUSH_INTERVAL_MS);\n unsubscribeHidden = onPageHidden(flush);\n log.debug(\"recording started\");\n } catch {\n log.error(\"rrweb failed to load, replay disabled\");\n }\n };\n\n // `init()` swallows rrweb load failures internally — no outer `.catch`\n // needed (it can never reject).\n init();\n\n return () => {\n flush();\n stopFn?.();\n if (flushTimer) {\n clearInterval(flushTimer);\n }\n unsubscribeHidden?.();\n };\n },\n};\n\nexport default replayPlugin;\n"],"mappings":";;;;;AAOA,MAAM,MAAM,aAAa,SAAS;AAElC,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB;AAC3B,MAAM,cAAc;AACpB,MAAM,oBAAoB;;;;;;;;AA0B1B,eAAe,aAAa,EAC1B,KACA,eACA,aACA,WAC8B;CAC9B,MAAM,YAAY,IAAI,cAAc;CACpC,IAAI,CAAC,WACH;CAGF,MAAM,OAAO,KAAK,UAAU,QAAQ,OAAO;CAC3C,MAAM,YAAY,KAAK;CAEvB,MAAM,MAAM,MAAM,MAAM,GAAG,gBAAgB,mBAAmB,UAAU,IAAI;EAC1E,QAAQ;EACR,SAAS;GAAE,GAAG;GAAa,gBAAgB;GAAoB;EAC/D;EACD,CAAC;CACF,IAAI,CAAC,IAAI,IAAI;EACX,IAAI,KAAK,qCAAqC,IAAI,OAAO;EACzD;;CAGF,MAAM,EAAE,cAAe,MAAM,IAAI,MAAM;CAmBvC,MAbmB,UAAU,YAAY,CAAC,UAAU,gBAAgB,EAClE,YAAY;EACV,oBAAoB;EACpB,2BAA2B;EAC3B,4BAA4B,QAAQ,OAAO;EAC3C,GAAI,QAAQ,YAAY,OACpB,EAAE,GACF,EAAE,+BAA+B,QAAQ,SAAS;EACtD,GAAI,QAAQ,WAAW,OACnB,EAAE,GACF,EAAE,8BAA8B,QAAQ,QAAQ;EACrD,EACF,CACG,CAAC,KAAK;;AAGZ,MAAa,eAAuB;CAClC,MAAM;CAEN,MAAM,KAAK;EACT,MAAM,UAAU,gBAAgB;EAChC,MAAM,gBAAgB,GAAG,QAAQ,iBAAiB,QAAQ,mBAAmB,GAAG,GAAG;EACnF,MAAM,cAAc,OAAO,YAAY,QAAQ,OAAO,QAAQ,SAAS,CAAC;EAExE,IAAI,SAA8B;EAClC,IAAI,SAAmB,EAAE;EACzB,IAAI,aAAoD;EACxD,IAAI,UAAyB;EAC7B,IAAI,SAAwB;EAE5B,MAAM,cAAc;GAClB,IAAI,OAAO,WAAW,GACpB;GAEF,MAAM,QAAQ;GACd,SAAS,EAAE;GACX,MAAM,UAAwB;IAC5B,QAAQ;IACR;IACA;IACD;GACD,UAAU;GACV,SAAS;GAKT,aAAa;IAAE;IAAK;IAAe;IAAa;IAAS,CAAC,CAAC,OACxD,UAAmB;IAClB,IAAI,KAAK,4BAA4B,MAAM;KAE9C;;EAGH,IAAI,oBAAyC;EAE7C,MAAM,OAAO,YAAY;GACvB,IAAI;IAEF,UACE,MAFkB,OAAO,UAEnB,OAAO,EACX,KAAK,OAAO;KACV,MAAM,KAAK,KAAK,KAAK;KACrB,IAAI,YAAY,MACd,UAAU;KAEZ,SAAS;KACT,OAAO,KAAK,KAAK,UAAU,MAAM,CAAC;OAErC,CAAC,IAAI;IAER,aAAa,YAAY,OAAO,kBAAkB;IAClD,oBAAoB,aAAa,MAAM;IACvC,IAAI,MAAM,oBAAoB;WACxB;IACN,IAAI,MAAM,wCAAwC;;;EAMtD,MAAM;EAEN,aAAa;GACX,OAAO;GACP,UAAU;GACV,IAAI,YACF,cAAc,WAAW;GAE3B,qBAAqB;;;CAG1B"}
|
package/dist/provider.d.mts
CHANGED
|
@@ -1,26 +1,9 @@
|
|
|
1
|
+
import { Kernel } from "./internal/kernel.mjs";
|
|
2
|
+
import { InterfereContext, InterfereContextValue } from "./internal/react-context.mjs";
|
|
1
3
|
import { PropsWithChildren, ReactNode } from "react";
|
|
2
4
|
import { ConsentState } from "@interfere/types/sdk/plugins/manifest";
|
|
3
|
-
import { IdentifyParams } from "@interfere/types/sdk/identify";
|
|
4
5
|
|
|
5
6
|
//#region src/provider.d.ts
|
|
6
|
-
interface InterfereContextValue {
|
|
7
|
-
consent: {
|
|
8
|
-
get(): ConsentState | null;
|
|
9
|
-
set(state?: ConsentState): void;
|
|
10
|
-
};
|
|
11
|
-
device: {
|
|
12
|
-
getDeviceId(): string | null;
|
|
13
|
-
getFpHash(): string | null;
|
|
14
|
-
};
|
|
15
|
-
identity: {
|
|
16
|
-
get(): IdentifyParams | null;
|
|
17
|
-
set(params: IdentifyParams): Promise<void>;
|
|
18
|
-
};
|
|
19
|
-
session: {
|
|
20
|
-
getId(): string | null;
|
|
21
|
-
getWindowId(): string | null;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
7
|
interface InterfereProviderProps extends PropsWithChildren {
|
|
25
8
|
consent?: ConsentState | undefined;
|
|
26
9
|
/**
|
|
@@ -40,8 +23,16 @@ interface InterfereProviderProps extends PropsWithChildren {
|
|
|
40
23
|
* @default true
|
|
41
24
|
*/
|
|
42
25
|
errorBoundary?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* The active kernel from `init()`. Pass `null` (or omit on the server)
|
|
28
|
+
* when the SDK isn't initialized — useful during SSR/SSG where `init()`
|
|
29
|
+
* runs client-side only. The provider mounts with null-safe accessors
|
|
30
|
+
* and switches to the real kernel once it's available.
|
|
31
|
+
*/
|
|
32
|
+
kernel: Kernel | null;
|
|
43
33
|
}
|
|
44
34
|
declare function InterfereProvider({
|
|
35
|
+
kernel,
|
|
45
36
|
children,
|
|
46
37
|
consent,
|
|
47
38
|
errorBoundary
|
|
@@ -49,4 +40,4 @@ declare function InterfereProvider({
|
|
|
49
40
|
declare function useInterfere(): InterfereContextValue;
|
|
50
41
|
declare function useSession(): string | null;
|
|
51
42
|
//#endregion
|
|
52
|
-
export { InterfereProvider, useInterfere, useSession };
|
|
43
|
+
export { InterfereContext, type InterfereContextValue, InterfereProvider, useInterfere, useSession };
|
package/dist/provider.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.mts","names":[],"sources":["../src/provider.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"provider.d.mts","names":[],"sources":["../src/provider.tsx"],"mappings":";;;;;;UAmBU,sBAAA,SAA+B,iBAAA;EACvC,OAAA,GAAU,YAAA;EADF;;;;;;;;;;;;;;;;EAkBR,aAAA;EAU+B;;;;;;EAH/B,MAAA,EAAQ,MAAA;AAAA;AAAA,iBAGM,iBAAA,CAAA;EACd,MAAA;EACA,QAAA;EACA,OAAA;EACA;AAAA,GACC,sBAAA,GAAyB,SAAA;AAAA,iBAwBZ,YAAA,CAAA,GAAgB,qBAAA;AAAA,iBAQhB,UAAA,CAAA"}
|
package/dist/provider.mjs
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import { consent, syncConsent } from "./internal/client.mjs";
|
|
2
|
+
import { InterfereContext, NULL_CONTEXT_VALUE } from "./internal/react-context.mjs";
|
|
4
3
|
import { CaptureBoundary } from "./internal/capture-boundary.mjs";
|
|
5
|
-
import {
|
|
4
|
+
import { useContext, useEffect } from "react";
|
|
6
5
|
import { jsx } from "react/jsx-runtime";
|
|
7
6
|
//#region src/provider.tsx
|
|
8
|
-
|
|
9
|
-
function InterfereProvider({ children, consent: consent$1, errorBoundary = true }) {
|
|
7
|
+
function InterfereProvider({ kernel, children, consent, errorBoundary = true }) {
|
|
10
8
|
useEffect(() => {
|
|
11
|
-
syncConsent(consent
|
|
12
|
-
}, [consent
|
|
9
|
+
kernel?.syncConsent(consent);
|
|
10
|
+
}, [kernel, consent]);
|
|
13
11
|
return /* @__PURE__ */ jsx(InterfereContext, {
|
|
14
|
-
value: {
|
|
15
|
-
consent,
|
|
16
|
-
device,
|
|
17
|
-
identity,
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
value: kernel ? {
|
|
13
|
+
consent: kernel.consent,
|
|
14
|
+
device: kernel.device,
|
|
15
|
+
identity: kernel.identity,
|
|
16
|
+
kernel,
|
|
17
|
+
session: kernel.session
|
|
18
|
+
} : NULL_CONTEXT_VALUE,
|
|
20
19
|
children: errorBoundary ? /* @__PURE__ */ jsx(CaptureBoundary, { children }) : children
|
|
21
20
|
});
|
|
22
21
|
}
|
|
@@ -29,4 +28,4 @@ function useSession() {
|
|
|
29
28
|
return useInterfere().session.getId();
|
|
30
29
|
}
|
|
31
30
|
//#endregion
|
|
32
|
-
export { InterfereProvider, useInterfere, useSession };
|
|
31
|
+
export { InterfereContext, InterfereProvider, useInterfere, useSession };
|
package/dist/provider.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.mjs","names":[
|
|
1
|
+
{"version":3,"file":"provider.mjs","names":[],"sources":["../src/provider.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ConsentState } from \"@interfere/types/sdk/plugins/manifest\";\n\nimport {\n type PropsWithChildren,\n type ReactNode,\n useContext,\n useEffect,\n} from \"react\";\n\nimport { CaptureBoundary } from \"./internal/capture-boundary.js\";\nimport type { Kernel } from \"./internal/kernel.js\";\nimport type { InterfereContextValue } from \"./internal/react-context.js\";\nimport {\n InterfereContext,\n NULL_CONTEXT_VALUE,\n} from \"./internal/react-context.js\";\n\ninterface InterfereProviderProps extends PropsWithChildren {\n consent?: ConsentState | undefined;\n /**\n * Auto-install an internal capture boundary around `children` so\n * render-phase React errors are reported even when the host app has no\n * `ErrorBoundary` / `error.tsx` of its own.\n *\n * The internal boundary is transparent: it captures the error and then\n * re-throws so upstream boundaries (the customer's own, Next.js's\n * `error.tsx` / `global-error.tsx`, or React's default unmount) keep full\n * control of what the user sees. Safe to leave enabled.\n *\n * Pass `false` only if you explicitly don't want automatic capture of\n * render-phase errors — for example, if you've already wired\n * {@link reactErrorHandler} into `createRoot()`.\n *\n * @default true\n */\n errorBoundary?: boolean;\n /**\n * The active kernel from `init()`. Pass `null` (or omit on the server)\n * when the SDK isn't initialized — useful during SSR/SSG where `init()`\n * runs client-side only. The provider mounts with null-safe accessors\n * and switches to the real kernel once it's available.\n */\n kernel: Kernel | null;\n}\n\nexport function InterfereProvider({\n kernel,\n children,\n consent,\n errorBoundary = true,\n}: InterfereProviderProps): ReactNode {\n useEffect(() => {\n kernel?.syncConsent(consent);\n }, [kernel, consent]);\n\n const value: InterfereContextValue = kernel\n ? {\n consent: kernel.consent,\n device: kernel.device,\n identity: kernel.identity,\n kernel,\n session: kernel.session,\n }\n : NULL_CONTEXT_VALUE;\n\n const body = errorBoundary ? (\n <CaptureBoundary>{children}</CaptureBoundary>\n ) : (\n children\n );\n\n return <InterfereContext value={value}>{body}</InterfereContext>;\n}\n\nexport function useInterfere(): InterfereContextValue {\n const ctx = useContext(InterfereContext);\n if (!ctx) {\n throw new Error(\"useInterfere must be used within <InterfereProvider>\");\n }\n return ctx;\n}\n\nexport function useSession(): string | null {\n return useInterfere().session.getId();\n}\n\nexport type { InterfereContextValue } from \"./internal/react-context.js\";\n// biome-ignore lint/performance/noBarrelFile: Re-export the context handle alongside the provider so consumers (custom error boundaries, the `@interfere/next` wrapper) can import everything from one entry.\nexport { InterfereContext } from \"./internal/react-context.js\";\n"],"mappings":";;;;;;AA+CA,SAAgB,kBAAkB,EAChC,QACA,UACA,SACA,gBAAgB,QACoB;CACpC,gBAAgB;EACd,QAAQ,YAAY,QAAQ;IAC3B,CAAC,QAAQ,QAAQ,CAAC;CAkBrB,OAAO,oBAAC,kBAAD;EAAkB,OAhBY,SACjC;GACE,SAAS,OAAO;GAChB,QAAQ,OAAO;GACf,UAAU,OAAO;GACjB;GACA,SAAS,OAAO;GACjB,GACD;YAES,gBACX,oBAAC,iBAAD,EAAkB,UAA2B,CAAA,GAE7C;EAG8D,CAAA;;AAGlE,SAAgB,eAAsC;CACpD,MAAM,MAAM,WAAW,iBAAiB;CACxC,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,uDAAuD;CAEzE,OAAO;;AAGT,SAAgB,aAA4B;CAC1C,OAAO,cAAc,CAAC,QAAQ,OAAO"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Kernel } from "./internal/kernel.mjs";
|
|
2
|
+
|
|
1
3
|
//#region src/react-error-handler.d.ts
|
|
2
4
|
/**
|
|
3
5
|
* Matches the shape React 19 passes to `onCaughtError`, `onUncaughtError`,
|
|
@@ -30,7 +32,18 @@ interface ReactErrorHandlerCallbacks {
|
|
|
30
32
|
*/
|
|
31
33
|
onUncaughtError?: RootErrorCallback;
|
|
32
34
|
}
|
|
33
|
-
interface ReactErrorHandlerOptions extends ReactErrorHandlerCallbacks {
|
|
35
|
+
interface ReactErrorHandlerOptions extends ReactErrorHandlerCallbacks {
|
|
36
|
+
/**
|
|
37
|
+
* The kernel to capture errors against. Pass either the kernel directly
|
|
38
|
+
* or a getter — framework wrappers expose `getKernelOrNull()` for the
|
|
39
|
+
* latter pattern, which lets you wire the handler before `init()`
|
|
40
|
+
* resolves. Pass `getKernelOrNull` (not `getKernel`) — the throwing
|
|
41
|
+
* variant breaks this contract since the handler runs in error-recovery
|
|
42
|
+
* paths where rethrowing isn't useful. Capture is silently skipped when
|
|
43
|
+
* no kernel is available.
|
|
44
|
+
*/
|
|
45
|
+
kernel?: Kernel | (() => Kernel | null) | null;
|
|
46
|
+
}
|
|
34
47
|
interface ReactRootErrorHandlers {
|
|
35
48
|
onCaughtError: RootErrorCallback;
|
|
36
49
|
onRecoverableError: RootErrorCallback;
|
|
@@ -42,11 +55,13 @@ interface ReactRootErrorHandlers {
|
|
|
42
55
|
*
|
|
43
56
|
* ```ts
|
|
44
57
|
* import { createRoot } from "react-dom/client";
|
|
58
|
+
* import { getKernelOrNull } from "@interfere/next";
|
|
45
59
|
* import { reactErrorHandler } from "@interfere/react/react-error-handler";
|
|
46
60
|
*
|
|
47
|
-
* createRoot(
|
|
48
|
-
*
|
|
49
|
-
* )
|
|
61
|
+
* createRoot(
|
|
62
|
+
* document.getElementById("root")!,
|
|
63
|
+
* reactErrorHandler({ kernel: getKernelOrNull })
|
|
64
|
+
* ).render(<App />);
|
|
50
65
|
* ```
|
|
51
66
|
*
|
|
52
67
|
* Pass your own callbacks to observe errors without replacing the capture
|
|
@@ -54,10 +69,11 @@ interface ReactRootErrorHandlers {
|
|
|
54
69
|
*
|
|
55
70
|
* ```ts
|
|
56
71
|
* reactErrorHandler({
|
|
72
|
+
* kernel: getKernelOrNull,
|
|
57
73
|
* onUncaughtError: (err) => myLogger.fatal(err),
|
|
58
74
|
* });
|
|
59
75
|
* ```
|
|
60
76
|
*/
|
|
61
|
-
declare function reactErrorHandler(
|
|
77
|
+
declare function reactErrorHandler(options?: ReactErrorHandlerOptions): ReactRootErrorHandlers;
|
|
62
78
|
//#endregion
|
|
63
79
|
export { ReactErrorHandlerCallbacks, ReactErrorHandlerOptions, reactErrorHandler };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-error-handler.d.mts","names":[],"sources":["../src/react-error-handler.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"react-error-handler.d.mts","names":[],"sources":["../src/react-error-handler.ts"],"mappings":";;;;;AAKmD;;;;;UASzC,kBAAA;EACR,cAAA;AAAA;AAAA,KAGG,iBAAA,IAAqB,KAAA,WAAgB,IAAA,EAAM,kBAAA;AAAA,UAE/B,0BAAA;EAF+B;;;;AAEhD;EAME,aAAA,GAAgB,iBAAA;;;;;;EAMhB,kBAAA,GAAqB,iBAAA;EANrB;;;;;EAYA,eAAA,GAAkB,iBAAA;AAAA;AAAA,UAGH,wBAAA,SAAiC,0BAAA;EAAjC;;;;;;;;;EAUf,MAAA,GAAS,MAAA,UAAgB,MAAA;AAAA;AAAA,UAGjB,sBAAA;EACR,aAAA,EAAe,iBAAA;EACf,kBAAA,EAAoB,iBAAA;EACpB,eAAA,EAAiB,iBAAA;AAAA;;;;;;;;;;;;;;;AAqCnB;;;;;;;;;;;iBAAgB,iBAAA,CACd,OAAA,GAAS,wBAAA,GACR,sBAAA"}
|
|
@@ -2,17 +2,23 @@
|
|
|
2
2
|
import { captureReactError } from "./internal/capture.mjs";
|
|
3
3
|
import { MECHANISM_TYPE } from "@interfere/types/sdk/errors";
|
|
4
4
|
//#region src/react-error-handler.ts
|
|
5
|
+
function resolveKernel(source) {
|
|
6
|
+
if (!source) return null;
|
|
7
|
+
return typeof source === "function" ? source() : source;
|
|
8
|
+
}
|
|
5
9
|
/**
|
|
6
10
|
* Creates the three error callbacks React 19's `createRoot()` / `hydrateRoot()`
|
|
7
11
|
* accept, wired into Interfere's capture pipeline.
|
|
8
12
|
*
|
|
9
13
|
* ```ts
|
|
10
14
|
* import { createRoot } from "react-dom/client";
|
|
15
|
+
* import { getKernelOrNull } from "@interfere/next";
|
|
11
16
|
* import { reactErrorHandler } from "@interfere/react/react-error-handler";
|
|
12
17
|
*
|
|
13
|
-
* createRoot(
|
|
14
|
-
*
|
|
15
|
-
* )
|
|
18
|
+
* createRoot(
|
|
19
|
+
* document.getElementById("root")!,
|
|
20
|
+
* reactErrorHandler({ kernel: getKernelOrNull })
|
|
21
|
+
* ).render(<App />);
|
|
16
22
|
* ```
|
|
17
23
|
*
|
|
18
24
|
* Pass your own callbacks to observe errors without replacing the capture
|
|
@@ -20,28 +26,30 @@ import { MECHANISM_TYPE } from "@interfere/types/sdk/errors";
|
|
|
20
26
|
*
|
|
21
27
|
* ```ts
|
|
22
28
|
* reactErrorHandler({
|
|
29
|
+
* kernel: getKernelOrNull,
|
|
23
30
|
* onUncaughtError: (err) => myLogger.fatal(err),
|
|
24
31
|
* });
|
|
25
32
|
* ```
|
|
26
33
|
*/
|
|
27
|
-
function reactErrorHandler(
|
|
34
|
+
function reactErrorHandler(options = {}) {
|
|
35
|
+
const { kernel: kernelSource, ...callbacks } = options;
|
|
28
36
|
return {
|
|
29
37
|
onCaughtError(error, info) {
|
|
30
|
-
if (error instanceof Error) captureReactError(error, info.componentStack, {
|
|
38
|
+
if (error instanceof Error) captureReactError(resolveKernel(kernelSource), error, info.componentStack, {
|
|
31
39
|
type: MECHANISM_TYPE.react.caughtError,
|
|
32
40
|
handled: true
|
|
33
41
|
});
|
|
34
42
|
callbacks.onCaughtError?.(error, info);
|
|
35
43
|
},
|
|
36
44
|
onUncaughtError(error, info) {
|
|
37
|
-
if (error instanceof Error) captureReactError(error, info.componentStack, {
|
|
45
|
+
if (error instanceof Error) captureReactError(resolveKernel(kernelSource), error, info.componentStack, {
|
|
38
46
|
type: MECHANISM_TYPE.react.uncaughtError,
|
|
39
47
|
handled: false
|
|
40
48
|
});
|
|
41
49
|
callbacks.onUncaughtError?.(error, info);
|
|
42
50
|
},
|
|
43
51
|
onRecoverableError(error, info) {
|
|
44
|
-
if (error instanceof Error) captureReactError(error, info.componentStack, {
|
|
52
|
+
if (error instanceof Error) captureReactError(resolveKernel(kernelSource), error, info.componentStack, {
|
|
45
53
|
type: MECHANISM_TYPE.react.recoverableError,
|
|
46
54
|
handled: true,
|
|
47
55
|
synthetic: true
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-error-handler.mjs","names":[],"sources":["../src/react-error-handler.ts"],"sourcesContent":["\"use client\";\n\nimport { MECHANISM_TYPE } from \"@interfere/types/sdk/errors\";\n\nimport { captureReactError } from \"./internal/capture.js\";\n\n/**\n * Matches the shape React 19 passes to `onCaughtError`, `onUncaughtError`,\n * and `onRecoverableError` on `createRoot()` / `hydrateRoot()`.\n *\n * We intentionally keep this permissive — React has iterated on the exact\n * fields of `ErrorInfo` across 19.x minors and we only need `componentStack`.\n */\ninterface ReactRootErrorInfo {\n componentStack?: string | null | undefined;\n}\n\ntype RootErrorCallback = (error: unknown, info: ReactRootErrorInfo) => void;\n\nexport interface ReactErrorHandlerCallbacks {\n /**\n * Called when a React error boundary caught and handled an error. Interfere\n * reports it with `mechanism.handled: true`. Your callback runs after the\n * capture.\n */\n onCaughtError?: RootErrorCallback;\n /**\n * Called when concurrent React auto-recovered from an error by retrying\n * the render. Reported with `mechanism.synthetic: true` so the agent can\n * deprioritize — these often aren't user-facing bugs.\n */\n onRecoverableError?: RootErrorCallback;\n /**\n * Called when a render-phase error propagated past every boundary and\n * React unmounted (or fell back to its default UI). Reported with\n * `mechanism.handled: false` — this is the \"real\" uncaught case.\n */\n onUncaughtError?: RootErrorCallback;\n}\n\nexport interface ReactErrorHandlerOptions extends ReactErrorHandlerCallbacks {}\n\ninterface ReactRootErrorHandlers {\n onCaughtError: RootErrorCallback;\n onRecoverableError: RootErrorCallback;\n onUncaughtError: RootErrorCallback;\n}\n\n/**\n * Creates the three error callbacks React 19's `createRoot()` / `hydrateRoot()`\n * accept, wired into Interfere's capture pipeline.\n *\n * ```ts\n * import { createRoot } from \"react-dom/client\";\n * import { reactErrorHandler } from \"@interfere/react/react-error-handler\";\n *\n * createRoot(document.getElementById(\"root\")
|
|
1
|
+
{"version":3,"file":"react-error-handler.mjs","names":[],"sources":["../src/react-error-handler.ts"],"sourcesContent":["\"use client\";\n\nimport { MECHANISM_TYPE } from \"@interfere/types/sdk/errors\";\n\nimport { captureReactError } from \"./internal/capture.js\";\nimport type { Kernel } from \"./internal/kernel.js\";\n\n/**\n * Matches the shape React 19 passes to `onCaughtError`, `onUncaughtError`,\n * and `onRecoverableError` on `createRoot()` / `hydrateRoot()`.\n *\n * We intentionally keep this permissive — React has iterated on the exact\n * fields of `ErrorInfo` across 19.x minors and we only need `componentStack`.\n */\ninterface ReactRootErrorInfo {\n componentStack?: string | null | undefined;\n}\n\ntype RootErrorCallback = (error: unknown, info: ReactRootErrorInfo) => void;\n\nexport interface ReactErrorHandlerCallbacks {\n /**\n * Called when a React error boundary caught and handled an error. Interfere\n * reports it with `mechanism.handled: true`. Your callback runs after the\n * capture.\n */\n onCaughtError?: RootErrorCallback;\n /**\n * Called when concurrent React auto-recovered from an error by retrying\n * the render. Reported with `mechanism.synthetic: true` so the agent can\n * deprioritize — these often aren't user-facing bugs.\n */\n onRecoverableError?: RootErrorCallback;\n /**\n * Called when a render-phase error propagated past every boundary and\n * React unmounted (or fell back to its default UI). Reported with\n * `mechanism.handled: false` — this is the \"real\" uncaught case.\n */\n onUncaughtError?: RootErrorCallback;\n}\n\nexport interface ReactErrorHandlerOptions extends ReactErrorHandlerCallbacks {\n /**\n * The kernel to capture errors against. Pass either the kernel directly\n * or a getter — framework wrappers expose `getKernelOrNull()` for the\n * latter pattern, which lets you wire the handler before `init()`\n * resolves. Pass `getKernelOrNull` (not `getKernel`) — the throwing\n * variant breaks this contract since the handler runs in error-recovery\n * paths where rethrowing isn't useful. Capture is silently skipped when\n * no kernel is available.\n */\n kernel?: Kernel | (() => Kernel | null) | null;\n}\n\ninterface ReactRootErrorHandlers {\n onCaughtError: RootErrorCallback;\n onRecoverableError: RootErrorCallback;\n onUncaughtError: RootErrorCallback;\n}\n\nfunction resolveKernel(\n source: ReactErrorHandlerOptions[\"kernel\"]\n): Kernel | null {\n if (!source) {\n return null;\n }\n return typeof source === \"function\" ? source() : source;\n}\n\n/**\n * Creates the three error callbacks React 19's `createRoot()` / `hydrateRoot()`\n * accept, wired into Interfere's capture pipeline.\n *\n * ```ts\n * import { createRoot } from \"react-dom/client\";\n * import { getKernelOrNull } from \"@interfere/next\";\n * import { reactErrorHandler } from \"@interfere/react/react-error-handler\";\n *\n * createRoot(\n * document.getElementById(\"root\")!,\n * reactErrorHandler({ kernel: getKernelOrNull })\n * ).render(<App />);\n * ```\n *\n * Pass your own callbacks to observe errors without replacing the capture\n * behaviour — user callbacks run after Interfere has captured:\n *\n * ```ts\n * reactErrorHandler({\n * kernel: getKernelOrNull,\n * onUncaughtError: (err) => myLogger.fatal(err),\n * });\n * ```\n */\nexport function reactErrorHandler(\n options: ReactErrorHandlerOptions = {}\n): ReactRootErrorHandlers {\n const { kernel: kernelSource, ...callbacks } = options;\n return {\n onCaughtError(error, info) {\n if (error instanceof Error) {\n captureReactError(\n resolveKernel(kernelSource),\n error,\n info.componentStack,\n {\n type: MECHANISM_TYPE.react.caughtError,\n handled: true,\n }\n );\n }\n callbacks.onCaughtError?.(error, info);\n },\n onUncaughtError(error, info) {\n if (error instanceof Error) {\n captureReactError(\n resolveKernel(kernelSource),\n error,\n info.componentStack,\n {\n type: MECHANISM_TYPE.react.uncaughtError,\n handled: false,\n }\n );\n }\n callbacks.onUncaughtError?.(error, info);\n },\n onRecoverableError(error, info) {\n if (error instanceof Error) {\n captureReactError(\n resolveKernel(kernelSource),\n error,\n info.componentStack,\n {\n type: MECHANISM_TYPE.react.recoverableError,\n handled: true,\n synthetic: true,\n }\n );\n }\n callbacks.onRecoverableError?.(error, info);\n },\n };\n}\n"],"mappings":";;;;AA4DA,SAAS,cACP,QACe;CACf,IAAI,CAAC,QACH,OAAO;CAET,OAAO,OAAO,WAAW,aAAa,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BnD,SAAgB,kBACd,UAAoC,EAAE,EACd;CACxB,MAAM,EAAE,QAAQ,cAAc,GAAG,cAAc;CAC/C,OAAO;EACL,cAAc,OAAO,MAAM;GACzB,IAAI,iBAAiB,OACnB,kBACE,cAAc,aAAa,EAC3B,OACA,KAAK,gBACL;IACE,MAAM,eAAe,MAAM;IAC3B,SAAS;IACV,CACF;GAEH,UAAU,gBAAgB,OAAO,KAAK;;EAExC,gBAAgB,OAAO,MAAM;GAC3B,IAAI,iBAAiB,OACnB,kBACE,cAAc,aAAa,EAC3B,OACA,KAAK,gBACL;IACE,MAAM,eAAe,MAAM;IAC3B,SAAS;IACV,CACF;GAEH,UAAU,kBAAkB,OAAO,KAAK;;EAE1C,mBAAmB,OAAO,MAAM;GAC9B,IAAI,iBAAiB,OACnB,kBACE,cAAc,aAAa,EAC3B,OACA,KAAK,gBACL;IACE,MAAM,eAAe,MAAM;IAC3B,SAAS;IACT,WAAW;IACZ,CACF;GAEH,UAAU,qBAAqB,OAAO,KAAK;;EAE9C"}
|
package/dist/sw.d.mts
ADDED
package/dist/sw.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// AUTOGENERATED by scripts/build-sw.ts — do not edit.
|
|
2
|
+
export const SW_SCRIPT = "(function(){try{self[`workbox:core:7.4.0`]&&_()}catch{}let e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n};var t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}};let n=(e,t)=>t.some(t=>e instanceof t),r,i;function a(){return r||=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction]}function o(){return i||=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey]}let s=new WeakMap,c=new WeakMap,l=new WeakMap,u=new WeakMap,d=new WeakMap;function f(e){let t=new Promise((t,n)=>{let r=()=>{e.removeEventListener(`success`,i),e.removeEventListener(`error`,a)},i=()=>{t(y(e.result)),r()},a=()=>{n(e.error),r()};e.addEventListener(`success`,i),e.addEventListener(`error`,a)});return t.then(t=>{t instanceof IDBCursor&&s.set(t,e)}).catch(()=>{}),d.set(t,e),t}function p(e){if(c.has(e))return;let t=new Promise((t,n)=>{let r=()=>{e.removeEventListener(`complete`,i),e.removeEventListener(`error`,a),e.removeEventListener(`abort`,a)},i=()=>{t(),r()},a=()=>{n(e.error||new DOMException(`AbortError`,`AbortError`)),r()};e.addEventListener(`complete`,i),e.addEventListener(`error`,a),e.addEventListener(`abort`,a)});c.set(e,t)}let m={get(e,t,n){if(e instanceof IDBTransaction){if(t===`done`)return c.get(e);if(t===`objectStoreNames`)return e.objectStoreNames||l.get(e);if(t===`store`)return n.objectStoreNames[1]?void 0:n.objectStore(n.objectStoreNames[0])}return y(e[t])},set(e,t,n){return e[t]=n,!0},has(e,t){return e instanceof IDBTransaction&&(t===`done`||t===`store`)?!0:t in e}};function h(e){m=e(m)}function g(e){return e===IDBDatabase.prototype.transaction&&!(`objectStoreNames`in IDBTransaction.prototype)?function(t,...n){let r=e.call(b(this),t,...n);return l.set(r,t.sort?t.sort():[t]),y(r)}:o().includes(e)?function(...t){return e.apply(b(this),t),y(s.get(this))}:function(...t){return y(e.apply(b(this),t))}}function v(e){return typeof e==`function`?g(e):(e instanceof IDBTransaction&&p(e),n(e,a())?new Proxy(e,m):e)}function y(e){if(e instanceof IDBRequest)return f(e);if(u.has(e))return u.get(e);let t=v(e);return t!==e&&(u.set(e,t),d.set(t,e)),t}let b=e=>d.get(e);function x(e,t,{blocked:n,upgrade:r,blocking:i,terminated:a}={}){let o=indexedDB.open(e,t),s=y(o);return r&&o.addEventListener(`upgradeneeded`,e=>{r(y(o.result),e.oldVersion,e.newVersion,y(o.transaction),e)}),n&&o.addEventListener(`blocked`,e=>n(e.oldVersion,e.newVersion,e)),s.then(e=>{a&&e.addEventListener(`close`,()=>a()),i&&e.addEventListener(`versionchange`,e=>i(e.oldVersion,e.newVersion,e))}).catch(()=>{}),s}let S=[`get`,`getKey`,`getAll`,`getAllKeys`,`count`],C=[`put`,`add`,`delete`,`clear`],w=new Map;function T(e,t){if(!(e instanceof IDBDatabase&&!(t in e)&&typeof t==`string`))return;if(w.get(t))return w.get(t);let n=t.replace(/FromIndex$/,``),r=t!==n,i=C.includes(n);if(!(n in(r?IDBIndex:IDBObjectStore).prototype)||!(i||S.includes(n)))return;let a=async function(e,...t){let a=this.transaction(e,i?`readwrite`:`readonly`),o=a.store;return r&&(o=o.index(t.shift())),(await Promise.all([o[n](...t),i&&a.done]))[0]};return w.set(t,a),a}h(e=>({...e,get:(t,n,r)=>T(t,n)||e.get(t,n,r),has:(t,n)=>!!T(t,n)||e.has(t,n)}));try{self[`workbox:background-sync:7.4.0`]&&_()}catch{}let E=`requests`,D=`queueName`;var O=class{constructor(){this._db=null}async addEntry(e){let t=(await this.getDb()).transaction(E,`readwrite`,{durability:`relaxed`});await t.store.add(e),await t.done}async getFirstEntryId(){return(await(await this.getDb()).transaction(E).store.openCursor())?.value.id}async getAllEntriesByQueueName(e){return await(await this.getDb()).getAllFromIndex(E,D,IDBKeyRange.only(e))||[]}async getEntryCountByQueueName(e){return(await this.getDb()).countFromIndex(E,D,IDBKeyRange.only(e))}async deleteEntry(e){await(await this.getDb()).delete(E,e)}async getFirstEntryByQueueName(e){return await this.getEndEntryFromIndex(IDBKeyRange.only(e),`next`)}async getLastEntryByQueueName(e){return await this.getEndEntryFromIndex(IDBKeyRange.only(e),`prev`)}async getEndEntryFromIndex(e,t){return(await(await this.getDb()).transaction(E).store.index(D).openCursor(e,t))?.value}async getDb(){return this._db||=await x(`workbox-background-sync`,3,{upgrade:this._upgradeDb}),this._db}_upgradeDb(e,t){t>0&&t<3&&e.objectStoreNames.contains(E)&&e.deleteObjectStore(E),e.createObjectStore(E,{autoIncrement:!0,keyPath:`id`}).createIndex(D,D,{unique:!1})}},k=class{constructor(e){this._queueName=e,this._queueDb=new O}async pushEntry(e){delete e.id,e.queueName=this._queueName,await this._queueDb.addEntry(e)}async unshiftEntry(e){let t=await this._queueDb.getFirstEntryId();t?e.id=t-1:delete e.id,e.queueName=this._queueName,await this._queueDb.addEntry(e)}async popEntry(){return this._removeEntry(await this._queueDb.getLastEntryByQueueName(this._queueName))}async shiftEntry(){return this._removeEntry(await this._queueDb.getFirstEntryByQueueName(this._queueName))}async getAll(){return await this._queueDb.getAllEntriesByQueueName(this._queueName)}async size(){return await this._queueDb.getEntryCountByQueueName(this._queueName)}async deleteEntry(e){await this._queueDb.deleteEntry(e)}async _removeEntry(e){return e&&await this.deleteEntry(e.id),e}};let A=[`method`,`referrer`,`referrerPolicy`,`mode`,`credentials`,`cache`,`redirect`,`integrity`,`keepalive`];var j=class e{static async fromRequest(t){let n={url:t.url,headers:{}};t.method!==`GET`&&(n.body=await t.clone().arrayBuffer());for(let[e,r]of t.headers.entries())n.headers[e]=r;for(let e of A)t[e]!==void 0&&(n[e]=t[e]);return new e(n)}constructor(e){e.mode===`navigate`&&(e.mode=`same-origin`),this._requestData=e}toObject(){let e=Object.assign({},this._requestData);return e.headers=Object.assign({},this._requestData.headers),e.body&&=e.body.slice(0),e}toRequest(){return new Request(this._requestData.url,this._requestData)}clone(){return new e(this.toObject())}};let M=`workbox-background-sync`,N=new Set,P=e=>{let t={request:new j(e.requestData).toRequest(),timestamp:e.timestamp};return e.metadata&&(t.metadata=e.metadata),t};var F=class{constructor(e,{forceSyncFallback:n,onSync:r,maxRetentionTime:i}={}){if(this._syncInProgress=!1,this._requestsAddedDuringSync=!1,N.has(e))throw new t(`duplicate-queue-name`,{name:e});N.add(e),this._name=e,this._onSync=r||this.replayRequests,this._maxRetentionTime=i||10080,this._forceSyncFallback=!!n,this._queueStore=new k(this._name),this._addSyncListener()}get name(){return this._name}async pushRequest(e){await this._addRequest(e,`push`)}async unshiftRequest(e){await this._addRequest(e,`unshift`)}async popRequest(){return this._removeRequest(`pop`)}async shiftRequest(){return this._removeRequest(`shift`)}async getAll(){let e=await this._queueStore.getAll(),t=Date.now(),n=[];for(let r of e){let e=this._maxRetentionTime*60*1e3;t-r.timestamp>e?await this._queueStore.deleteEntry(r.id):n.push(P(r))}return n}async size(){return await this._queueStore.size()}async _addRequest({request:e,metadata:t,timestamp:n=Date.now()},r){let i={requestData:(await j.fromRequest(e.clone())).toObject(),timestamp:n};switch(t&&(i.metadata=t),r){case`push`:await this._queueStore.pushEntry(i);break;case`unshift`:await this._queueStore.unshiftEntry(i);break}this._syncInProgress?this._requestsAddedDuringSync=!0:await this.registerSync()}async _removeRequest(e){let t=Date.now(),n;switch(e){case`pop`:n=await this._queueStore.popEntry();break;case`shift`:n=await this._queueStore.shiftEntry();break}if(n){let r=this._maxRetentionTime*60*1e3;return t-n.timestamp>r?this._removeRequest(e):P(n)}else return}async replayRequests(){let e;for(;e=await this.shiftRequest();)try{await fetch(e.request.clone())}catch{throw await this.unshiftRequest(e),new t(`queue-replay-failed`,{name:this._name})}}async registerSync(){if(`sync`in self.registration&&!this._forceSyncFallback)try{await self.registration.sync.register(`${M}:${this._name}`)}catch{}}_addSyncListener(){`sync`in self.registration&&!this._forceSyncFallback?self.addEventListener(`sync`,e=>{e.tag===`${M}:${this._name}`&&e.waitUntil((async()=>{this._syncInProgress=!0;let t;try{await this._onSync({queue:this})}catch(e){if(e instanceof Error)throw t=e,t}finally{this._requestsAddedDuringSync&&!(t&&!e.lastChance)&&await this.registerSync(),this._syncInProgress=!1,this._requestsAddedDuringSync=!1}})())}):this._onSync({queue:this})}static get _queueNames(){return N}};let I={status:202,statusText:`Queued`};async function L(e){let t=await self.clients.matchAll({includeUncontrolled:!1});for(let n of t)n.postMessage(e)}async function R(e){let t=await e.getAll(),n=0;for(let e of t){let t=e.metadata;typeof t?.bytes==`number`&&(n+=t.bytes)}return n}async function z(e,t){let n=await R(e);for(;n+t>10485760;){let t=await e.shiftRequest();if(!t)break;let r=t.metadata;n-=r?.bytes??0,await L({type:`interfere.sw.dropped`,url:t.request.url,reason:`queue_size_cap`})}}async function B(e,t){let n=t.clone(),r=(await n.clone().arrayBuffer()).byteLength;await z(e,r),await e.pushRequest({request:n,metadata:{bytes:r}}),await L({type:`interfere.sw.queued`,url:t.url})}let V=new F(`interfere-ingest`,{maxRetentionTime:1440,onSync:async({queue:e})=>{let t=await e.shiftRequest();for(;t;){try{let e=await fetch(t.request.clone());if(!e.ok&&(e.status>=500||e.status===429))throw Error(`retry status ${e.status}`);await L({type:`interfere.sw.replayed`,url:t.request.url})}catch(n){throw await e.unshiftRequest(t),n}t=await e.shiftRequest()}}});self.addEventListener(`install`,()=>{self.skipWaiting()}),self.addEventListener(`activate`,e=>{e.waitUntil((async()=>{await self.clients.claim(),V.replayRequests().catch(()=>{})})())}),self.addEventListener(`fetch`,e=>{let{request:t}=e;if(t.method!==`POST`)return;let n;try{n=new URL(t.url)}catch{return}n.pathname.startsWith(`/api/interfere/`)&&e.respondWith((async()=>{try{let e=await fetch(t.clone());return e.ok?e:e.status>=500||e.status===429?(await B(V,t),new Response(null,I)):e}catch{return await B(V,t),new Response(null,I)}})())})})();";
|