@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,62 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Single point of `console.{debug,log,info,warn,error}` patching. The
|
|
4
|
-
* errors plugin and the logs plugin both want to observe console calls;
|
|
5
|
-
* before this consolidation, each patched independently and the
|
|
6
|
-
* resulting wrap-order coupling made teardown leaky.
|
|
7
|
-
*
|
|
8
|
-
* Now: a single set of patched methods is installed on first
|
|
9
|
-
* subscription, fans out to every subscriber, and is uninstalled on the
|
|
10
|
-
* last unsubscription. Handlers never wrap each other; they observe.
|
|
11
|
-
*/
|
|
12
|
-
const CONSOLE_LEVELS = [
|
|
13
|
-
"debug",
|
|
14
|
-
"log",
|
|
15
|
-
"info",
|
|
16
|
-
"warn",
|
|
17
|
-
"error"
|
|
18
|
-
];
|
|
19
|
-
const handlers = /* @__PURE__ */ new Set();
|
|
20
|
-
const originals = {};
|
|
21
|
-
let installed = false;
|
|
22
|
-
function install() {
|
|
23
|
-
if (installed) return;
|
|
24
|
-
installed = true;
|
|
25
|
-
const target = globalThis.console;
|
|
26
|
-
for (const level of CONSOLE_LEVELS) {
|
|
27
|
-
const orig = target[level];
|
|
28
|
-
if (typeof orig !== "function") continue;
|
|
29
|
-
originals[level] = orig;
|
|
30
|
-
target[level] = (...args) => {
|
|
31
|
-
orig.apply(globalThis.console, args);
|
|
32
|
-
for (const handler of handlers) handler(level, args);
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function uninstall() {
|
|
37
|
-
if (!installed) return;
|
|
38
|
-
installed = false;
|
|
39
|
-
const target = globalThis.console;
|
|
40
|
-
for (const level of CONSOLE_LEVELS) {
|
|
41
|
-
const orig = originals[level];
|
|
42
|
-
if (orig) {
|
|
43
|
-
target[level] = orig;
|
|
44
|
-
delete originals[level];
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function onConsoleCall(handler) {
|
|
49
|
-
install();
|
|
50
|
-
handlers.add(handler);
|
|
51
|
-
return () => {
|
|
52
|
-
handlers.delete(handler);
|
|
53
|
-
if (handlers.size === 0) uninstall();
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
/** Test hook — drops every handler and restores originals. */
|
|
57
|
-
function _resetConsolePatchForTests() {
|
|
58
|
-
handlers.clear();
|
|
59
|
-
uninstall();
|
|
60
|
-
}
|
|
61
|
-
//#endregion
|
|
62
|
-
export { CONSOLE_LEVELS, _resetConsolePatchForTests, onConsoleCall };
|
|
1
|
+
const CONSOLE_LEVELS=[`debug`,`log`,`info`,`warn`,`error`],handlers=new Set,originals={};let installed=!1;function install(){if(installed)return;installed=!0;let target=globalThis.console;for(let level of CONSOLE_LEVELS){let orig=target[level];typeof orig==`function`&&(originals[level]=orig,target[level]=(...args)=>{orig.apply(globalThis.console,args);for(let handler of handlers)handler(level,args)})}}function uninstall(){if(!installed)return;installed=!1;let target=globalThis.console;for(let level of CONSOLE_LEVELS){let orig=originals[level];orig&&(target[level]=orig,delete originals[level])}}function onConsoleCall(handler){return install(),handlers.add(handler),()=>{handlers.delete(handler),handlers.size===0&&uninstall()}}function _resetConsolePatchForTests(){handlers.clear(),uninstall()}export{CONSOLE_LEVELS,_resetConsolePatchForTests,onConsoleCall};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"console-patch.mjs","names":[],"sources":["../../src/internal/console-patch.ts"],"sourcesContent":["/**\n * Single point of `console.{debug,log,info,warn,error}` patching. The\n * errors plugin and the logs plugin both want to observe console calls;\n * before this consolidation, each patched independently and the\n * resulting wrap-order coupling made teardown leaky.\n *\n * Now: a single set of patched methods is installed on first\n * subscription, fans out to every subscriber, and is uninstalled on the\n * last unsubscription. Handlers never wrap each other; they observe.\n */\nexport const CONSOLE_LEVELS = [\n \"debug\",\n \"log\",\n \"info\",\n \"warn\",\n \"error\",\n] as const;\n\nexport type ConsoleLevel = (typeof CONSOLE_LEVELS)[number];\n\nexport type ConsoleHandler = (level: ConsoleLevel, args: unknown[]) => void;\n\nconst handlers = new Set<ConsoleHandler>();\nconst originals: Partial<Record<ConsoleLevel, (...args: unknown[]) => void>> =\n {};\nlet installed = false;\n\nfunction install(): void {\n if (installed) {\n return;\n }\n installed = true;\n const target = globalThis.console as unknown as Record<\n ConsoleLevel,\n (...args: unknown[]) => void\n >;\n for (const level of CONSOLE_LEVELS) {\n const orig = target[level];\n if (typeof orig !== \"function\") {\n continue;\n }\n originals[level] = orig;\n target[level] = (...args: unknown[]) => {\n orig.apply(globalThis.console, args);\n for (const handler of handlers) {\n handler(level, args);\n }\n };\n }\n}\n\nfunction uninstall(): void {\n if (!installed) {\n return;\n }\n installed = false;\n const target = globalThis.console as unknown as Record<\n ConsoleLevel,\n (...args: unknown[]) => void\n >;\n for (const level of CONSOLE_LEVELS) {\n const orig = originals[level];\n if (orig) {\n target[level] = orig;\n delete originals[level];\n }\n }\n}\n\nexport function onConsoleCall(handler: ConsoleHandler): () => void {\n install();\n handlers.add(handler);\n return () => {\n handlers.delete(handler);\n if (handlers.size === 0) {\n uninstall();\n }\n };\n}\n\n/** Test hook — drops every handler and restores originals. */\nexport function _resetConsolePatchForTests(): void {\n handlers.clear();\n uninstall();\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"console-patch.mjs","names":[],"sources":["../../src/internal/console-patch.ts"],"sourcesContent":["/**\n * Single point of `console.{debug,log,info,warn,error}` patching. The\n * errors plugin and the logs plugin both want to observe console calls;\n * before this consolidation, each patched independently and the\n * resulting wrap-order coupling made teardown leaky.\n *\n * Now: a single set of patched methods is installed on first\n * subscription, fans out to every subscriber, and is uninstalled on the\n * last unsubscription. Handlers never wrap each other; they observe.\n */\nexport const CONSOLE_LEVELS = [\n \"debug\",\n \"log\",\n \"info\",\n \"warn\",\n \"error\",\n] as const;\n\nexport type ConsoleLevel = (typeof CONSOLE_LEVELS)[number];\n\nexport type ConsoleHandler = (level: ConsoleLevel, args: unknown[]) => void;\n\nconst handlers = new Set<ConsoleHandler>();\nconst originals: Partial<Record<ConsoleLevel, (...args: unknown[]) => void>> =\n {};\nlet installed = false;\n\nfunction install(): void {\n if (installed) {\n return;\n }\n installed = true;\n const target = globalThis.console as unknown as Record<\n ConsoleLevel,\n (...args: unknown[]) => void\n >;\n for (const level of CONSOLE_LEVELS) {\n const orig = target[level];\n if (typeof orig !== \"function\") {\n continue;\n }\n originals[level] = orig;\n target[level] = (...args: unknown[]) => {\n orig.apply(globalThis.console, args);\n for (const handler of handlers) {\n handler(level, args);\n }\n };\n }\n}\n\nfunction uninstall(): void {\n if (!installed) {\n return;\n }\n installed = false;\n const target = globalThis.console as unknown as Record<\n ConsoleLevel,\n (...args: unknown[]) => void\n >;\n for (const level of CONSOLE_LEVELS) {\n const orig = originals[level];\n if (orig) {\n target[level] = orig;\n delete originals[level];\n }\n }\n}\n\nexport function onConsoleCall(handler: ConsoleHandler): () => void {\n install();\n handlers.add(handler);\n return () => {\n handlers.delete(handler);\n if (handlers.size === 0) {\n uninstall();\n }\n };\n}\n\n/** Test hook — drops every handler and restores originals. */\nexport function _resetConsolePatchForTests(): void {\n handlers.clear();\n uninstall();\n}\n"],"mappings":"AAUA,MAAa,eAAiB,CAC5B,QACA,MACA,OACA,OACA,OACF,EAMM,SAAW,IAAI,IACf,UACJ,CAAC,EACH,IAAI,UAAY,GAEhB,SAAS,SAAgB,CACvB,GAAI,UACF,OAEF,UAAY,GACZ,IAAM,OAAS,WAAW,QAI1B,IAAK,IAAM,SAAS,eAAgB,CAClC,IAAM,KAAO,OAAO,OAChB,OAAO,MAAS,aAGpB,UAAU,OAAS,KACnB,OAAO,QAAU,GAAG,OAAoB,CACtC,KAAK,MAAM,WAAW,QAAS,IAAI,EACnC,IAAK,IAAM,WAAW,SACpB,QAAQ,MAAO,IAAI,CAEvB,EACF,CACF,CAEA,SAAS,WAAkB,CACzB,GAAI,CAAC,UACH,OAEF,UAAY,GACZ,IAAM,OAAS,WAAW,QAI1B,IAAK,IAAM,SAAS,eAAgB,CAClC,IAAM,KAAO,UAAU,OACnB,OACF,OAAO,OAAS,KAChB,OAAO,UAAU,OAErB,CACF,CAEA,SAAgB,cAAc,QAAqC,CAGjE,OAFA,QAAQ,EACR,SAAS,IAAI,OAAO,MACP,CACX,SAAS,OAAO,OAAO,EACnB,SAAS,OAAS,GACpB,UAAU,CAEd,CACF,CAGA,SAAgB,4BAAmC,CACjD,SAAS,MAAM,EACf,UAAU,CACZ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actionable.d.mts","names":[],"sources":["../../../src/internal/dom/actionable.ts"],"mappings":";;AAkCA;;;;;
|
|
1
|
+
{"version":3,"file":"actionable.d.mts","names":[],"sources":["../../../src/internal/dom/actionable.ts"],"mappings":";;AAkCA;;;;AAAwC;AAWxC;;;;;iBAXgB,YAAA,CAAa,EAAW,EAAP,OAAO;AAAA,iBAWxB,iBAAA,CAAkB,MAAA,EAAQ,WAAA,UAAqB,OAAO;AAAA,UAcrD,oBAAA;EACf,SAAA;EACA,IAAA;EACA,EAAA;EACA,IAAA;EACA,IAAA;EACA,GAAA;EACA,IAAA;EAAA,CACC,GAAA;AAAA;AAAA,iBAGa,kBAAA,CAAmB,EAAA,EAAI,OAAA,GAAU,oBAAoB"}
|
|
@@ -1,62 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Single source of truth for "what counts as a clickable element" across
|
|
4
|
-
* the SDK. Used by both the `pages` plugin (which captures `ui_event`
|
|
5
|
-
* envelopes) and the OTel `UserInteractionInstrumentation` enrichment
|
|
6
|
-
* hook (which gates span creation + stamps target attrs). Before this
|
|
7
|
-
* was extracted, the two sites diverged and produced inconsistent
|
|
8
|
-
* notions of "actionable."
|
|
9
|
-
*
|
|
10
|
-
* `data-track-click` is the customer escape hatch — any element opted
|
|
11
|
-
* in via that attribute is treated as actionable regardless of tag/role.
|
|
12
|
-
*/
|
|
13
|
-
const ACTIONABLE_TAGS = new Set([
|
|
14
|
-
"a",
|
|
15
|
-
"button",
|
|
16
|
-
"input",
|
|
17
|
-
"select",
|
|
18
|
-
"textarea",
|
|
19
|
-
"summary",
|
|
20
|
-
"label"
|
|
21
|
-
]);
|
|
22
|
-
const ACTIONABLE_ROLES = new Set([
|
|
23
|
-
"button",
|
|
24
|
-
"link",
|
|
25
|
-
"menuitem",
|
|
26
|
-
"tab",
|
|
27
|
-
"checkbox",
|
|
28
|
-
"radio",
|
|
29
|
-
"switch"
|
|
30
|
-
]);
|
|
31
|
-
const TEXT_TRUNCATE = 120;
|
|
32
|
-
function isActionable(el) {
|
|
33
|
-
if (ACTIONABLE_TAGS.has(el.tagName.toLowerCase())) return true;
|
|
34
|
-
const role = el.getAttribute("role");
|
|
35
|
-
if (role && ACTIONABLE_ROLES.has(role)) return true;
|
|
36
|
-
return el.hasAttribute("data-track-click");
|
|
37
|
-
}
|
|
38
|
-
function closestActionable(target) {
|
|
39
|
-
if (!(target instanceof Element)) return null;
|
|
40
|
-
let el = target;
|
|
41
|
-
while (el) {
|
|
42
|
-
if (isActionable(el)) return el;
|
|
43
|
-
el = el.parentElement;
|
|
44
|
-
}
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
function describeActionable(el) {
|
|
48
|
-
const desc = { tag: el.tagName.toLowerCase() };
|
|
49
|
-
if (el.id) desc.id = el.id;
|
|
50
|
-
const role = el.getAttribute("role");
|
|
51
|
-
if (role) desc.role = role;
|
|
52
|
-
const name = el.getAttribute("name");
|
|
53
|
-
if (name) desc.name = name;
|
|
54
|
-
const ariaLabel = el.getAttribute("aria-label");
|
|
55
|
-
if (ariaLabel) desc.ariaLabel = ariaLabel;
|
|
56
|
-
if (el instanceof HTMLAnchorElement) desc.href = el.href;
|
|
57
|
-
const text = el.textContent?.trim().slice(0, TEXT_TRUNCATE);
|
|
58
|
-
if (text) desc.text = text;
|
|
59
|
-
return desc;
|
|
60
|
-
}
|
|
61
|
-
//#endregion
|
|
62
|
-
export { closestActionable, describeActionable, isActionable };
|
|
1
|
+
const ACTIONABLE_TAGS=new Set([`a`,`button`,`input`,`select`,`textarea`,`summary`,`label`]),ACTIONABLE_ROLES=new Set([`button`,`link`,`menuitem`,`tab`,`checkbox`,`radio`,`switch`]);function isActionable(el){if(ACTIONABLE_TAGS.has(el.tagName.toLowerCase()))return!0;let role=el.getAttribute(`role`);return role&&ACTIONABLE_ROLES.has(role)?!0:el.hasAttribute(`data-track-click`)}function closestActionable(target){if(!(target instanceof Element))return null;let el=target;for(;el;){if(isActionable(el))return el;el=el.parentElement}return null}function describeActionable(el){let desc={tag:el.tagName.toLowerCase()};el.id&&(desc.id=el.id);let role=el.getAttribute(`role`);role&&(desc.role=role);let name=el.getAttribute(`name`);name&&(desc.name=name);let ariaLabel=el.getAttribute(`aria-label`);ariaLabel&&(desc.ariaLabel=ariaLabel),el instanceof HTMLAnchorElement&&(desc.href=el.href);let text=el.textContent?.trim().slice(0,120);return text&&(desc.text=text),desc}export{closestActionable,describeActionable,isActionable};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actionable.mjs","names":[],"sources":["../../../src/internal/dom/actionable.ts"],"sourcesContent":["/**\n * Single source of truth for \"what counts as a clickable element\" across\n * the SDK. Used by both the `pages` plugin (which captures `ui_event`\n * envelopes) and the OTel `UserInteractionInstrumentation` enrichment\n * hook (which gates span creation + stamps target attrs). Before this\n * was extracted, the two sites diverged and produced inconsistent\n * notions of \"actionable.\"\n *\n * `data-track-click` is the customer escape hatch — any element opted\n * in via that attribute is treated as actionable regardless of tag/role.\n */\n\nconst ACTIONABLE_TAGS = new Set([\n \"a\",\n \"button\",\n \"input\",\n \"select\",\n \"textarea\",\n \"summary\",\n \"label\",\n]);\n\nconst ACTIONABLE_ROLES = new Set([\n \"button\",\n \"link\",\n \"menuitem\",\n \"tab\",\n \"checkbox\",\n \"radio\",\n \"switch\",\n]);\n\nconst TEXT_TRUNCATE = 120;\n\nexport function isActionable(el: Element): boolean {\n if (ACTIONABLE_TAGS.has(el.tagName.toLowerCase())) {\n return true;\n }\n const role = el.getAttribute(\"role\");\n if (role && ACTIONABLE_ROLES.has(role)) {\n return true;\n }\n return el.hasAttribute(\"data-track-click\");\n}\n\nexport function closestActionable(target: EventTarget | null): Element | null {\n if (!(target instanceof Element)) {\n return null;\n }\n let el: Element | null = target;\n while (el) {\n if (isActionable(el)) {\n return el;\n }\n el = el.parentElement;\n }\n return null;\n}\n\nexport interface ActionableDescriptor {\n ariaLabel?: string | undefined;\n href?: string | undefined;\n id?: string | undefined;\n name?: string | undefined;\n role?: string | undefined;\n tag: string;\n text?: string | undefined;\n [key: string]: unknown;\n}\n\nexport function describeActionable(el: Element): ActionableDescriptor {\n const desc: ActionableDescriptor = {\n tag: el.tagName.toLowerCase(),\n };\n if (el.id) {\n desc.id = el.id;\n }\n const role = el.getAttribute(\"role\");\n if (role) {\n desc.role = role;\n }\n const name = el.getAttribute(\"name\");\n if (name) {\n desc.name = name;\n }\n const ariaLabel = el.getAttribute(\"aria-label\");\n if (ariaLabel) {\n desc.ariaLabel = ariaLabel;\n }\n if (el instanceof HTMLAnchorElement) {\n desc.href = el.href;\n }\n const text = el.textContent?.trim().slice(0, TEXT_TRUNCATE);\n if (text) {\n desc.text = text;\n }\n return desc;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"actionable.mjs","names":[],"sources":["../../../src/internal/dom/actionable.ts"],"sourcesContent":["/**\n * Single source of truth for \"what counts as a clickable element\" across\n * the SDK. Used by both the `pages` plugin (which captures `ui_event`\n * envelopes) and the OTel `UserInteractionInstrumentation` enrichment\n * hook (which gates span creation + stamps target attrs). Before this\n * was extracted, the two sites diverged and produced inconsistent\n * notions of \"actionable.\"\n *\n * `data-track-click` is the customer escape hatch — any element opted\n * in via that attribute is treated as actionable regardless of tag/role.\n */\n\nconst ACTIONABLE_TAGS = new Set([\n \"a\",\n \"button\",\n \"input\",\n \"select\",\n \"textarea\",\n \"summary\",\n \"label\",\n]);\n\nconst ACTIONABLE_ROLES = new Set([\n \"button\",\n \"link\",\n \"menuitem\",\n \"tab\",\n \"checkbox\",\n \"radio\",\n \"switch\",\n]);\n\nconst TEXT_TRUNCATE = 120;\n\nexport function isActionable(el: Element): boolean {\n if (ACTIONABLE_TAGS.has(el.tagName.toLowerCase())) {\n return true;\n }\n const role = el.getAttribute(\"role\");\n if (role && ACTIONABLE_ROLES.has(role)) {\n return true;\n }\n return el.hasAttribute(\"data-track-click\");\n}\n\nexport function closestActionable(target: EventTarget | null): Element | null {\n if (!(target instanceof Element)) {\n return null;\n }\n let el: Element | null = target;\n while (el) {\n if (isActionable(el)) {\n return el;\n }\n el = el.parentElement;\n }\n return null;\n}\n\nexport interface ActionableDescriptor {\n ariaLabel?: string | undefined;\n href?: string | undefined;\n id?: string | undefined;\n name?: string | undefined;\n role?: string | undefined;\n tag: string;\n text?: string | undefined;\n [key: string]: unknown;\n}\n\nexport function describeActionable(el: Element): ActionableDescriptor {\n const desc: ActionableDescriptor = {\n tag: el.tagName.toLowerCase(),\n };\n if (el.id) {\n desc.id = el.id;\n }\n const role = el.getAttribute(\"role\");\n if (role) {\n desc.role = role;\n }\n const name = el.getAttribute(\"name\");\n if (name) {\n desc.name = name;\n }\n const ariaLabel = el.getAttribute(\"aria-label\");\n if (ariaLabel) {\n desc.ariaLabel = ariaLabel;\n }\n if (el instanceof HTMLAnchorElement) {\n desc.href = el.href;\n }\n const text = el.textContent?.trim().slice(0, TEXT_TRUNCATE);\n if (text) {\n desc.text = text;\n }\n return desc;\n}\n"],"mappings":"AAYA,MAAM,gBAAkB,IAAI,IAAI,CAC9B,IACA,SACA,QACA,SACA,WACA,UACA,OACF,CAAC,EAEK,iBAAmB,IAAI,IAAI,CAC/B,SACA,OACA,WACA,MACA,WACA,QACA,QACF,CAAC,EAID,SAAgB,aAAa,GAAsB,CACjD,GAAI,gBAAgB,IAAI,GAAG,QAAQ,YAAY,CAAC,EAC9C,MAAO,GAET,IAAM,KAAO,GAAG,aAAa,MAAM,EAInC,OAHI,MAAQ,iBAAiB,IAAI,IAAI,EAC5B,GAEF,GAAG,aAAa,kBAAkB,CAC3C,CAEA,SAAgB,kBAAkB,OAA4C,CAC5E,GAAI,EAAE,kBAAkB,SACtB,OAAO,KAET,IAAI,GAAqB,OACzB,KAAO,IAAI,CACT,GAAI,aAAa,EAAE,EACjB,OAAO,GAET,GAAK,GAAG,aACV,CACA,OAAO,IACT,CAaA,SAAgB,mBAAmB,GAAmC,CACpE,IAAM,KAA6B,CACjC,IAAK,GAAG,QAAQ,YAAY,CAC9B,EACI,GAAG,KACL,KAAK,GAAK,GAAG,IAEf,IAAM,KAAO,GAAG,aAAa,MAAM,EAC/B,OACF,KAAK,KAAO,MAEd,IAAM,KAAO,GAAG,aAAa,MAAM,EAC/B,OACF,KAAK,KAAO,MAEd,IAAM,UAAY,GAAG,aAAa,YAAY,EAC1C,YACF,KAAK,UAAY,WAEf,cAAc,oBAChB,KAAK,KAAO,GAAG,MAEjB,IAAM,KAAO,GAAG,aAAa,KAAK,EAAE,MAAM,EAAG,GAAa,EAI1D,OAHI,OACF,KAAK,KAAO,MAEP,IACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kernel-registry.d.mts","names":[],"sources":["../../src/internal/kernel-registry.ts"],"mappings":";;;iBAsBgB,cAAA,CAAe,
|
|
1
|
+
{"version":3,"file":"kernel-registry.d.mts","names":[],"sources":["../../src/internal/kernel-registry.ts"],"mappings":";;;iBAsBgB,cAAA,CAAe,MAAc,EAAN,MAAM;AAAA,iBAS7B,gBAAA,CAAiB,MAAc,EAAN,MAAM;AAAA,iBAM/B,YAAA,CAAA,GAAgB,MAAM"}
|
|
@@ -1,31 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
//#region src/internal/kernel-registry.ts
|
|
3
|
-
const log = createLogger("kernel-registry");
|
|
4
|
-
/**
|
|
5
|
-
* Module-scoped active kernel reference. The framework wrapper
|
|
6
|
-
* (`@interfere/next`, `@interfere/vite`) registers its kernel here on
|
|
7
|
-
* `init()` so the public `api.ts` helpers can find it without each customer
|
|
8
|
-
* call site threading the kernel through.
|
|
9
|
-
*
|
|
10
|
-
* Single-kernel by design: each app bundle has its own module instance, so
|
|
11
|
-
* the common case (one app, one init) gets a clean global. The warn on
|
|
12
|
-
* overwrite catches HMR / accidental double-init / module-federated MFEs
|
|
13
|
-
* that share `@interfere/react` across surfaces — those setups need to
|
|
14
|
-
* reach for `kernel.recordException()` directly rather than the registry.
|
|
15
|
-
*
|
|
16
|
-
* @internal Wrappers only. Importing this from app code will silently
|
|
17
|
-
* de-fang the public api helpers.
|
|
18
|
-
*/
|
|
19
|
-
let active = null;
|
|
20
|
-
function registerKernel(kernel) {
|
|
21
|
-
if (active && active !== kernel) log.warn("init() called twice with different kernels; later one wins. The public capture()/span() helpers route only to the latest kernel — multi-surface apps should call kernel.recordException() directly.");
|
|
22
|
-
active = kernel;
|
|
23
|
-
}
|
|
24
|
-
function unregisterKernel(kernel) {
|
|
25
|
-
if (active === kernel) active = null;
|
|
26
|
-
}
|
|
27
|
-
function activeKernel() {
|
|
28
|
-
return active;
|
|
29
|
-
}
|
|
30
|
-
//#endregion
|
|
31
|
-
export { activeKernel, registerKernel, unregisterKernel };
|
|
1
|
+
import{createLogger}from"../util/log.mjs";const log=createLogger(`kernel-registry`);let active=null;function registerKernel(kernel){active&&active!==kernel&&log.warn(`init() called twice with different kernels; later one wins. The public capture()/span() helpers route only to the latest kernel — multi-surface apps should call kernel.recordException() directly.`),active=kernel}function unregisterKernel(kernel){active===kernel&&(active=null)}function activeKernel(){return active}export{activeKernel,registerKernel,unregisterKernel};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kernel-registry.mjs","names":[],"sources":["../../src/internal/kernel-registry.ts"],"sourcesContent":["import { createLogger } from \"../util/log.js\";\nimport type { Kernel } from \"./kernel.js\";\n\nconst log = createLogger(\"kernel-registry\");\n\n/**\n * Module-scoped active kernel reference. The framework wrapper\n * (`@interfere/next`, `@interfere/vite`) registers its kernel here on\n * `init()` so the public `api.ts` helpers can find it without each customer\n * call site threading the kernel through.\n *\n * Single-kernel by design: each app bundle has its own module instance, so\n * the common case (one app, one init) gets a clean global. The warn on\n * overwrite catches HMR / accidental double-init / module-federated MFEs\n * that share `@interfere/react` across surfaces — those setups need to\n * reach for `kernel.recordException()` directly rather than the registry.\n *\n * @internal Wrappers only. Importing this from app code will silently\n * de-fang the public api helpers.\n */\nlet active: Kernel | null = null;\n\nexport function registerKernel(kernel: Kernel): void {\n if (active && active !== kernel) {\n log.warn(\n \"init() called twice with different kernels; later one wins. The public capture()/span() helpers route only to the latest kernel — multi-surface apps should call kernel.recordException() directly.\"\n );\n }\n active = kernel;\n}\n\nexport function unregisterKernel(kernel: Kernel): void {\n if (active === kernel) {\n active = null;\n }\n}\n\nexport function activeKernel(): Kernel | null {\n return active;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"kernel-registry.mjs","names":[],"sources":["../../src/internal/kernel-registry.ts"],"sourcesContent":["import { createLogger } from \"../util/log.js\";\nimport type { Kernel } from \"./kernel.js\";\n\nconst log = createLogger(\"kernel-registry\");\n\n/**\n * Module-scoped active kernel reference. The framework wrapper\n * (`@interfere/next`, `@interfere/vite`) registers its kernel here on\n * `init()` so the public `api.ts` helpers can find it without each customer\n * call site threading the kernel through.\n *\n * Single-kernel by design: each app bundle has its own module instance, so\n * the common case (one app, one init) gets a clean global. The warn on\n * overwrite catches HMR / accidental double-init / module-federated MFEs\n * that share `@interfere/react` across surfaces — those setups need to\n * reach for `kernel.recordException()` directly rather than the registry.\n *\n * @internal Wrappers only. Importing this from app code will silently\n * de-fang the public api helpers.\n */\nlet active: Kernel | null = null;\n\nexport function registerKernel(kernel: Kernel): void {\n if (active && active !== kernel) {\n log.warn(\n \"init() called twice with different kernels; later one wins. The public capture()/span() helpers route only to the latest kernel — multi-surface apps should call kernel.recordException() directly.\"\n );\n }\n active = kernel;\n}\n\nexport function unregisterKernel(kernel: Kernel): void {\n if (active === kernel) {\n active = null;\n }\n}\n\nexport function activeKernel(): Kernel | null {\n return active;\n}\n"],"mappings":"0CAGA,MAAM,IAAM,aAAa,iBAAiB,EAiB1C,IAAI,OAAwB,KAE5B,SAAgB,eAAe,OAAsB,CAC/C,QAAU,SAAW,QACvB,IAAI,KACF,qMACF,EAEF,OAAS,MACX,CAEA,SAAgB,iBAAiB,OAAsB,CACjD,SAAW,SACb,OAAS,KAEb,CAEA,SAAgB,cAA8B,CAC5C,OAAO,MACT"}
|
|
@@ -33,7 +33,7 @@ interface KernelOptions {
|
|
|
33
33
|
* Override the OTel `service.name` resource attribute. Defaults to
|
|
34
34
|
* `"interfere-sdk"`. Customers running multiple frontends in one
|
|
35
35
|
* monitoring backend (e.g. `"@interfere/homepage"` vs
|
|
36
|
-
* `"@interfere/
|
|
36
|
+
* `"@interfere/app"`) set this so spans/metrics/logs slice
|
|
37
37
|
* cleanly by surface.
|
|
38
38
|
*/
|
|
39
39
|
serviceName?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kernel.d.mts","names":[],"sources":["../../src/internal/kernel.ts"],"mappings":";;;;;;;;;;;;;;;;;iBAyCgB,aAAA,CAAc,
|
|
1
|
+
{"version":3,"file":"kernel.d.mts","names":[],"sources":["../../src/internal/kernel.ts"],"mappings":";;;;;;;;;;;;;;;;;iBAyCgB,aAAA,CAAc,eAA0B;AAAA,UAyDvC,aAAA;EACf,OAAA,GAAU,YAAA;EA1DI;;;;AAAwC;AAyDxD;;EASE,OAAA;EARU;EAUV,KAAA,UAAe,UAAA,CAAW,KAAA;EAC1B,OAAA,GAAU,eAAA;EAgBU;;;;;;;EARpB,WAAA;EAT0B;;;;;;;EAiB1B,OAAA,aAAoB,cAAA;AAAA;;;;;;;UASL,cAAA;EAsBf;;;;AAMS;AAUX;EA/BE,UAAA,aAAuB,MAAA;;;;;;;EAOvB,oBAAA,aAAiC,MAAM;EAwBM;;;;;;;EAhB7C,YAAA,IAAgB,QAAA;EAuCA;AAAA;AAGlB;;;EApCE,SAAA;AAAA;;;;;;AAsCwB;AAG1B;UA/BiB,qBAAA,SAA8B,aAAA;;;;;;;;EAQ7C,sCAAA,GAAyC,kBAAA;EAyBzC;;;;;AAAoC;AAGtC;EApBE,gCAAA,GAAmC,YAAA;;;AAsB1B;AAGX;EApBE,iCAAA,GAAoC,aAAA;;EAEpC,gBAAA;AAAA;AAAA,UAGe,aAAA;EACf,GAAA,IAAO,YAAA;EACP,GAAA,CAAI,KAAA,GAAQ,YAAY;AAAA;AAAA,UAGT,cAAA;EACf,GAAA,IAAO,cAAA;EACP,GAAA,CAAI,MAAA,EAAQ,cAAA,GAAiB,OAAA;AAAA;AAAA,UAGd,YAAA;EACf,WAAA;EACA,SAAS;AAAA;AAAA,UAGM,aAAA;EACf,KAAA,IAAS,SAAS;EAClB,WAAA;AAAA;AAAA,UAGe,mBAAA;EAuBN;;;AAAyB;AAcpC;;EAdW,SAhBA,YAAA,YAAwB,aAAA;EA+Bf;;;;;;;EAAA,SAvBT,WAAA;EA0HD;;;;;;EAAA,SAnHC,cAAA,YAA0B,aAAA;EAAA,SAC1B,SAAA,EAAW,cAAA;AAAA;;;;;;;;;;;;cAcT,MAAA,YAAkB,aAAA;EAAA,SACpB,OAAA,EAAS,aAAA;EAAA,SACT,QAAA,EAAU,cAAA;EAAA,SACV,MAAA,EAAQ,YAAA;EAAA,SACR,OAAA,EAAS,aAAA;EAAA,iBAED,OAAA;EAAA,iBAGA,IAAA;EAAA,QAKT,OAAA;EAAA,QACA,IAAA;EAAA,QACA,WAAA;EAAA,QACA,qBAAA;cAEI,OAAA,EAAS,cAAA;EA+ErB;EAtCA,aAAA,CAAc,OAAA,EAAS,aAAA;EAAA,QAIf,cAAA;EAmCN;EAzBF,YAAA,CAAA;EA0BE;;;;;;;;;;;;;;;;;;;;EAFF,eAAA,CACE,KAAA,EAAO,KAAA,GAAQ,iBAAA,EACf,IAAA,EAAM,mBAAA;EA6HA;;;;AACoB;AAS9B;;EAlFE,SAAA,CAAU,KAAA;IACR,YAAA;IACA,cAAA;IACA,IAAA;IACA,UAAA,GAAa,MAAA;EAAA;EA+ER;;;;;AACQ;AA4PjB;EAxTQ,KAAA,CAAA,GAAS,OAAA;;;AAwTqB;AAyBtC;;;;EApUE,UAAA,CAAW,MAAA,EAAQ,kBAAA,EAAoB,OAAA;;;;;EASvC,WAAA,CAAY,KAAA,EAAO,YAAA;EASb,OAAA,CAAA,GAAW,OAAA;AAAA;AAAA,UAiBT,iBAAA;EACR,IAAA,GAAO,qBAAqB;AAAA;;;;;;;iBASR,YAAA,CACpB,KAAA,GAAO,iBAAA,GACN,OAAA,CAAQ,MAAA;AAAA,iBA4PK,sBAAA,CAAA;;;;;;;;;;;;iBAyBA,0BAAA,CAAA,GAA8B,WAAW"}
|
package/dist/internal/kernel.mjs
CHANGED
|
@@ -1,322 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { getGlobal } from "../util/global.mjs";
|
|
3
|
-
import { resolveTargets } from "./config.mjs";
|
|
4
|
-
import { SessionTracker } from "../tracking/api.mjs";
|
|
5
|
-
import { safeStringify } from "../util/stringify.mjs";
|
|
6
|
-
import { onPageHidden } from "./page-lifecycle.mjs";
|
|
7
|
-
import { PluginRuntime } from "./plugin-runtime.mjs";
|
|
8
|
-
import { registerServiceWorker } from "./sw.mjs";
|
|
9
|
-
import { PRODUCER_VERSION } from "./version.mjs";
|
|
10
|
-
import { isNonErrorException, shouldDropBrowserExtensionNoise, shouldDropUnresolvableStack, toExceptions } from "@interfere/types/sdk/errors";
|
|
11
|
-
import { trace } from "@opentelemetry/api";
|
|
12
|
-
import { releaseSlugSchema } from "@interfere/types/releases/slug";
|
|
13
|
-
//#region src/internal/kernel.ts
|
|
14
|
-
const log = createLogger("kernel");
|
|
15
|
-
const TRAILING_SLASH_RE = /\/$/;
|
|
16
|
-
function buildSdkStack(wrapperVersions) {
|
|
17
|
-
return [...wrapperVersions ?? [], PRODUCER_VERSION];
|
|
18
|
-
}
|
|
19
|
-
function buildExceptionEventAttrs(value, opts) {
|
|
20
|
-
const attrs = {
|
|
21
|
-
"interfere.exception.mechanism": opts.mechanism.type,
|
|
22
|
-
"interfere.exception.handled": String(opts.mechanism.handled)
|
|
23
|
-
};
|
|
24
|
-
if (opts.errorDigest) attrs["interfere.error.digest"] = opts.errorDigest;
|
|
25
|
-
if (isNonErrorException(value)) {
|
|
26
|
-
attrs["exception.type"] = value.type;
|
|
27
|
-
attrs["exception.message"] = value.value;
|
|
28
|
-
attrs["interfere.exception.kind"] = "non-error";
|
|
29
|
-
attrs["interfere.exception.serialized"] = safeStringify(value.serialized);
|
|
30
|
-
return attrs;
|
|
31
|
-
}
|
|
32
|
-
attrs["exception.type"] = value.name;
|
|
33
|
-
attrs["exception.message"] = value.message;
|
|
34
|
-
attrs["interfere.exception.kind"] = "error";
|
|
35
|
-
if (value.stack) attrs["exception.stacktrace"] = value.stack;
|
|
36
|
-
attrs["interfere.exception.chain"] = safeStringify(toExceptions(value, opts.mechanism));
|
|
37
|
-
if (opts.appendFrames && opts.appendFrames.length > 0) attrs["interfere.exception.appended_frames"] = safeStringify(opts.appendFrames);
|
|
38
|
-
if (opts.fallbackFrames && opts.fallbackFrames.length > 0) attrs["interfere.exception.fallback_frames"] = safeStringify(opts.fallbackFrames);
|
|
39
|
-
return attrs;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* The replacement for the old `Client` god-object. Pure construction graph
|
|
43
|
-
* via `createKernel`; no module-level mutable state, no global lookups.
|
|
44
|
-
* Framework wrappers (`@interfere/next`, `@interfere/vite`) own the
|
|
45
|
-
* singleton lifecycle.
|
|
46
|
-
*
|
|
47
|
-
* Implements `PluginContext` directly: plugins are wired with the kernel
|
|
48
|
-
* itself rather than a closure adapter. The kernel⇄runtime cycle is
|
|
49
|
-
* resolved by constructing the kernel first and binding the runtime via
|
|
50
|
-
* `attachRuntime` immediately after.
|
|
51
|
-
*/
|
|
52
|
-
var Kernel = class {
|
|
53
|
-
consent;
|
|
54
|
-
identity;
|
|
55
|
-
device;
|
|
56
|
-
session;
|
|
57
|
-
tracker;
|
|
58
|
-
seen = /* @__PURE__ */ new WeakSet();
|
|
59
|
-
runtime = null;
|
|
60
|
-
otel = null;
|
|
61
|
-
otelDispose = null;
|
|
62
|
-
unsubscribePageHidden = null;
|
|
63
|
-
constructor(tracker) {
|
|
64
|
-
this.tracker = tracker;
|
|
65
|
-
this.consent = {
|
|
66
|
-
get: () => this.requireRuntime().getConsent(),
|
|
67
|
-
set: (value) => {
|
|
68
|
-
const runtime = this.requireRuntime();
|
|
69
|
-
if (value) runtime.setConsent(value);
|
|
70
|
-
else runtime.resetConsent();
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
this.identity = {
|
|
74
|
-
get: () => this.tracker.getIdentity(),
|
|
75
|
-
set: (params) => this.tracker.identify(params)
|
|
76
|
-
};
|
|
77
|
-
this.device = {
|
|
78
|
-
getDeviceId: () => this.tracker.getDeviceId(),
|
|
79
|
-
getFpHash: () => this.tracker.getFpHash()
|
|
80
|
-
};
|
|
81
|
-
this.session = {
|
|
82
|
-
getId: () => this.tracker.sessionId(),
|
|
83
|
-
getWindowId: () => this.tracker.windowId()
|
|
84
|
-
};
|
|
85
|
-
this.unsubscribePageHidden = onPageHidden(() => {
|
|
86
|
-
this.flush().catch(() => void 0);
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
/** @internal Bound by `createKernel` immediately after construction. */
|
|
90
|
-
attachRuntime(runtime) {
|
|
91
|
-
this.runtime = runtime;
|
|
92
|
-
}
|
|
93
|
-
requireRuntime() {
|
|
94
|
-
if (!this.runtime) throw new Error("Kernel runtime not attached. createKernel must call attachRuntime before any consent/dispose path runs.");
|
|
95
|
-
return this.runtime;
|
|
96
|
-
}
|
|
97
|
-
/** PluginContext shim: plugins expect a non-null string. */
|
|
98
|
-
getSessionId() {
|
|
99
|
-
return this.tracker.sessionId() ?? "";
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Records an exception event on the active OTel span. No envelope
|
|
103
|
-
* path — the SDK is OTel-only. Single dedup boundary: drops repeats
|
|
104
|
-
* of the same `Error` instance, plus browser-extension noise and
|
|
105
|
-
* unresolvable stacks. Upstream callers (the errors plugin's
|
|
106
|
-
* `window.onerror` / `console.error` / `unhandledrejection`, the
|
|
107
|
-
* React error boundary) just call this — they don't pre-filter.
|
|
108
|
-
*
|
|
109
|
-
* Does NOT mark the active span as failed. A console.error inside a
|
|
110
|
-
* customer-owned `span()` block would otherwise taint the customer's
|
|
111
|
-
* span even when the customer caught the error explicitly. The
|
|
112
|
-
* customer's `span()` helper sets status itself when it observes a
|
|
113
|
-
* throw.
|
|
114
|
-
*
|
|
115
|
-
* `appendFrames` are always appended (component-stack supplements);
|
|
116
|
-
* `fallbackFrames` are only adopted when the parsed stack is empty
|
|
117
|
-
* (`window.onerror` source/line/col when the Error has no usable
|
|
118
|
-
* stack). Both ride on the serialised `interfere.exception.chain`
|
|
119
|
-
* attribute when the chain is non-trivial.
|
|
120
|
-
*/
|
|
121
|
-
recordException(value, opts) {
|
|
122
|
-
if (!isNonErrorException(value)) {
|
|
123
|
-
if (this.seen.has(value)) return;
|
|
124
|
-
this.seen.add(value);
|
|
125
|
-
}
|
|
126
|
-
if (!isNonErrorException(value)) {
|
|
127
|
-
const stacks = [];
|
|
128
|
-
let current = value;
|
|
129
|
-
for (let depth = 0; current && depth < 5; depth += 1) {
|
|
130
|
-
if (current.stack) stacks.push(current.stack);
|
|
131
|
-
current = current.cause instanceof Error ? current.cause : void 0;
|
|
132
|
-
}
|
|
133
|
-
if (shouldDropBrowserExtensionNoise(stacks) || shouldDropUnresolvableStack(stacks)) return;
|
|
134
|
-
}
|
|
135
|
-
const span = trace.getActiveSpan();
|
|
136
|
-
if (span) span.addEvent("exception", buildExceptionEventAttrs(value, opts));
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Emits an OTel `LogRecord` via the kernel's logger provider. Used by
|
|
140
|
-
* `plugins/logs.ts` to capture string-only `console.*` calls (the
|
|
141
|
-
* errors plugin still owns Error-bearing console calls — class
|
|
142
|
-
* boundary). No-ops when OTel is not wired (`tracing: false`) since
|
|
143
|
-
* there's no logger provider to emit through.
|
|
144
|
-
*/
|
|
145
|
-
recordLog(input) {
|
|
146
|
-
if (!this.otel) return;
|
|
147
|
-
this.otel.loggerProvider.getLogger("@interfere/react").emit({
|
|
148
|
-
severityText: input.severityText,
|
|
149
|
-
severityNumber: input.severityNumber,
|
|
150
|
-
body: input.body,
|
|
151
|
-
...input.attributes ? { attributes: input.attributes } : {}
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Force-flushes the OTel exporters. Returns once they've settled
|
|
156
|
-
* (success or failure) so callers driving unload — page-lifecycle
|
|
157
|
-
* handlers, integration test teardown — can actually await
|
|
158
|
-
* completion. The OTel SDK swallows export errors internally; we
|
|
159
|
-
* surface the resolution either way.
|
|
160
|
-
*/
|
|
161
|
-
async flush() {
|
|
162
|
-
if (this.otel) await this.otel.flush();
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* @internal Wired by `createKernel` after the OTel module has been
|
|
166
|
-
* lazy-loaded. Held on the kernel so `flush()` and `dispose()` can fan
|
|
167
|
-
* out to the providers. The tracer/meter providers stay private —
|
|
168
|
-
* customers don't get raw OTel access; the public surface is the
|
|
169
|
-
* `span()` / `capture()` helpers in `api.ts`.
|
|
170
|
-
*/
|
|
171
|
-
attachOtel(handle, dispose) {
|
|
172
|
-
this.otel = handle;
|
|
173
|
-
this.otelDispose = dispose;
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Provider-driven consent sync. Pass `undefined` to clear consent (treats
|
|
177
|
-
* the prop as "unmanaged"); pass a state to apply it.
|
|
178
|
-
*/
|
|
179
|
-
syncConsent(value) {
|
|
180
|
-
const runtime = this.requireRuntime();
|
|
181
|
-
if (value) {
|
|
182
|
-
runtime.setConsent(value);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
runtime.resetConsent();
|
|
186
|
-
}
|
|
187
|
-
async dispose() {
|
|
188
|
-
this.unsubscribePageHidden?.();
|
|
189
|
-
this.unsubscribePageHidden = null;
|
|
190
|
-
if (this.runtime) await this.runtime.dispose();
|
|
191
|
-
this.tracker.dispose();
|
|
192
|
-
this.otelDispose?.();
|
|
193
|
-
this.otelDispose = null;
|
|
194
|
-
if (this.otel) {
|
|
195
|
-
await this.otel.shutdown();
|
|
196
|
-
this.otel = null;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
/**
|
|
201
|
-
* Pure construction graph. Awaits the remote-config fetch before resolving,
|
|
202
|
-
* so the runtime sees a fully-applied config before any capture is attempted
|
|
203
|
-
* by the caller. Removes the capture-before-config race the old `Client`
|
|
204
|
-
* had.
|
|
205
|
-
*/
|
|
206
|
-
async function createKernel(input = {}) {
|
|
207
|
-
const { opts = {} } = input;
|
|
208
|
-
const fetcher = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
209
|
-
const targets = resolveTargets();
|
|
210
|
-
log.info("collector: %s", targets.collectorBaseUrl);
|
|
211
|
-
if (getGlobal("__INTERFERE_FORCE_ENABLE__")) log.warn("FORCE_ENABLE is active. Events will be dropped by production collectors. This should only be set during local development.");
|
|
212
|
-
const tracker = new SessionTracker({
|
|
213
|
-
target: targets.session,
|
|
214
|
-
fetcher
|
|
215
|
-
});
|
|
216
|
-
if (opts._wrapperVersions?.length) globalThis["__INTERFERE_SDK_STACK__"] = opts._wrapperVersions;
|
|
217
|
-
const kernel = new Kernel(tracker);
|
|
218
|
-
const runtime = new PluginRuntime(kernel, opts.plugins, opts.consent);
|
|
219
|
-
kernel.attachRuntime(runtime);
|
|
220
|
-
tracker.start();
|
|
221
|
-
runtime.start();
|
|
222
|
-
if (opts.tracing !== false) {
|
|
223
|
-
const tracingOpts = typeof opts.tracing === "object" ? opts.tracing : void 0;
|
|
224
|
-
await wireOtel(kernel, targets, opts._wrapperVersions, {
|
|
225
|
-
...tracingOpts ?? {},
|
|
226
|
-
...opts.serviceName ? { serviceName: opts.serviceName } : {},
|
|
227
|
-
...opts._internalAdditionalSpanProcessors ? { additionalSpanProcessors: opts._internalAdditionalSpanProcessors } : {},
|
|
228
|
-
...opts._internalAdditionalLogRecordProcessors ? { additionalLogRecordProcessors: opts._internalAdditionalLogRecordProcessors } : {},
|
|
229
|
-
...opts._internalAdditionalMetricReaders ? { additionalMetricReaders: opts._internalAdditionalMetricReaders } : {}
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
await fetchRemoteConfig(targets.config, fetcher, runtime);
|
|
233
|
-
registerServiceWorker({ onMessage: (msg) => recordSwMessage(kernel, msg) }).catch(() => {});
|
|
234
|
-
return kernel;
|
|
235
|
-
}
|
|
236
|
-
function recordSwMessage(kernel, msg) {
|
|
237
|
-
kernel.recordLog({
|
|
238
|
-
severityText: "info",
|
|
239
|
-
severityNumber: 9,
|
|
240
|
-
body: msg.type,
|
|
241
|
-
attributes: {
|
|
242
|
-
"url.full": msg.url,
|
|
243
|
-
...msg.reason ? { "interfere.sw.reason": msg.reason } : {}
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
async function wireOtel(kernel, targets, wrapperVersions, tracing) {
|
|
248
|
-
const { buildOtelProvider, captureWebVitals, readPropagationFromDocument, registerBundledInstrumentations } = await import("./otel/index.mjs");
|
|
249
|
-
const collectorUrl = targets.collectorBaseUrl.replace(TRAILING_SLASH_RE, "");
|
|
250
|
-
const releaseSlug = readReleaseSlugFromGlobals();
|
|
251
|
-
const handle = buildOtelProvider({
|
|
252
|
-
collectorUrl,
|
|
253
|
-
authHeaders: targets.ingest.headers,
|
|
254
|
-
sdkStack: buildSdkStack(wrapperVersions),
|
|
255
|
-
getSessionId: () => kernel.session.getId(),
|
|
256
|
-
deviceId: kernel.device.getDeviceId(),
|
|
257
|
-
releaseSlug,
|
|
258
|
-
...tracing.serviceName ? { serviceName: tracing.serviceName } : {},
|
|
259
|
-
...tracing.additionalSpanProcessors ? { additionalSpanProcessors: tracing.additionalSpanProcessors } : {},
|
|
260
|
-
...tracing.additionalLogRecordProcessors ? { additionalLogRecordProcessors: tracing.additionalLogRecordProcessors } : {},
|
|
261
|
-
...tracing.additionalMetricReaders ? { additionalMetricReaders: tracing.additionalMetricReaders } : {}
|
|
262
|
-
});
|
|
263
|
-
const extracted = readPropagationFromDocument();
|
|
264
|
-
if (extracted) handle.contextManager.setPageScope(extracted);
|
|
265
|
-
const dispose = registerBundledInstrumentations({
|
|
266
|
-
tracerProvider: handle.tracerProvider,
|
|
267
|
-
ignoreUrls: [
|
|
268
|
-
`${collectorUrl}/v2/sink`,
|
|
269
|
-
targets.config.url,
|
|
270
|
-
targets.session.url
|
|
271
|
-
],
|
|
272
|
-
...tracing.propagateContextUrls ? { propagateContextUrls: tracing.propagateContextUrls } : {},
|
|
273
|
-
...tracing.resolveRoute ? { resolveRoute: tracing.resolveRoute } : {}
|
|
274
|
-
});
|
|
275
|
-
if (tracing.webVitals !== false) captureWebVitals({
|
|
276
|
-
meter: handle.meterProvider.getMeter("@interfere/react/web-vitals"),
|
|
277
|
-
flush: () => handle.flush(),
|
|
278
|
-
...tracing.resolveRoute ? { resolveRoute: tracing.resolveRoute } : {}
|
|
279
|
-
});
|
|
280
|
-
kernel.attachOtel(handle, dispose);
|
|
281
|
-
}
|
|
282
|
-
async function fetchRemoteConfig(configTarget, fetcher, runtime) {
|
|
283
|
-
try {
|
|
284
|
-
const headers = Object.fromEntries(configTarget.headers.entries());
|
|
285
|
-
const res = await fetcher(configTarget.url, {
|
|
286
|
-
method: "GET",
|
|
287
|
-
headers,
|
|
288
|
-
signal: AbortSignal.timeout(1e4)
|
|
289
|
-
});
|
|
290
|
-
if (!res.ok) return;
|
|
291
|
-
const config = await res.json();
|
|
292
|
-
if (config?.plugins) {
|
|
293
|
-
runtime.applyRemoteConfig(config.plugins);
|
|
294
|
-
log.debug("applied remote config");
|
|
295
|
-
}
|
|
296
|
-
} catch {
|
|
297
|
-
log.warn("remote config fetch failed, using local defaults");
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
function isEnabledByEnvironment() {
|
|
301
|
-
if (typeof process === "undefined" || !process.env) return true;
|
|
302
|
-
const nodeEnv = process.env["NODE_ENV"];
|
|
303
|
-
if (nodeEnv === "production" || nodeEnv === void 0) return true;
|
|
304
|
-
return !!(getGlobal("__INTERFERE_FORCE_ENABLE__") || process.env["INTERFERE_FORCE_ENABLE"]);
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* Build-time-derived release slug. The build step
|
|
308
|
-
* (`@interfere/next/withInterfere`, `@interfere/vite/plugin`) takes the
|
|
309
|
-
* leading 16 hex chars of the commit SHA (`rel_<16hex>`) and stamps it into
|
|
310
|
-
* the bundle as `__INTERFERE_RELEASE_SLUG__`. The collector derives the same
|
|
311
|
-
* value from the create-release request, so the bundle's runtime slug is
|
|
312
|
-
* guaranteed to match the row without a round-trip.
|
|
313
|
-
*
|
|
314
|
-
* Returns `null` when no slug was injected (dev builds, customers who
|
|
315
|
-
* haven't wired up the plugin).
|
|
316
|
-
*/
|
|
317
|
-
function readReleaseSlugFromGlobals() {
|
|
318
|
-
const value = getGlobal("__INTERFERE_RELEASE_SLUG__");
|
|
319
|
-
return typeof value === "string" ? releaseSlugSchema.parse(value) : null;
|
|
320
|
-
}
|
|
321
|
-
//#endregion
|
|
322
|
-
export { Kernel, buildSdkStack, createKernel, isEnabledByEnvironment, readReleaseSlugFromGlobals };
|
|
1
|
+
import{createLogger}from"../util/log.mjs";import{getGlobal}from"../util/global.mjs";import{resolveTargets}from"./config.mjs";import{SessionTracker}from"../tracking/api.mjs";import{safeStringify}from"../util/stringify.mjs";import{onPageHidden}from"./page-lifecycle.mjs";import{PluginRuntime}from"./plugin-runtime.mjs";import{registerServiceWorker}from"./sw.mjs";import{PRODUCER_VERSION}from"./version.mjs";import{isNonErrorException,shouldDropBrowserExtensionNoise,shouldDropUnresolvableStack,toExceptions}from"@interfere/types/sdk/errors";import{trace}from"@opentelemetry/api";import{releaseSlugSchema}from"@interfere/types/releases/slug";const log=createLogger(`kernel`),TRAILING_SLASH_RE=/\/$/;function buildSdkStack(wrapperVersions){return[...wrapperVersions??[],PRODUCER_VERSION]}function buildExceptionEventAttrs(value,opts){let attrs={"interfere.exception.mechanism":opts.mechanism.type,"interfere.exception.handled":String(opts.mechanism.handled)};return opts.errorDigest&&(attrs[`interfere.error.digest`]=opts.errorDigest),isNonErrorException(value)?(attrs[`exception.type`]=value.type,attrs[`exception.message`]=value.value,attrs[`interfere.exception.kind`]=`non-error`,attrs[`interfere.exception.serialized`]=safeStringify(value.serialized),attrs):(attrs[`exception.type`]=value.name,attrs[`exception.message`]=value.message,attrs[`interfere.exception.kind`]=`error`,value.stack&&(attrs[`exception.stacktrace`]=value.stack),attrs[`interfere.exception.chain`]=safeStringify(toExceptions(value,opts.mechanism)),opts.appendFrames&&opts.appendFrames.length>0&&(attrs[`interfere.exception.appended_frames`]=safeStringify(opts.appendFrames)),opts.fallbackFrames&&opts.fallbackFrames.length>0&&(attrs[`interfere.exception.fallback_frames`]=safeStringify(opts.fallbackFrames)),attrs)}var Kernel=class{consent;identity;device;session;tracker;seen=new WeakSet;runtime=null;otel=null;otelDispose=null;unsubscribePageHidden=null;constructor(tracker){this.tracker=tracker,this.consent={get:()=>this.requireRuntime().getConsent(),set:value=>{let runtime=this.requireRuntime();value?runtime.setConsent(value):runtime.resetConsent()}},this.identity={get:()=>this.tracker.getIdentity(),set:params=>this.tracker.identify(params)},this.device={getDeviceId:()=>this.tracker.getDeviceId(),getFpHash:()=>this.tracker.getFpHash()},this.session={getId:()=>this.tracker.sessionId(),getWindowId:()=>this.tracker.windowId()},this.unsubscribePageHidden=onPageHidden(()=>{this.flush().catch(()=>void 0)})}attachRuntime(runtime){this.runtime=runtime}requireRuntime(){if(!this.runtime)throw Error(`Kernel runtime not attached. createKernel must call attachRuntime before any consent/dispose path runs.`);return this.runtime}getSessionId(){return this.tracker.sessionId()??``}recordException(value,opts){if(!isNonErrorException(value)){if(this.seen.has(value))return;this.seen.add(value)}if(!isNonErrorException(value)){let stacks=[],current=value;for(let depth=0;current&&depth<5;depth+=1)current.stack&&stacks.push(current.stack),current=current.cause instanceof Error?current.cause:void 0;if(shouldDropBrowserExtensionNoise(stacks)||shouldDropUnresolvableStack(stacks))return}let span=trace.getActiveSpan();span&&span.addEvent(`exception`,buildExceptionEventAttrs(value,opts))}recordLog(input){this.otel&&this.otel.loggerProvider.getLogger(`@interfere/react`).emit({severityText:input.severityText,severityNumber:input.severityNumber,body:input.body,...input.attributes?{attributes:input.attributes}:{}})}async flush(){this.otel&&await this.otel.flush()}attachOtel(handle,dispose){this.otel=handle,this.otelDispose=dispose}syncConsent(value){let runtime=this.requireRuntime();if(value){runtime.setConsent(value);return}runtime.resetConsent()}async dispose(){this.unsubscribePageHidden?.(),this.unsubscribePageHidden=null,this.runtime&&await this.runtime.dispose(),this.tracker.dispose(),this.otelDispose?.(),this.otelDispose=null,this.otel&&=(await this.otel.shutdown(),null)}};async function createKernel(input={}){let{opts={}}=input,fetcher=opts.fetch??globalThis.fetch.bind(globalThis),targets=resolveTargets();log.info(`collector: %s`,targets.collectorBaseUrl),getGlobal(`__INTERFERE_FORCE_ENABLE__`)&&log.warn(`FORCE_ENABLE is active. Events will be dropped by production collectors. This should only be set during local development.`);let tracker=new SessionTracker({target:targets.session,fetcher});opts._wrapperVersions?.length&&(globalThis.__INTERFERE_SDK_STACK__=opts._wrapperVersions);let kernel=new Kernel(tracker),runtime=new PluginRuntime(kernel,opts.plugins,opts.consent);if(kernel.attachRuntime(runtime),tracker.start(),runtime.start(),opts.tracing!==!1){let tracingOpts=typeof opts.tracing==`object`?opts.tracing:void 0;await wireOtel(kernel,targets,opts._wrapperVersions,{...tracingOpts??{},...opts.serviceName?{serviceName:opts.serviceName}:{},...opts._internalAdditionalSpanProcessors?{additionalSpanProcessors:opts._internalAdditionalSpanProcessors}:{},...opts._internalAdditionalLogRecordProcessors?{additionalLogRecordProcessors:opts._internalAdditionalLogRecordProcessors}:{},...opts._internalAdditionalMetricReaders?{additionalMetricReaders:opts._internalAdditionalMetricReaders}:{}})}return await fetchRemoteConfig(targets.config,fetcher,runtime),registerServiceWorker({onMessage:msg=>recordSwMessage(kernel,msg)}).catch(()=>{}),kernel}function recordSwMessage(kernel,msg){kernel.recordLog({severityText:`info`,severityNumber:9,body:msg.type,attributes:{"url.full":msg.url,...msg.reason?{"interfere.sw.reason":msg.reason}:{}}})}async function wireOtel(kernel,targets,wrapperVersions,tracing){let{buildOtelProvider,captureWebVitals,readPropagationFromDocument,registerBundledInstrumentations}=await import(`./otel/index.mjs`),collectorUrl=targets.collectorBaseUrl.replace(TRAILING_SLASH_RE,``),releaseSlug=readReleaseSlugFromGlobals(),handle=buildOtelProvider({collectorUrl,authHeaders:targets.ingest.headers,sdkStack:buildSdkStack(wrapperVersions),getSessionId:()=>kernel.session.getId(),deviceId:kernel.device.getDeviceId(),releaseSlug,...tracing.serviceName?{serviceName:tracing.serviceName}:{},...tracing.additionalSpanProcessors?{additionalSpanProcessors:tracing.additionalSpanProcessors}:{},...tracing.additionalLogRecordProcessors?{additionalLogRecordProcessors:tracing.additionalLogRecordProcessors}:{},...tracing.additionalMetricReaders?{additionalMetricReaders:tracing.additionalMetricReaders}:{}}),extracted=readPropagationFromDocument();extracted&&handle.contextManager.setPageScope(extracted);let dispose=registerBundledInstrumentations({tracerProvider:handle.tracerProvider,ignoreUrls:[`${collectorUrl}/v2/sink`,targets.config.url,targets.session.url],...tracing.propagateContextUrls?{propagateContextUrls:tracing.propagateContextUrls}:{},...tracing.resolveRoute?{resolveRoute:tracing.resolveRoute}:{}});tracing.webVitals!==!1&&captureWebVitals({meter:handle.meterProvider.getMeter(`@interfere/react/web-vitals`),flush:()=>handle.flush(),...tracing.resolveRoute?{resolveRoute:tracing.resolveRoute}:{}}),kernel.attachOtel(handle,dispose)}async function fetchRemoteConfig(configTarget,fetcher,runtime){try{let headers=Object.fromEntries(configTarget.headers.entries()),res=await fetcher(configTarget.url,{method:`GET`,headers,signal:AbortSignal.timeout(1e4)});if(!res.ok)return;let config=await res.json();config?.plugins&&(runtime.applyRemoteConfig(config.plugins),log.debug(`applied remote config`))}catch{log.warn(`remote config fetch failed, using local defaults`)}}function isEnabledByEnvironment(){if(typeof process>`u`||!process.env)return!0;let nodeEnv=process.env.NODE_ENV;return nodeEnv===`production`||nodeEnv===void 0?!0:!!(getGlobal(`__INTERFERE_FORCE_ENABLE__`)||process.env.INTERFERE_FORCE_ENABLE)}function readReleaseSlugFromGlobals(){let value=getGlobal(`__INTERFERE_RELEASE_SLUG__`);return typeof value==`string`?releaseSlugSchema.parse(value):null}export{Kernel,buildSdkStack,createKernel,isEnabledByEnvironment,readReleaseSlugFromGlobals};
|