@interfere/react 10.0.0 → 10.0.1-canary.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.
Files changed (121) hide show
  1. package/dist/api.d.mts.map +1 -1
  2. package/dist/api.mjs +1 -68
  3. package/dist/api.mjs.map +1 -1
  4. package/dist/error-boundary.d.mts +1 -2
  5. package/dist/error-boundary.d.mts.map +1 -1
  6. package/dist/error-boundary.mjs +1 -42
  7. package/dist/error-boundary.mjs.map +1 -1
  8. package/dist/internal/browser-context.d.mts.map +1 -1
  9. package/dist/internal/browser-context.mjs +1 -59
  10. package/dist/internal/browser-context.mjs.map +1 -1
  11. package/dist/internal/capture-boundary.d.mts +1 -2
  12. package/dist/internal/capture-boundary.d.mts.map +1 -1
  13. package/dist/internal/capture-boundary.mjs +1 -48
  14. package/dist/internal/capture-boundary.mjs.map +1 -1
  15. package/dist/internal/capture.mjs +1 -27
  16. package/dist/internal/capture.mjs.map +1 -1
  17. package/dist/internal/config.d.mts +3 -1
  18. package/dist/internal/config.d.mts.map +1 -1
  19. package/dist/internal/config.mjs +1 -33
  20. package/dist/internal/config.mjs.map +1 -1
  21. package/dist/internal/consent.d.mts.map +1 -1
  22. package/dist/internal/consent.mjs +1 -27
  23. package/dist/internal/consent.mjs.map +1 -1
  24. package/dist/internal/console-patch.d.mts.map +1 -1
  25. package/dist/internal/console-patch.mjs +1 -62
  26. package/dist/internal/console-patch.mjs.map +1 -1
  27. package/dist/internal/dom/actionable.d.mts.map +1 -1
  28. package/dist/internal/dom/actionable.mjs +1 -62
  29. package/dist/internal/dom/actionable.mjs.map +1 -1
  30. package/dist/internal/kernel-registry.d.mts.map +1 -1
  31. package/dist/internal/kernel-registry.mjs +1 -31
  32. package/dist/internal/kernel-registry.mjs.map +1 -1
  33. package/dist/internal/kernel.d.mts.map +1 -1
  34. package/dist/internal/kernel.mjs +1 -322
  35. package/dist/internal/kernel.mjs.map +1 -1
  36. package/dist/internal/otel/exporter.d.mts +4 -12
  37. package/dist/internal/otel/exporter.d.mts.map +1 -1
  38. package/dist/internal/otel/exporter.mjs +1 -212
  39. package/dist/internal/otel/exporter.mjs.map +1 -1
  40. package/dist/internal/otel/index.mjs +1 -6
  41. package/dist/internal/otel/instrumentations.d.mts.map +1 -1
  42. package/dist/internal/otel/instrumentations.mjs +1 -150
  43. package/dist/internal/otel/instrumentations.mjs.map +1 -1
  44. package/dist/internal/otel/page-scope-context-manager.d.mts.map +1 -1
  45. package/dist/internal/otel/page-scope-context-manager.mjs +1 -36
  46. package/dist/internal/otel/page-scope-context-manager.mjs.map +1 -1
  47. package/dist/internal/otel/propagation.d.mts.map +1 -1
  48. package/dist/internal/otel/propagation.mjs +1 -40
  49. package/dist/internal/otel/propagation.mjs.map +1 -1
  50. package/dist/internal/otel/provider.d.mts +1 -2
  51. package/dist/internal/otel/provider.d.mts.map +1 -1
  52. package/dist/internal/otel/provider.mjs +1 -151
  53. package/dist/internal/otel/provider.mjs.map +1 -1
  54. package/dist/internal/otel/web-vitals.d.mts.map +1 -1
  55. package/dist/internal/otel/web-vitals.mjs +1 -162
  56. package/dist/internal/otel/web-vitals.mjs.map +1 -1
  57. package/dist/internal/page-lifecycle.d.mts.map +1 -1
  58. package/dist/internal/page-lifecycle.mjs +1 -33
  59. package/dist/internal/page-lifecycle.mjs.map +1 -1
  60. package/dist/internal/plugin-runtime.mjs +1 -101
  61. package/dist/internal/plugin-runtime.mjs.map +1 -1
  62. package/dist/internal/react-context.d.mts +1 -2
  63. package/dist/internal/react-context.d.mts.map +1 -1
  64. package/dist/internal/react-context.mjs +1 -34
  65. package/dist/internal/react-context.mjs.map +1 -1
  66. package/dist/internal/sw.d.mts.map +1 -1
  67. package/dist/internal/sw.mjs +1 -37
  68. package/dist/internal/sw.mjs.map +1 -1
  69. package/dist/internal/version.mjs +1 -7
  70. package/dist/internal/version.mjs.map +1 -1
  71. package/dist/internal/wrapper-singleton.d.mts.map +1 -1
  72. package/dist/internal/wrapper-singleton.mjs +1 -73
  73. package/dist/internal/wrapper-singleton.mjs.map +1 -1
  74. package/dist/package.mjs +1 -5
  75. package/dist/plugins/errors.d.mts.map +1 -1
  76. package/dist/plugins/errors.mjs +1 -84
  77. package/dist/plugins/errors.mjs.map +1 -1
  78. package/dist/plugins/lib/loader.mjs +1 -34
  79. package/dist/plugins/lib/loader.mjs.map +1 -1
  80. package/dist/plugins/lib/types.d.mts.map +1 -1
  81. package/dist/plugins/lib/types.mjs +1 -1
  82. package/dist/plugins/logs.d.mts.map +1 -1
  83. package/dist/plugins/logs.mjs +1 -53
  84. package/dist/plugins/logs.mjs.map +1 -1
  85. package/dist/plugins/rage-clicks.d.mts.map +1 -1
  86. package/dist/plugins/rage-clicks.mjs +1 -55
  87. package/dist/plugins/rage-clicks.mjs.map +1 -1
  88. package/dist/plugins/replay.d.mts.map +1 -1
  89. package/dist/plugins/replay.mjs +1 -101
  90. package/dist/plugins/replay.mjs.map +1 -1
  91. package/dist/provider.d.mts.map +1 -1
  92. package/dist/provider.mjs +1 -31
  93. package/dist/provider.mjs.map +1 -1
  94. package/dist/react-error-handler.d.mts.map +1 -1
  95. package/dist/react-error-handler.mjs +1 -62
  96. package/dist/react-error-handler.mjs.map +1 -1
  97. package/dist/tracking/api.d.mts.map +1 -1
  98. package/dist/tracking/api.mjs +1 -152
  99. package/dist/tracking/api.mjs.map +1 -1
  100. package/dist/tracking/device.d.mts.map +1 -1
  101. package/dist/tracking/device.mjs +1 -104
  102. package/dist/tracking/device.mjs.map +1 -1
  103. package/dist/tracking/geo.d.mts.map +1 -1
  104. package/dist/tracking/geo.mjs +2 -48
  105. package/dist/tracking/geo.mjs.map +1 -1
  106. package/dist/tracking/session.d.mts.map +1 -1
  107. package/dist/tracking/session.mjs +1 -75
  108. package/dist/tracking/session.mjs.map +1 -1
  109. package/dist/util/bot.d.mts.map +1 -1
  110. package/dist/util/bot.mjs +1 -14
  111. package/dist/util/bot.mjs.map +1 -1
  112. package/dist/util/global.d.mts.map +1 -1
  113. package/dist/util/global.mjs +1 -12
  114. package/dist/util/global.mjs.map +1 -1
  115. package/dist/util/log.d.mts.map +1 -1
  116. package/dist/util/log.mjs +1 -44
  117. package/dist/util/log.mjs.map +1 -1
  118. package/dist/util/stringify.d.mts.map +1 -1
  119. package/dist/util/stringify.mjs +1 -16
  120. package/dist/util/stringify.mjs.map +1 -1
  121. package/package.json +34 -33
@@ -1,62 +1 @@
1
- //#region src/internal/console-patch.ts
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":";;;;;;;;;;;AAUA,MAAa,iBAAiB;CAC5B;CACA;CACA;CACA;CACA;CACD;AAMD,MAAM,2BAAW,IAAI,KAAqB;AAC1C,MAAM,YACJ,EAAE;AACJ,IAAI,YAAY;AAEhB,SAAS,UAAgB;CACvB,IAAI,WACF;CAEF,YAAY;CACZ,MAAM,SAAS,WAAW;CAI1B,KAAK,MAAM,SAAS,gBAAgB;EAClC,MAAM,OAAO,OAAO;EACpB,IAAI,OAAO,SAAS,YAClB;EAEF,UAAU,SAAS;EACnB,OAAO,UAAU,GAAG,SAAoB;GACtC,KAAK,MAAM,WAAW,SAAS,KAAK;GACpC,KAAK,MAAM,WAAW,UACpB,QAAQ,OAAO,KAAK;;;;AAM5B,SAAS,YAAkB;CACzB,IAAI,CAAC,WACH;CAEF,YAAY;CACZ,MAAM,SAAS,WAAW;CAI1B,KAAK,MAAM,SAAS,gBAAgB;EAClC,MAAM,OAAO,UAAU;EACvB,IAAI,MAAM;GACR,OAAO,SAAS;GAChB,OAAO,UAAU;;;;AAKvB,SAAgB,cAAc,SAAqC;CACjE,SAAS;CACT,SAAS,IAAI,QAAQ;CACrB,aAAa;EACX,SAAS,OAAO,QAAQ;EACxB,IAAI,SAAS,SAAS,GACpB,WAAW;;;;AAMjB,SAAgB,6BAAmC;CACjD,SAAS,OAAO;CAChB,WAAW"}
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;;;;;AAWA;;;;;iBAXgB,YAAA,CAAa,EAAA,EAAI,OAAA;AAAA,iBAWjB,iBAAA,CAAkB,MAAA,EAAQ,WAAA,UAAqB,OAAA;AAAA,UAc9C,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,oBAAA"}
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
- //#region src/internal/dom/actionable.ts
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":";;;;;;;;;;;;AAYA,MAAM,kBAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAM,mBAAmB,IAAI,IAAI;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAM,gBAAgB;AAEtB,SAAgB,aAAa,IAAsB;CACjD,IAAI,gBAAgB,IAAI,GAAG,QAAQ,aAAa,CAAC,EAC/C,OAAO;CAET,MAAM,OAAO,GAAG,aAAa,OAAO;CACpC,IAAI,QAAQ,iBAAiB,IAAI,KAAK,EACpC,OAAO;CAET,OAAO,GAAG,aAAa,mBAAmB;;AAG5C,SAAgB,kBAAkB,QAA4C;CAC5E,IAAI,EAAE,kBAAkB,UACtB,OAAO;CAET,IAAI,KAAqB;CACzB,OAAO,IAAI;EACT,IAAI,aAAa,GAAG,EAClB,OAAO;EAET,KAAK,GAAG;;CAEV,OAAO;;AAcT,SAAgB,mBAAmB,IAAmC;CACpE,MAAM,OAA6B,EACjC,KAAK,GAAG,QAAQ,aAAa,EAC9B;CACD,IAAI,GAAG,IACL,KAAK,KAAK,GAAG;CAEf,MAAM,OAAO,GAAG,aAAa,OAAO;CACpC,IAAI,MACF,KAAK,OAAO;CAEd,MAAM,OAAO,GAAG,aAAa,OAAO;CACpC,IAAI,MACF,KAAK,OAAO;CAEd,MAAM,YAAY,GAAG,aAAa,aAAa;CAC/C,IAAI,WACF,KAAK,YAAY;CAEnB,IAAI,cAAc,mBAChB,KAAK,OAAO,GAAG;CAEjB,MAAM,OAAO,GAAG,aAAa,MAAM,CAAC,MAAM,GAAG,cAAc;CAC3D,IAAI,MACF,KAAK,OAAO;CAEd,OAAO"}
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,MAAA,EAAQ,MAAA;AAAA,iBASvB,gBAAA,CAAiB,MAAA,EAAQ,MAAA;AAAA,iBAMzB,YAAA,CAAA,GAAgB,MAAA"}
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 { createLogger } from "../util/log.mjs";
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":";;AAGA,MAAM,MAAM,aAAa,kBAAkB;;;;;;;;;;;;;;;;AAiB3C,IAAI,SAAwB;AAE5B,SAAgB,eAAe,QAAsB;CACnD,IAAI,UAAU,WAAW,QACvB,IAAI,KACF,sMACD;CAEH,SAAS;;AAGX,SAAgB,iBAAiB,QAAsB;CACrD,IAAI,WAAW,QACb,SAAS;;AAIb,SAAgB,eAA8B;CAC5C,OAAO"}
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"}
@@ -1 +1 @@
1
- {"version":3,"file":"kernel.d.mts","names":[],"sources":["../../src/internal/kernel.ts"],"mappings":";;;;;;;;;;;;;;;;;iBAyCgB,aAAA,CAAc,eAAA;AAAA,UAyDb,aAAA;EACf,OAAA,GAAU,YAAA;EA1DI;;;;;AAyDhB;;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;;;;;AAgBF;EA/BE,UAAA,aAAuB,MAAA;;;;;;;EAOvB,oBAAA,aAAiC,MAAA;EAwBY;;;;;;;EAhB7C,YAAA,IAAgB,QAAA;EAuCA;;AAGlB;;;EApCE,SAAA;AAAA;;;;;;;AAyCF;UA/BiB,qBAAA,SAA8B,aAAA;;;;;;;;EAQ7C,sCAAA,GAAyC,kBAAA;EAyBzC;;;;;;AAGF;EApBE,gCAAA,GAAmC,YAAA;;;;AAyBrC;EApBE,iCAAA,GAAoC,aAAA;;EAEpC,gBAAA;AAAA;AAAA,UAGe,aAAA;EACf,GAAA,IAAO,YAAA;EACP,GAAA,CAAI,KAAA,GAAQ,YAAA;AAAA;AAAA,UAGG,cAAA;EACf,GAAA,IAAO,cAAA;EACP,GAAA,CAAI,MAAA,EAAQ,cAAA,GAAiB,OAAA;AAAA;AAAA,UAGd,YAAA;EACf,WAAA;EACA,SAAA;AAAA;AAAA,UAGe,aAAA;EACf,KAAA,IAAS,SAAA;EACT,WAAA;AAAA;AAAA,UAGe,mBAAA;EAuBN;;;;AAcX;;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;;;;;AAUV;;EAlFE,SAAA,CAAU,KAAA;IACR,YAAA;IACA,cAAA;IACA,IAAA;IACA,UAAA,GAAa,MAAA;EAAA;EA+ER;;;;;;AA6PT;EAxTQ,KAAA,CAAA,GAAS,OAAA;;;;AAiVjB;;;;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,qBAAA;AAAA;;;;;;;iBASa,YAAA,CACpB,KAAA,GAAO,iBAAA,GACN,OAAA,CAAQ,MAAA;AAAA,iBA4PK,sBAAA,CAAA;;;;;;;;;;;;iBAyBA,0BAAA,CAAA,GAA8B,WAAA"}
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"}
@@ -1,322 +1 @@
1
- import { createLogger } from "../util/log.mjs";
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};