@interfere/react 9.0.1 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +4 -4
  2. package/dist/api.d.mts +25 -0
  3. package/dist/api.d.mts.map +1 -0
  4. package/dist/api.mjs +68 -0
  5. package/dist/api.mjs.map +1 -0
  6. package/dist/error-boundary.d.mts +11 -4
  7. package/dist/error-boundary.d.mts.map +1 -1
  8. package/dist/error-boundary.mjs +6 -3
  9. package/dist/error-boundary.mjs.map +1 -1
  10. package/dist/internal/browser-context.d.mts +6 -0
  11. package/dist/internal/browser-context.d.mts.map +1 -0
  12. package/dist/internal/browser-context.mjs +59 -0
  13. package/dist/internal/browser-context.mjs.map +1 -0
  14. package/dist/internal/capture-boundary.d.mts +5 -1
  15. package/dist/internal/capture-boundary.d.mts.map +1 -1
  16. package/dist/internal/capture-boundary.mjs +9 -5
  17. package/dist/internal/capture-boundary.mjs.map +1 -1
  18. package/dist/internal/capture.d.mts +16 -5
  19. package/dist/internal/capture.d.mts.map +1 -1
  20. package/dist/internal/capture.mjs +20 -16
  21. package/dist/internal/capture.mjs.map +1 -1
  22. package/dist/internal/config.d.mts +20 -4
  23. package/dist/internal/config.d.mts.map +1 -1
  24. package/dist/internal/config.mjs +12 -12
  25. package/dist/internal/config.mjs.map +1 -1
  26. package/dist/internal/consent.d.mts.map +1 -1
  27. package/dist/internal/consent.mjs +3 -1
  28. package/dist/internal/consent.mjs.map +1 -1
  29. package/dist/internal/console-patch.d.mts +19 -0
  30. package/dist/internal/console-patch.d.mts.map +1 -0
  31. package/dist/internal/console-patch.mjs +62 -0
  32. package/dist/internal/console-patch.mjs.map +1 -0
  33. package/dist/internal/dom/actionable.d.mts +27 -0
  34. package/dist/internal/dom/actionable.d.mts.map +1 -0
  35. package/dist/internal/dom/actionable.mjs +62 -0
  36. package/dist/internal/dom/actionable.mjs.map +1 -0
  37. package/dist/internal/kernel-registry.d.mts +8 -0
  38. package/dist/internal/kernel-registry.d.mts.map +1 -0
  39. package/dist/internal/kernel-registry.mjs +31 -0
  40. package/dist/internal/kernel-registry.mjs.map +1 -0
  41. package/dist/internal/kernel.d.mts +267 -0
  42. package/dist/internal/kernel.d.mts.map +1 -0
  43. package/dist/internal/kernel.mjs +322 -0
  44. package/dist/internal/kernel.mjs.map +1 -0
  45. package/dist/internal/otel/exporter.d.mts +93 -0
  46. package/dist/internal/otel/exporter.d.mts.map +1 -0
  47. package/dist/internal/otel/exporter.mjs +212 -0
  48. package/dist/internal/otel/exporter.mjs.map +1 -0
  49. package/dist/internal/otel/index.d.mts +6 -0
  50. package/dist/internal/otel/index.mjs +6 -0
  51. package/dist/internal/otel/instrumentations.d.mts +42 -0
  52. package/dist/internal/otel/instrumentations.d.mts.map +1 -0
  53. package/dist/internal/otel/instrumentations.mjs +150 -0
  54. package/dist/internal/otel/instrumentations.mjs.map +1 -0
  55. package/dist/internal/otel/page-scope-context-manager.d.mts +32 -0
  56. package/dist/internal/otel/page-scope-context-manager.d.mts.map +1 -0
  57. package/dist/internal/otel/page-scope-context-manager.mjs +36 -0
  58. package/dist/internal/otel/page-scope-context-manager.mjs.map +1 -0
  59. package/dist/internal/otel/propagation.d.mts +21 -0
  60. package/dist/internal/otel/propagation.d.mts.map +1 -0
  61. package/dist/internal/otel/propagation.mjs +40 -0
  62. package/dist/internal/otel/propagation.mjs.map +1 -0
  63. package/dist/internal/otel/provider.d.mts +107 -0
  64. package/dist/internal/otel/provider.d.mts.map +1 -0
  65. package/dist/internal/otel/provider.mjs +151 -0
  66. package/dist/internal/otel/provider.mjs.map +1 -0
  67. package/dist/internal/otel/web-vitals.d.mts +35 -0
  68. package/dist/internal/otel/web-vitals.d.mts.map +1 -0
  69. package/dist/internal/otel/web-vitals.mjs +162 -0
  70. package/dist/internal/otel/web-vitals.mjs.map +1 -0
  71. package/dist/internal/page-lifecycle.d.mts +21 -0
  72. package/dist/internal/page-lifecycle.d.mts.map +1 -0
  73. package/dist/internal/page-lifecycle.mjs +33 -0
  74. package/dist/internal/page-lifecycle.mjs.map +1 -0
  75. package/dist/internal/plugin-runtime.d.mts +0 -2
  76. package/dist/internal/plugin-runtime.d.mts.map +1 -1
  77. package/dist/internal/plugin-runtime.mjs +1 -7
  78. package/dist/internal/plugin-runtime.mjs.map +1 -1
  79. package/dist/internal/react-context.d.mts +45 -0
  80. package/dist/internal/react-context.d.mts.map +1 -0
  81. package/dist/internal/react-context.mjs +34 -0
  82. package/dist/internal/react-context.mjs.map +1 -0
  83. package/dist/internal/sw.d.mts +22 -2
  84. package/dist/internal/sw.d.mts.map +1 -1
  85. package/dist/internal/sw.mjs +30 -3
  86. package/dist/internal/sw.mjs.map +1 -1
  87. package/dist/internal/version.d.mts +3 -1
  88. package/dist/internal/version.d.mts.map +1 -1
  89. package/dist/internal/version.mjs +4 -2
  90. package/dist/internal/version.mjs.map +1 -1
  91. package/dist/internal/wrapper-singleton.d.mts +47 -0
  92. package/dist/internal/wrapper-singleton.d.mts.map +1 -0
  93. package/dist/internal/wrapper-singleton.mjs +73 -0
  94. package/dist/internal/wrapper-singleton.mjs.map +1 -0
  95. package/dist/package.mjs +1 -1
  96. package/dist/plugins/errors.d.mts.map +1 -1
  97. package/dist/plugins/errors.mjs +18 -25
  98. package/dist/plugins/errors.mjs.map +1 -1
  99. package/dist/plugins/lib/loader.d.mts +1 -2
  100. package/dist/plugins/lib/loader.d.mts.map +1 -1
  101. package/dist/plugins/lib/loader.mjs +2 -11
  102. package/dist/plugins/lib/loader.mjs.map +1 -1
  103. package/dist/plugins/lib/types.d.mts +3 -2
  104. package/dist/plugins/lib/types.d.mts.map +1 -1
  105. package/dist/plugins/logs.d.mts +13 -0
  106. package/dist/plugins/logs.d.mts.map +1 -0
  107. package/dist/plugins/logs.mjs +53 -0
  108. package/dist/plugins/logs.mjs.map +1 -0
  109. package/dist/plugins/rage-clicks.d.mts.map +1 -1
  110. package/dist/plugins/rage-clicks.mjs +12 -10
  111. package/dist/plugins/rage-clicks.mjs.map +1 -1
  112. package/dist/plugins/replay.d.mts.map +1 -1
  113. package/dist/plugins/replay.mjs +58 -19
  114. package/dist/plugins/replay.mjs.map +1 -1
  115. package/dist/provider.d.mts +11 -20
  116. package/dist/provider.d.mts.map +1 -1
  117. package/dist/provider.mjs +13 -14
  118. package/dist/provider.mjs.map +1 -1
  119. package/dist/react-error-handler.d.mts +21 -5
  120. package/dist/react-error-handler.d.mts.map +1 -1
  121. package/dist/react-error-handler.mjs +15 -7
  122. package/dist/react-error-handler.mjs.map +1 -1
  123. package/dist/sw.d.mts +2 -0
  124. package/dist/sw.mjs +2 -0
  125. package/dist/tracking/api.d.mts +41 -15
  126. package/dist/tracking/api.d.mts.map +1 -1
  127. package/dist/tracking/api.mjs +122 -104
  128. package/dist/tracking/api.mjs.map +1 -1
  129. package/dist/tracking/device.d.mts +30 -7
  130. package/dist/tracking/device.d.mts.map +1 -1
  131. package/dist/tracking/device.mjs +70 -46
  132. package/dist/tracking/device.mjs.map +1 -1
  133. package/dist/tracking/geo.d.mts +11 -3
  134. package/dist/tracking/geo.d.mts.map +1 -1
  135. package/dist/tracking/geo.mjs +33 -29
  136. package/dist/tracking/geo.mjs.map +1 -1
  137. package/dist/tracking/session.d.mts +3 -1
  138. package/dist/tracking/session.d.mts.map +1 -1
  139. package/dist/tracking/session.mjs.map +1 -1
  140. package/dist/util/bot.d.mts +10 -0
  141. package/dist/util/bot.d.mts.map +1 -0
  142. package/dist/util/bot.mjs +14 -0
  143. package/dist/util/bot.mjs.map +1 -0
  144. package/dist/util/global.d.mts +10 -0
  145. package/dist/util/global.d.mts.map +1 -0
  146. package/dist/util/global.mjs +12 -0
  147. package/dist/util/global.mjs.map +1 -0
  148. package/dist/util/log.d.mts.map +1 -1
  149. package/dist/util/log.mjs +8 -1
  150. package/dist/util/log.mjs.map +1 -1
  151. package/dist/util/stringify.d.mts +9 -0
  152. package/dist/util/stringify.d.mts.map +1 -0
  153. package/dist/util/stringify.mjs +16 -0
  154. package/dist/util/stringify.mjs.map +1 -0
  155. package/package.json +73 -20
  156. package/dist/internal/client.d.mts +0 -48
  157. package/dist/internal/client.d.mts.map +0 -1
  158. package/dist/internal/client.mjs +0 -146
  159. package/dist/internal/client.mjs.map +0 -1
  160. package/dist/internal/context.d.mts +0 -6
  161. package/dist/internal/context.d.mts.map +0 -1
  162. package/dist/internal/context.mjs +0 -32
  163. package/dist/internal/context.mjs.map +0 -1
  164. package/dist/internal/envelope.d.mts +0 -15
  165. package/dist/internal/envelope.d.mts.map +0 -1
  166. package/dist/internal/envelope.mjs +0 -24
  167. package/dist/internal/envelope.mjs.map +0 -1
  168. package/dist/internal/errors.d.mts +0 -4
  169. package/dist/internal/errors.d.mts.map +0 -1
  170. package/dist/internal/errors.mjs +0 -4
  171. package/dist/internal/errors.mjs.map +0 -1
  172. package/dist/plugins/device.d.mts +0 -6
  173. package/dist/plugins/device.d.mts.map +0 -1
  174. package/dist/plugins/device.mjs +0 -13
  175. package/dist/plugins/device.mjs.map +0 -1
  176. package/dist/plugins/pages.d.mts +0 -6
  177. package/dist/plugins/pages.d.mts.map +0 -1
  178. package/dist/plugins/pages.mjs +0 -102
  179. package/dist/plugins/pages.mjs.map +0 -1
  180. package/dist/transport/http.d.mts +0 -21
  181. package/dist/transport/http.d.mts.map +0 -1
  182. package/dist/transport/http.mjs +0 -72
  183. package/dist/transport/http.mjs.map +0 -1
  184. package/dist/transport/queue.d.mts +0 -34
  185. package/dist/transport/queue.d.mts.map +0 -1
  186. package/dist/transport/queue.mjs +0 -95
  187. package/dist/transport/queue.mjs.map +0 -1
package/README.md CHANGED
@@ -2,16 +2,16 @@
2
2
  <a href="https://interfere.com">
3
3
  <picture>
4
4
  <source media="(prefers-color-scheme: dark)" srcset="https://qyzkf4cgb8ydxtq1.public.blob.vercel-storage.com/v2/header/logo-dark.png">
5
- <img src="https://qyzkf4cgb8ydxtq1.public.blob.vercel-storage.com/v2/header/logo-light.png" height="64">
5
+ <img src="https://qyzkf4cgb8ydxtq1.public.blob.vercel-storage.com/v2/header/logo-light.png" height="64" alt="Interfere">
6
6
  </picture>
7
7
  </a>
8
8
  <h1 align="center">@interfere/react</h1>
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <a href="https://www.npmjs.com/package/@interfere/react"><img src="https://img.shields.io/npm/v/@interfere/react.svg" /></a>
13
- <a href="https://github.com/interfere-inc/interfere/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@interfere/react.svg" /></a>
14
- <a href="https://www.npmjs.com/package/@interfere/react"><img src="https://img.shields.io/npm/dm/@interfere/react.svg" /></a>
12
+ <a href="https://www.npmjs.com/package/@interfere/react"><img src="https://img.shields.io/npm/v/@interfere/react.svg" alt="npm version" /></a>
13
+ <a href="https://github.com/interfere-inc/interfere/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@interfere/react.svg" alt="License" /></a>
14
+ <a href="https://www.npmjs.com/package/@interfere/react"><img src="https://img.shields.io/npm/dm/@interfere/react.svg" alt="npm downloads" /></a>
15
15
  </p>
16
16
 
17
17
  <p align="center">
package/dist/api.d.mts ADDED
@@ -0,0 +1,25 @@
1
+ import { Span, SpanOptions } from "@opentelemetry/api";
2
+
3
+ //#region src/api.d.ts
4
+ /**
5
+ * Wraps `fn` in an OTel span. The span becomes the active context for the
6
+ * duration of the call (sync) or the awaited promise (async). Errors thrown
7
+ * by `fn` are recorded on the span before re-throwing — caller still owns
8
+ * the error.
9
+ *
10
+ * Works pre-init: `trace.getTracer()` returns a no-op tracer that still
11
+ * runs `fn` and propagates its return value, so call sites don't need
12
+ * defensive checks.
13
+ */
14
+ declare function span<T>(name: string, fn: (span: Span) => T, options?: SpanOptions): T;
15
+ /**
16
+ * Captures an error. Records on the active OTel span (when present) AND on
17
+ * the legacy envelope path. Accepts any thrown value; non-Error inputs that
18
+ * carry structure (objects without a recoverable nested Error) are preserved
19
+ * via `toException` so the original shape survives to the wire.
20
+ *
21
+ * Safe to call before init — drops silently if no kernel is registered.
22
+ */
23
+ declare function capture(error: unknown): void;
24
+ //#endregion
25
+ export { capture, span };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.mts","names":[],"sources":["../src/api.ts"],"mappings":";;;;;AAuCA;;;;;;;;iBAAgB,IAAA,GAAA,CACd,IAAA,UACA,EAAA,GAAK,IAAA,EAAM,IAAA,KAAS,CAAA,EACpB,OAAA,GAAU,WAAA,GACT,CAAA;;;;;;;;;iBAuCa,OAAA,CAAQ,KAAA"}
package/dist/api.mjs ADDED
@@ -0,0 +1,68 @@
1
+ import { activeKernel } from "./internal/kernel-registry.mjs";
2
+ import { MECHANISM_TYPE, toError, toException } from "@interfere/types/sdk/errors";
3
+ import { SpanStatusCode, trace } from "@opentelemetry/api";
4
+ //#region src/api.ts
5
+ const TRACER_NAME = "@interfere/react";
6
+ function tracer() {
7
+ return trace.getTracer(TRACER_NAME);
8
+ }
9
+ function isPromise(value) {
10
+ return !!value && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
11
+ }
12
+ /**
13
+ * Wraps `fn` in an OTel span. The span becomes the active context for the
14
+ * duration of the call (sync) or the awaited promise (async). Errors thrown
15
+ * by `fn` are recorded on the span before re-throwing — caller still owns
16
+ * the error.
17
+ *
18
+ * Works pre-init: `trace.getTracer()` returns a no-op tracer that still
19
+ * runs `fn` and propagates its return value, so call sites don't need
20
+ * defensive checks.
21
+ */
22
+ function span(name, fn, options) {
23
+ return tracer().startActiveSpan(name, options ?? {}, (s) => {
24
+ try {
25
+ const result = fn(s);
26
+ if (isPromise(result)) return result.then((value) => {
27
+ s.end();
28
+ return value;
29
+ }, (err) => {
30
+ const e = toError(err);
31
+ s.recordException(e);
32
+ s.setStatus({
33
+ code: SpanStatusCode.ERROR,
34
+ message: e.message
35
+ });
36
+ s.end();
37
+ throw err;
38
+ });
39
+ s.end();
40
+ return result;
41
+ } catch (err) {
42
+ const e = toError(err);
43
+ s.recordException(e);
44
+ s.setStatus({
45
+ code: SpanStatusCode.ERROR,
46
+ message: e.message
47
+ });
48
+ s.end();
49
+ throw err;
50
+ }
51
+ });
52
+ }
53
+ /**
54
+ * Captures an error. Records on the active OTel span (when present) AND on
55
+ * the legacy envelope path. Accepts any thrown value; non-Error inputs that
56
+ * carry structure (objects without a recoverable nested Error) are preserved
57
+ * via `toException` so the original shape survives to the wire.
58
+ *
59
+ * Safe to call before init — drops silently if no kernel is registered.
60
+ */
61
+ function capture(error) {
62
+ activeKernel()?.recordException(toException(error), { mechanism: {
63
+ type: MECHANISM_TYPE.manual.capture,
64
+ handled: true
65
+ } });
66
+ }
67
+ //#endregion
68
+ export { capture, span };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.mjs","names":[],"sources":["../src/api.ts"],"sourcesContent":["import {\n MECHANISM_TYPE,\n toError,\n toException,\n} from \"@interfere/types/sdk/errors\";\n\nimport {\n type Span,\n type SpanOptions,\n SpanStatusCode,\n trace,\n} from \"@opentelemetry/api\";\n\nimport { activeKernel } from \"./internal/kernel-registry.js\";\n\nconst TRACER_NAME = \"@interfere/react\";\n\nfunction tracer() {\n return trace.getTracer(TRACER_NAME);\n}\n\nfunction isPromise<T>(value: unknown): value is Promise<T> {\n return (\n !!value &&\n (typeof value === \"object\" || typeof value === \"function\") &&\n typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\n/**\n * Wraps `fn` in an OTel span. The span becomes the active context for the\n * duration of the call (sync) or the awaited promise (async). Errors thrown\n * by `fn` are recorded on the span before re-throwing — caller still owns\n * the error.\n *\n * Works pre-init: `trace.getTracer()` returns a no-op tracer that still\n * runs `fn` and propagates its return value, so call sites don't need\n * defensive checks.\n */\nexport function span<T>(\n name: string,\n fn: (span: Span) => T,\n options?: SpanOptions\n): T {\n return tracer().startActiveSpan(name, options ?? {}, (s): T => {\n try {\n const result = fn(s);\n if (isPromise<T>(result)) {\n return result.then(\n (value) => {\n s.end();\n return value;\n },\n (err: unknown) => {\n const e = toError(err);\n s.recordException(e);\n s.setStatus({ code: SpanStatusCode.ERROR, message: e.message });\n s.end();\n throw err;\n }\n ) as T;\n }\n s.end();\n return result;\n } catch (err) {\n const e = toError(err);\n s.recordException(e);\n s.setStatus({ code: SpanStatusCode.ERROR, message: e.message });\n s.end();\n throw err;\n }\n });\n}\n\n/**\n * Captures an error. Records on the active OTel span (when present) AND on\n * the legacy envelope path. Accepts any thrown value; non-Error inputs that\n * carry structure (objects without a recoverable nested Error) are preserved\n * via `toException` so the original shape survives to the wire.\n *\n * Safe to call before init — drops silently if no kernel is registered.\n */\nexport function capture(error: unknown): void {\n activeKernel()?.recordException(toException(error), {\n mechanism: { type: MECHANISM_TYPE.manual.capture, handled: true },\n });\n}\n"],"mappings":";;;;AAeA,MAAM,cAAc;AAEpB,SAAS,SAAS;CAChB,OAAO,MAAM,UAAU,YAAY;;AAGrC,SAAS,UAAa,OAAqC;CACzD,OACE,CAAC,CAAC,UACD,OAAO,UAAU,YAAY,OAAO,UAAU,eAC/C,OAAQ,MAA6B,SAAS;;;;;;;;;;;;AAclD,SAAgB,KACd,MACA,IACA,SACG;CACH,OAAO,QAAQ,CAAC,gBAAgB,MAAM,WAAW,EAAE,GAAG,MAAS;EAC7D,IAAI;GACF,MAAM,SAAS,GAAG,EAAE;GACpB,IAAI,UAAa,OAAO,EACtB,OAAO,OAAO,MACX,UAAU;IACT,EAAE,KAAK;IACP,OAAO;OAER,QAAiB;IAChB,MAAM,IAAI,QAAQ,IAAI;IACtB,EAAE,gBAAgB,EAAE;IACpB,EAAE,UAAU;KAAE,MAAM,eAAe;KAAO,SAAS,EAAE;KAAS,CAAC;IAC/D,EAAE,KAAK;IACP,MAAM;KAET;GAEH,EAAE,KAAK;GACP,OAAO;WACA,KAAK;GACZ,MAAM,IAAI,QAAQ,IAAI;GACtB,EAAE,gBAAgB,EAAE;GACpB,EAAE,UAAU;IAAE,MAAM,eAAe;IAAO,SAAS,EAAE;IAAS,CAAC;GAC/D,EAAE,KAAK;GACP,MAAM;;GAER;;;;;;;;;;AAWJ,SAAgB,QAAQ,OAAsB;CAC5C,cAAc,EAAE,gBAAgB,YAAY,MAAM,EAAE,EAClD,WAAW;EAAE,MAAM,eAAe,OAAO;EAAS,SAAS;EAAM,EAClE,CAAC"}
@@ -1,4 +1,6 @@
1
- import { Component, ErrorInfo, ReactNode } from "react";
1
+ import { InterfereContext, InterfereContextValue } from "./internal/react-context.mjs";
2
+ import * as _$react from "react";
3
+ import { Component, ContextType, ErrorInfo, ReactNode } from "react";
2
4
 
3
5
  //#region src/error-boundary.d.ts
4
6
  interface ErrorBoundaryProps {
@@ -11,15 +13,20 @@ interface ErrorBoundaryState {
11
13
  }
12
14
  /**
13
15
  * Catches render-phase React errors, reports them to the SDK, and renders a
14
- * fallback. Requires the SDK to be bootstrapped via `init()` before React
15
- * renders so `capture` has an active runtime.
16
+ * fallback. Reads the kernel from `InterfereContext`; mounting outside the
17
+ * provider is supported — capture is silently skipped, fallback + `onError`
18
+ * still fire.
16
19
  *
17
20
  * Class component required — React has no hook-based error boundary API.
18
21
  */
19
22
  declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
23
+ static contextType: _$react.Context<InterfereContextValue | null>;
24
+ context: ContextType<typeof InterfereContext>;
20
25
  state: ErrorBoundaryState;
21
26
  static getDerivedStateFromError(error: Error): ErrorBoundaryState;
22
- componentDidCatch(error: Error, info: ErrorInfo): void;
27
+ componentDidCatch(error: Error & {
28
+ digest?: string;
29
+ }, info: ErrorInfo): void;
23
30
  private readonly reset;
24
31
  render(): ReactNode;
25
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"error-boundary.d.mts","names":[],"sources":["../src/error-boundary.tsx"],"mappings":";;;UAQiB,kBAAA;EACf,QAAA,EAAU,SAAA;EACV,QAAA,GAAW,SAAA,KAAc,KAAA,EAAO,KAAA,EAAO,KAAA,iBAAsB,SAAA;EAC7D,OAAA,IAAW,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,SAAA;AAAA;AAAA,UAGvB,kBAAA;EACR,KAAA,EAAO,KAAA;AAAA;;;;;;;;cAUI,aAAA,SAAsB,SAAA,CACjC,kBAAA,EACA,kBAAA;EAES,KAAA,EAAO,kBAAA;EAAA,OAET,wBAAA,CAAyB,KAAA,EAAO,KAAA,GAAQ,kBAAA;EAItC,iBAAA,CAAkB,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,SAAA;EAAA,iBAS9B,KAAA;EAIR,MAAA,CAAA,GAAM,SAAA;AAAA"}
1
+ {"version":3,"file":"error-boundary.d.mts","names":[],"sources":["../src/error-boundary.tsx"],"mappings":";;;;;UAciB,kBAAA;EACf,QAAA,EAAU,SAAA;EACV,QAAA,GAAW,SAAA,KAAc,KAAA,EAAO,KAAA,EAAO,KAAA,iBAAsB,SAAA;EAC7D,OAAA,IAAW,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,SAAA;AAAA;AAAA,UAGvB,kBAAA;EACR,KAAA,EAAO,KAAA;AAAA;;;;;;;;;cAWI,aAAA,SAAsB,SAAA,CACjC,kBAAA,EACA,kBAAA;EAAA,OAEgB,WAAA,EAAW,OAAA,CAAA,OAAA,CAFT,qBAAA;EAGV,OAAA,EAAS,WAAA,QAAmB,gBAAA;EAE3B,KAAA,EAAO,kBAAA;EAAA,OAET,wBAAA,CAAyB,KAAA,EAAO,KAAA,GAAQ,kBAAA;EAItC,iBAAA,CACP,KAAA,EAAO,KAAA;IAAU,MAAA;EAAA,GACjB,IAAA,EAAM,SAAA;EAAA,iBAeS,KAAA;EAIR,MAAA,CAAA,GAAM,SAAA;AAAA"}
@@ -1,22 +1,25 @@
1
1
  "use client";
2
2
  import { captureReactError } from "./internal/capture.mjs";
3
+ import { InterfereContext } from "./internal/react-context.mjs";
3
4
  import { MECHANISM_TYPE } from "@interfere/types/sdk/errors";
4
5
  import { Component } from "react";
5
6
  //#region src/error-boundary.tsx
6
7
  /**
7
8
  * Catches render-phase React errors, reports them to the SDK, and renders a
8
- * fallback. Requires the SDK to be bootstrapped via `init()` before React
9
- * renders so `capture` has an active runtime.
9
+ * fallback. Reads the kernel from `InterfereContext`; mounting outside the
10
+ * provider is supported — capture is silently skipped, fallback + `onError`
11
+ * still fire.
10
12
  *
11
13
  * Class component required — React has no hook-based error boundary API.
12
14
  */
13
15
  var ErrorBoundary = class extends Component {
16
+ static contextType = InterfereContext;
14
17
  state = { error: null };
15
18
  static getDerivedStateFromError(error) {
16
19
  return { error };
17
20
  }
18
21
  componentDidCatch(error, info) {
19
- captureReactError(error, info.componentStack, {
22
+ captureReactError(this.context?.kernel ?? null, error, info.componentStack, {
20
23
  type: MECHANISM_TYPE.react.errorBoundary,
21
24
  handled: true
22
25
  });
@@ -1 +1 @@
1
- {"version":3,"file":"error-boundary.mjs","names":[],"sources":["../src/error-boundary.tsx"],"sourcesContent":["\"use client\";\n\nimport { MECHANISM_TYPE } from \"@interfere/types/sdk/errors\";\n\nimport { Component, type ErrorInfo, type ReactNode } from \"react\";\n\nimport { captureReactError } from \"./internal/capture.js\";\n\nexport interface ErrorBoundaryProps {\n children: ReactNode;\n fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);\n onError?: (error: Error, info: ErrorInfo) => void;\n}\n\ninterface ErrorBoundaryState {\n error: Error | null;\n}\n\n/**\n * Catches render-phase React errors, reports them to the SDK, and renders a\n * fallback. Requires the SDK to be bootstrapped via `init()` before React\n * renders so `capture` has an active runtime.\n *\n * Class component required — React has no hook-based error boundary API.\n */\nexport class ErrorBoundary extends Component<\n ErrorBoundaryProps,\n ErrorBoundaryState\n> {\n override state: ErrorBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { error };\n }\n\n override componentDidCatch(error: Error, info: ErrorInfo) {\n captureReactError(error, info.componentStack, {\n type: MECHANISM_TYPE.react.errorBoundary,\n handled: true,\n });\n\n this.props.onError?.(error, info);\n }\n\n private readonly reset = () => {\n this.setState({ error: null });\n };\n\n override render() {\n const { error } = this.state;\n\n if (error) {\n const { fallback } = this.props;\n if (typeof fallback === \"function\") {\n return fallback(error, this.reset);\n }\n return fallback ?? null;\n }\n\n return this.props.children;\n }\n}\n"],"mappings":";;;;;;;;;;;;AAyBA,IAAa,gBAAb,cAAmC,UAGjC;CACA,QAAqC,EAAE,OAAO,MAAM;CAEpD,OAAO,yBAAyB,OAAkC;AAChE,SAAO,EAAE,OAAO;;CAGlB,kBAA2B,OAAc,MAAiB;AACxD,oBAAkB,OAAO,KAAK,gBAAgB;GAC5C,MAAM,eAAe,MAAM;GAC3B,SAAS;GACV,CAAC;AAEF,OAAK,MAAM,UAAU,OAAO,KAAK;;CAGnC,cAA+B;AAC7B,OAAK,SAAS,EAAE,OAAO,MAAM,CAAC;;CAGhC,SAAkB;EAChB,MAAM,EAAE,UAAU,KAAK;AAEvB,MAAI,OAAO;GACT,MAAM,EAAE,aAAa,KAAK;AAC1B,OAAI,OAAO,aAAa,WACtB,QAAO,SAAS,OAAO,KAAK,MAAM;AAEpC,UAAO,YAAY;;AAGrB,SAAO,KAAK,MAAM"}
1
+ {"version":3,"file":"error-boundary.mjs","names":[],"sources":["../src/error-boundary.tsx"],"sourcesContent":["\"use client\";\n\nimport { MECHANISM_TYPE } from \"@interfere/types/sdk/errors\";\n\nimport {\n Component,\n type ContextType,\n type ErrorInfo,\n type ReactNode,\n} from \"react\";\n\nimport { captureReactError } from \"./internal/capture.js\";\nimport { InterfereContext } from \"./internal/react-context.js\";\n\nexport interface ErrorBoundaryProps {\n children: ReactNode;\n fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);\n onError?: (error: Error, info: ErrorInfo) => void;\n}\n\ninterface ErrorBoundaryState {\n error: Error | null;\n}\n\n/**\n * Catches render-phase React errors, reports them to the SDK, and renders a\n * fallback. Reads the kernel from `InterfereContext`; mounting outside the\n * provider is supported — capture is silently skipped, fallback + `onError`\n * still fire.\n *\n * Class component required — React has no hook-based error boundary API.\n */\nexport class ErrorBoundary extends Component<\n ErrorBoundaryProps,\n ErrorBoundaryState\n> {\n static override contextType = InterfereContext;\n declare context: ContextType<typeof InterfereContext>;\n\n override state: ErrorBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { error };\n }\n\n override componentDidCatch(\n error: Error & { digest?: string },\n info: ErrorInfo\n ) {\n captureReactError(\n this.context?.kernel ?? null,\n error,\n info.componentStack,\n {\n type: MECHANISM_TYPE.react.errorBoundary,\n handled: true,\n }\n );\n\n this.props.onError?.(error, info);\n }\n\n private readonly reset = () => {\n this.setState({ error: null });\n };\n\n override render() {\n const { error } = this.state;\n\n if (error) {\n const { fallback } = this.props;\n if (typeof fallback === \"function\") {\n return fallback(error, this.reset);\n }\n return fallback ?? null;\n }\n\n return this.props.children;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAgCA,IAAa,gBAAb,cAAmC,UAGjC;CACA,OAAgB,cAAc;CAG9B,QAAqC,EAAE,OAAO,MAAM;CAEpD,OAAO,yBAAyB,OAAkC;EAChE,OAAO,EAAE,OAAO;;CAGlB,kBACE,OACA,MACA;EACA,kBACE,KAAK,SAAS,UAAU,MACxB,OACA,KAAK,gBACL;GACE,MAAM,eAAe,MAAM;GAC3B,SAAS;GACV,CACF;EAED,KAAK,MAAM,UAAU,OAAO,KAAK;;CAGnC,cAA+B;EAC7B,KAAK,SAAS,EAAE,OAAO,MAAM,CAAC;;CAGhC,SAAkB;EAChB,MAAM,EAAE,UAAU,KAAK;EAEvB,IAAI,OAAO;GACT,MAAM,EAAE,aAAa,KAAK;GAC1B,IAAI,OAAO,aAAa,YACtB,OAAO,SAAS,OAAO,KAAK,MAAM;GAEpC,OAAO,YAAY;;EAGrB,OAAO,KAAK,MAAM"}
@@ -0,0 +1,6 @@
1
+ import { BrowserContext } from "@interfere/types/sdk/plugins/context/browser";
2
+
3
+ //#region src/internal/browser-context.d.ts
4
+ declare function collectContext(): Promise<BrowserContext>;
5
+ //#endregion
6
+ export { collectContext };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-context.d.mts","names":[],"sources":["../../src/internal/browser-context.ts"],"mappings":";;;iBA0GsB,cAAA,CAAA,GAAkB,OAAA,CAAQ,cAAA"}
@@ -0,0 +1,59 @@
1
+ import { UAParser } from "@ua-parser-js/pro-enterprise";
2
+ import { InApps } from "@ua-parser-js/pro-enterprise/extensions";
3
+ import { isFrozenUA } from "@ua-parser-js/pro-enterprise/helpers";
4
+ //#region src/internal/browser-context.ts
5
+ const NOT_A_BRAND_REGEX = /not.a.brand/i;
6
+ const HIGH_ENTROPY_HINTS = [
7
+ "bitness",
8
+ "platformVersion",
9
+ "fullVersionList"
10
+ ];
11
+ async function getDeviceMetadata() {
12
+ if (typeof navigator === "undefined") return null;
13
+ const uaData = navigator.userAgentData;
14
+ const [parsed, hints] = await Promise.all([UAParser(navigator.userAgent, InApps).withClientHints(), uaData?.getHighEntropyValues ? uaData.getHighEntropyValues(HIGH_ENTROPY_HINTS).catch(() => ({})) : Promise.resolve({})]);
15
+ if (!parsed) return null;
16
+ const fullVersion = hints.fullVersionList?.find((b) => !NOT_A_BRAND_REGEX.test(b.brand))?.version;
17
+ return {
18
+ ...parsed,
19
+ browser: {
20
+ ...parsed.browser,
21
+ ...fullVersion ? { fullVersion } : {}
22
+ },
23
+ cpu: {
24
+ ...parsed.cpu,
25
+ ...hints.bitness ? { bitness: hints.bitness } : {}
26
+ },
27
+ os: {
28
+ ...parsed.os,
29
+ ...hints.platformVersion ? { versionFull: hints.platformVersion } : {}
30
+ },
31
+ frozenUa: isFrozenUA(navigator.userAgent)
32
+ };
33
+ }
34
+ function getBrowserMetadata() {
35
+ if ([
36
+ typeof navigator,
37
+ typeof screen,
38
+ typeof globalThis
39
+ ].some((x) => x === "undefined")) return null;
40
+ const { language } = navigator;
41
+ return {
42
+ language,
43
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
44
+ display: { screen: {
45
+ height: screen.availHeight,
46
+ width: screen.availWidth,
47
+ orientation: screen.orientation?.type
48
+ } }
49
+ };
50
+ }
51
+ async function collectContext() {
52
+ return {
53
+ runtime: "browser",
54
+ browser: getBrowserMetadata(),
55
+ device: await getDeviceMetadata()
56
+ };
57
+ }
58
+ //#endregion
59
+ export { collectContext };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-context.mjs","names":[],"sources":["../../src/internal/browser-context.ts"],"sourcesContent":["import type {\n BrowserContext,\n BrowserMetadata,\n DeviceMetadata,\n} from \"@interfere/types/sdk/plugins/context/browser\";\n\nimport { UAParser } from \"@ua-parser-js/pro-enterprise\";\nimport { InApps } from \"@ua-parser-js/pro-enterprise/extensions\";\nimport { isFrozenUA } from \"@ua-parser-js/pro-enterprise/helpers\";\n\ninterface NavigatorUABrand {\n brand: string;\n version: string;\n}\n\ninterface NavigatorUAData {\n brands?: NavigatorUABrand[];\n getHighEntropyValues?: (hints: string[]) => Promise<HighEntropyValues>;\n}\n\ninterface HighEntropyValues {\n bitness?: string;\n fullVersionList?: NavigatorUABrand[];\n platformVersion?: string;\n}\n\nconst NOT_A_BRAND_REGEX = /not.a.brand/i;\nconst HIGH_ENTROPY_HINTS = [\"bitness\", \"platformVersion\", \"fullVersionList\"];\n\nasync function getDeviceMetadata(): Promise<DeviceMetadata | null> {\n if (typeof navigator === \"undefined\") {\n return null;\n }\n\n const uaData = (navigator as Navigator & { userAgentData?: NavigatorUAData })\n .userAgentData;\n\n // `withClientHints()` resolves with UA-only data on non-Chromium and never\n // rejects. We separately request the raw high-entropy values so we can\n // surface `bitness`, the unrounded `platformVersion`, and the full\n // browser version — fields the parser folds into existing slots and\n // therefore loses precision on. `getHighEntropyValues` can reject with\n // `NotAllowedError` (Permissions Policy / Privacy Budget); swallow it.\n // The `InApps` extension is merged into the default regex map (see\n // `extend(defaultRegexes, extensions)` in ua-parser-js), so we still get\n // normal browser/OS detection plus tagging for in-app browsers (Slack,\n // Teams, Discord, VS Code, etc.) — those land as `browser.type: \"inapp\"`.\n const [parsed, hints] = await Promise.all([\n UAParser(navigator.userAgent, InApps).withClientHints(),\n uaData?.getHighEntropyValues\n ? uaData\n .getHighEntropyValues(HIGH_ENTROPY_HINTS)\n .catch((): HighEntropyValues => ({}))\n : Promise.resolve<HighEntropyValues>({}),\n ]);\n\n if (!parsed) {\n return null;\n }\n\n const fullVersion = hints.fullVersionList?.find(\n (b) => !NOT_A_BRAND_REGEX.test(b.brand)\n )?.version;\n\n return {\n ...parsed,\n browser: {\n ...parsed.browser,\n ...(fullVersion ? { fullVersion } : {}),\n },\n cpu: {\n ...parsed.cpu,\n ...(hints.bitness ? { bitness: hints.bitness } : {}),\n },\n os: {\n ...parsed.os,\n ...(hints.platformVersion ? { versionFull: hints.platformVersion } : {}),\n },\n frozenUa: isFrozenUA(navigator.userAgent),\n };\n}\n\nfunction getBrowserMetadata(): BrowserMetadata | null {\n if (\n [typeof navigator, typeof screen, typeof globalThis].some(\n (x) => x === \"undefined\"\n )\n ) {\n return null;\n }\n\n const { language } = navigator;\n\n return {\n language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n display: {\n screen: {\n height: screen.availHeight,\n width: screen.availWidth,\n orientation: screen.orientation?.type,\n },\n },\n };\n}\n\nexport async function collectContext(): Promise<BrowserContext> {\n return {\n runtime: \"browser\",\n browser: getBrowserMetadata(),\n device: await getDeviceMetadata(),\n };\n}\n"],"mappings":";;;;AA0BA,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB;CAAC;CAAW;CAAmB;CAAkB;AAE5E,eAAe,oBAAoD;CACjE,IAAI,OAAO,cAAc,aACvB,OAAO;CAGT,MAAM,SAAU,UACb;CAYH,MAAM,CAAC,QAAQ,SAAS,MAAM,QAAQ,IAAI,CACxC,SAAS,UAAU,WAAW,OAAO,CAAC,iBAAiB,EACvD,QAAQ,uBACJ,OACG,qBAAqB,mBAAmB,CACxC,aAAgC,EAAE,EAAE,GACvC,QAAQ,QAA2B,EAAE,CAAC,CAC3C,CAAC;CAEF,IAAI,CAAC,QACH,OAAO;CAGT,MAAM,cAAc,MAAM,iBAAiB,MACxC,MAAM,CAAC,kBAAkB,KAAK,EAAE,MAAM,CACxC,EAAE;CAEH,OAAO;EACL,GAAG;EACH,SAAS;GACP,GAAG,OAAO;GACV,GAAI,cAAc,EAAE,aAAa,GAAG,EAAE;GACvC;EACD,KAAK;GACH,GAAG,OAAO;GACV,GAAI,MAAM,UAAU,EAAE,SAAS,MAAM,SAAS,GAAG,EAAE;GACpD;EACD,IAAI;GACF,GAAG,OAAO;GACV,GAAI,MAAM,kBAAkB,EAAE,aAAa,MAAM,iBAAiB,GAAG,EAAE;GACxE;EACD,UAAU,WAAW,UAAU,UAAU;EAC1C;;AAGH,SAAS,qBAA6C;CACpD,IACE;EAAC,OAAO;EAAW,OAAO;EAAQ,OAAO;EAAW,CAAC,MAClD,MAAM,MAAM,YACd,EAED,OAAO;CAGT,MAAM,EAAE,aAAa;CAErB,OAAO;EACL;EACA,UAAU,KAAK,gBAAgB,CAAC,iBAAiB,CAAC;EAClD,SAAS,EACP,QAAQ;GACN,QAAQ,OAAO;GACf,OAAO,OAAO;GACd,aAAa,OAAO,aAAa;GAClC,EACF;EACF;;AAGH,eAAsB,iBAA0C;CAC9D,OAAO;EACL,SAAS;EACT,SAAS,oBAAoB;EAC7B,QAAQ,MAAM,mBAAmB;EAClC"}
@@ -1,4 +1,6 @@
1
- import { Component, ReactNode } from "react";
1
+ import { InterfereContext, InterfereContextValue } from "./react-context.mjs";
2
+ import * as _$react from "react";
3
+ import { Component, ContextType, ReactNode } from "react";
2
4
 
3
5
  //#region src/internal/capture-boundary.d.ts
4
6
  interface CaptureBoundaryProps {
@@ -31,6 +33,8 @@ interface CaptureBoundaryState {
31
33
  * captured the error but did not render a fallback — propagation continues.
32
34
  */
33
35
  declare class CaptureBoundary extends Component<CaptureBoundaryProps, CaptureBoundaryState> {
36
+ static contextType: _$react.Context<InterfereContextValue | null>;
37
+ context: ContextType<typeof InterfereContext>;
34
38
  state: CaptureBoundaryState;
35
39
  static getDerivedStateFromError(error: Error): CaptureBoundaryState;
36
40
  render(): ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"capture-boundary.d.mts","names":[],"sources":["../../src/internal/capture-boundary.tsx"],"mappings":";;;UAQU,oBAAA;EACR,QAAA,EAAU,SAAA;AAAA;AAAA,UAGF,oBAAA;EACR,KAAA,EAAO,KAAA;AAAA;;AAJY;;;;;AA8BrB;;;;;;;;;;;;;;;;;cAAa,eAAA,SAAwB,SAAA,CACnC,oBAAA,EACA,oBAAA;EAES,KAAA,EAAO,oBAAA;EAAA,OAET,wBAAA,CAAyB,KAAA,EAAO,KAAA,GAAQ,oBAAA;EAQtC,MAAA,CAAA,GAAU,SAAA;AAAA"}
1
+ {"version":3,"file":"capture-boundary.d.mts","names":[],"sources":["../../src/internal/capture-boundary.tsx"],"mappings":";;;;;UASU,oBAAA;EACR,QAAA,EAAU,SAAA;AAAA;AAAA,UAGF,oBAAA;EACR,KAAA,EAAO,KAAA;AAAA;;;AAJY;;;;;AA8BrB;;;;;;;;;;;;;;;;cAAa,eAAA,SAAwB,SAAA,CACnC,oBAAA,EACA,oBAAA;EAAA,OAEgB,WAAA,EAAW,OAAA,CAAA,OAAA,CAFP,qBAAA;EAGZ,OAAA,EAAS,WAAA,QAAmB,gBAAA;EAE3B,KAAA,EAAO,oBAAA;EAAA,OAET,wBAAA,CAAyB,KAAA,EAAO,KAAA,GAAQ,oBAAA;EAItC,MAAA,CAAA,GAAU,SAAA;AAAA"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { captureReactError } from "./capture.mjs";
3
+ import { InterfereContext } from "./react-context.mjs";
3
4
  import { MECHANISM_TYPE } from "@interfere/types/sdk/errors";
4
5
  import { Component } from "react";
5
6
  //#region src/internal/capture-boundary.tsx
@@ -27,16 +28,19 @@ import { Component } from "react";
27
28
  * captured the error but did not render a fallback — propagation continues.
28
29
  */
29
30
  var CaptureBoundary = class extends Component {
31
+ static contextType = InterfereContext;
30
32
  state = { error: null };
31
33
  static getDerivedStateFromError(error) {
32
- captureReactError(error, null, {
33
- type: MECHANISM_TYPE.react.captureBoundary,
34
- handled: false
35
- });
36
34
  return { error };
37
35
  }
38
36
  render() {
39
- if (this.state.error) throw this.state.error;
37
+ if (this.state.error) {
38
+ captureReactError(this.context?.kernel ?? null, this.state.error, null, {
39
+ type: MECHANISM_TYPE.react.captureBoundary,
40
+ handled: false
41
+ });
42
+ throw this.state.error;
43
+ }
40
44
  return this.props.children;
41
45
  }
42
46
  };
@@ -1 +1 @@
1
- {"version":3,"file":"capture-boundary.mjs","names":[],"sources":["../../src/internal/capture-boundary.tsx"],"sourcesContent":["\"use client\";\n\nimport { MECHANISM_TYPE } from \"@interfere/types/sdk/errors\";\n\nimport { Component, type ReactNode } from \"react\";\n\nimport { captureReactError } from \"./capture.js\";\n\ninterface CaptureBoundaryProps {\n children: ReactNode;\n}\n\ninterface CaptureBoundaryState {\n error: Error | null;\n}\n\n/**\n * Internal boundary used by `<InterfereProvider>` to capture render-phase\n * React errors without changing the app's UX.\n *\n * Unlike the public `ErrorBoundary`, this boundary always re-throws the\n * captured error in its `render()` so upstream boundaries — the customer's\n * own `ErrorBoundary`, Next.js's `error.tsx` / `global-error.tsx`, or React's\n * default unmount — keep control of what the user sees. The net effect:\n * zero visual change, full capture coverage for any render-phase error in\n * the subtree.\n *\n * Capture happens inside `getDerivedStateFromError` rather than\n * `componentDidCatch` because `componentDidCatch` does not fire on a boundary\n * that re-throws in render — React considers such a boundary to have failed\n * and skips its commit-phase lifecycle. The trade-off: we don't get\n * `errorInfo.componentStack` in this capture. Callers who want the component\n * tree should use the public `ErrorBoundary` (which renders a fallback and\n * therefore receives `componentDidCatch`), or pass\n * {@link reactErrorHandler} to `createRoot()`.\n *\n * `mechanism.handled` is `false` because from Interfere's perspective we\n * captured the error but did not render a fallback — propagation continues.\n */\nexport class CaptureBoundary extends Component<\n CaptureBoundaryProps,\n CaptureBoundaryState\n> {\n override state: CaptureBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: Error): CaptureBoundaryState {\n captureReactError(error, null, {\n type: MECHANISM_TYPE.react.captureBoundary,\n handled: false,\n });\n return { error };\n }\n\n override render(): ReactNode {\n if (this.state.error) {\n throw this.state.error;\n }\n return this.props.children;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,IAAa,kBAAb,cAAqC,UAGnC;CACA,QAAuC,EAAE,OAAO,MAAM;CAEtD,OAAO,yBAAyB,OAAoC;AAClE,oBAAkB,OAAO,MAAM;GAC7B,MAAM,eAAe,MAAM;GAC3B,SAAS;GACV,CAAC;AACF,SAAO,EAAE,OAAO;;CAGlB,SAA6B;AAC3B,MAAI,KAAK,MAAM,MACb,OAAM,KAAK,MAAM;AAEnB,SAAO,KAAK,MAAM"}
1
+ {"version":3,"file":"capture-boundary.mjs","names":[],"sources":["../../src/internal/capture-boundary.tsx"],"sourcesContent":["\"use client\";\n\nimport { MECHANISM_TYPE } from \"@interfere/types/sdk/errors\";\n\nimport { Component, type ContextType, type ReactNode } from \"react\";\n\nimport { captureReactError } from \"./capture.js\";\nimport { InterfereContext } from \"./react-context.js\";\n\ninterface CaptureBoundaryProps {\n children: ReactNode;\n}\n\ninterface CaptureBoundaryState {\n error: Error | null;\n}\n\n/**\n * Internal boundary used by `<InterfereProvider>` to capture render-phase\n * React errors without changing the app's UX.\n *\n * Unlike the public `ErrorBoundary`, this boundary always re-throws the\n * captured error in its `render()` so upstream boundaries — the customer's\n * own `ErrorBoundary`, Next.js's `error.tsx` / `global-error.tsx`, or React's\n * default unmount — keep control of what the user sees. The net effect:\n * zero visual change, full capture coverage for any render-phase error in\n * the subtree.\n *\n * Capture happens inside `getDerivedStateFromError` rather than\n * `componentDidCatch` because `componentDidCatch` does not fire on a boundary\n * that re-throws in render — React considers such a boundary to have failed\n * and skips its commit-phase lifecycle. The trade-off: we don't get\n * `errorInfo.componentStack` in this capture. Callers who want the component\n * tree should use the public `ErrorBoundary` (which renders a fallback and\n * therefore receives `componentDidCatch`), or pass\n * {@link reactErrorHandler} to `createRoot()`.\n *\n * `mechanism.handled` is `false` because from Interfere's perspective we\n * captured the error but did not render a fallback — propagation continues.\n */\nexport class CaptureBoundary extends Component<\n CaptureBoundaryProps,\n CaptureBoundaryState\n> {\n static override contextType = InterfereContext;\n declare context: ContextType<typeof InterfereContext>;\n\n override state: CaptureBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: Error): CaptureBoundaryState {\n return { error };\n }\n\n override render(): ReactNode {\n if (this.state.error) {\n // Capture before re-throwing. `componentDidCatch` never fires when\n // render throws (React treats the boundary itself as failed), so the\n // capture has to happen here. `captureReactError` dedupes on the\n // error instance, so the multiple-render case in concurrent React\n // doesn't cause duplicate captures.\n captureReactError(this.context?.kernel ?? null, this.state.error, null, {\n type: MECHANISM_TYPE.react.captureBoundary,\n handled: false,\n });\n throw this.state.error;\n }\n return this.props.children;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,IAAa,kBAAb,cAAqC,UAGnC;CACA,OAAgB,cAAc;CAG9B,QAAuC,EAAE,OAAO,MAAM;CAEtD,OAAO,yBAAyB,OAAoC;EAClE,OAAO,EAAE,OAAO;;CAGlB,SAA6B;EAC3B,IAAI,KAAK,MAAM,OAAO;GAMpB,kBAAkB,KAAK,SAAS,UAAU,MAAM,KAAK,MAAM,OAAO,MAAM;IACtE,MAAM,eAAe,MAAM;IAC3B,SAAS;IACV,CAAC;GACF,MAAM,KAAK,MAAM;;EAEnB,OAAO,KAAK,MAAM"}
@@ -1,13 +1,24 @@
1
+ import { Kernel } from "./kernel.mjs";
1
2
  import { ErrorMechanism } from "@interfere/types/sdk/plugins/payload/errors";
2
3
 
3
4
  //#region src/internal/capture.d.ts
4
5
  /**
5
- * Captures a React error through the SDK, attaching the component stack
6
- * reported by React as additional frames. Dedupes on the Error instance.
6
+ * Routes a React error through `kernel.recordException()`, attaching the
7
+ * component stack reported by React as additional frames. Dedup of the
8
+ * same `Error` instance caught by both the boundary and `window.onerror`
9
+ * happens inside `recordException`.
7
10
  *
8
- * Swallows errors from `getClient()` so callers can run before the SDK is
9
- * initialized (e.g. an error boundary catching a pre-init crash).
11
+ * `error.digest` is React's identifier for an RSC server-side throw that
12
+ * was sanitised and rebuilt on the client promote it so enrichment can
13
+ * fingerprint the redacted client capture into the same problem as the
14
+ * unredacted server capture.
15
+ *
16
+ * Accepts a nullable kernel — error boundaries can mount outside the
17
+ * `<InterfereProvider>` tree, in which case we silently skip capture and
18
+ * let the host render its fallback / call its `onError` callback.
10
19
  */
11
- declare function captureReactError(error: Error, componentStack: string | null | undefined, mechanism: ErrorMechanism): void;
20
+ declare function captureReactError(kernel: Kernel | null, error: Error & {
21
+ digest?: string;
22
+ }, componentStack: string | null | undefined, mechanism: ErrorMechanism): void;
12
23
  //#endregion
13
24
  export { captureReactError };
@@ -1 +1 @@
1
- {"version":3,"file":"capture.d.mts","names":[],"sources":["../../src/internal/capture.ts"],"mappings":";;;;;AAiBA;;;;;iBAAgB,iBAAA,CACd,KAAA,EAAO,KAAA,EACP,cAAA,6BACA,SAAA,EAAW,cAAA"}
1
+ {"version":3,"file":"capture.d.mts","names":[],"sources":["../../src/internal/capture.ts"],"mappings":";;;;;;AAoBA;;;;;;;;;;;;;iBAAgB,iBAAA,CACd,MAAA,EAAQ,MAAA,SACR,KAAA,EAAO,KAAA;EAAU,MAAA;AAAA,GACjB,cAAA,6BACA,SAAA,EAAW,cAAA"}
@@ -1,23 +1,27 @@
1
- import { seen } from "./errors.mjs";
2
- import { getClient } from "./client.mjs";
3
- import { parseReactComponentStack, shouldDropBrowserExtensionNoise, toExceptions } from "@interfere/types/sdk/errors";
1
+ import { parseReactComponentStack } from "@interfere/types/sdk/errors";
4
2
  //#region src/internal/capture.ts
5
3
  /**
6
- * Captures a React error through the SDK, attaching the component stack
7
- * reported by React as additional frames. Dedupes on the Error instance.
4
+ * Routes a React error through `kernel.recordException()`, attaching the
5
+ * component stack reported by React as additional frames. Dedup of the
6
+ * same `Error` instance caught by both the boundary and `window.onerror`
7
+ * happens inside `recordException`.
8
8
  *
9
- * Swallows errors from `getClient()` so callers can run before the SDK is
10
- * initialized (e.g. an error boundary catching a pre-init crash).
9
+ * `error.digest` is React's identifier for an RSC server-side throw that
10
+ * was sanitised and rebuilt on the client promote it so enrichment can
11
+ * fingerprint the redacted client capture into the same problem as the
12
+ * unredacted server capture.
13
+ *
14
+ * Accepts a nullable kernel — error boundaries can mount outside the
15
+ * `<InterfereProvider>` tree, in which case we silently skip capture and
16
+ * let the host render its fallback / call its `onError` callback.
11
17
  */
12
- function captureReactError(error, componentStack, mechanism) {
13
- if (seen.has(error)) return;
14
- seen.add(error);
15
- try {
16
- const exceptions = toExceptions(error, mechanism);
17
- if (componentStack && exceptions[0]) exceptions[0].frames.push(...parseReactComponentStack(componentStack));
18
- if (shouldDropBrowserExtensionNoise(exceptions)) return;
19
- getClient().capture("error", { exceptions });
20
- } catch {}
18
+ function captureReactError(kernel, error, componentStack, mechanism) {
19
+ if (!kernel) return;
20
+ kernel.recordException(error, {
21
+ mechanism,
22
+ ...componentStack ? { appendFrames: parseReactComponentStack(componentStack) } : {},
23
+ ...error.digest ? { errorDigest: error.digest } : {}
24
+ });
21
25
  }
22
26
  //#endregion
23
27
  export { captureReactError };
@@ -1 +1 @@
1
- {"version":3,"file":"capture.mjs","names":[],"sources":["../../src/internal/capture.ts"],"sourcesContent":["import {\n parseReactComponentStack,\n shouldDropBrowserExtensionNoise,\n toExceptions,\n} from \"@interfere/types/sdk/errors\";\nimport type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport { getClient } from \"./client.js\";\nimport { seen } from \"./errors.js\";\n\n/**\n * Captures a React error through the SDK, attaching the component stack\n * reported by React as additional frames. Dedupes on the Error instance.\n *\n * Swallows errors from `getClient()` so callers can run before the SDK is\n * initialized (e.g. an error boundary catching a pre-init crash).\n */\nexport function captureReactError(\n error: Error,\n componentStack: string | null | undefined,\n mechanism: ErrorMechanism\n): void {\n if (seen.has(error)) {\n return;\n }\n seen.add(error);\n\n try {\n const exceptions = toExceptions(error, mechanism);\n\n if (componentStack && exceptions[0]) {\n exceptions[0].frames.push(...parseReactComponentStack(componentStack));\n }\n\n if (shouldDropBrowserExtensionNoise(exceptions)) {\n return;\n }\n\n getClient().capture(\"error\", { exceptions });\n } catch {\n // SDK not initialized — the caller still gets their fallback / user cb.\n }\n}\n"],"mappings":";;;;;;;;;;;AAiBA,SAAgB,kBACd,OACA,gBACA,WACM;AACN,KAAI,KAAK,IAAI,MAAM,CACjB;AAEF,MAAK,IAAI,MAAM;AAEf,KAAI;EACF,MAAM,aAAa,aAAa,OAAO,UAAU;AAEjD,MAAI,kBAAkB,WAAW,GAC/B,YAAW,GAAG,OAAO,KAAK,GAAG,yBAAyB,eAAe,CAAC;AAGxE,MAAI,gCAAgC,WAAW,CAC7C;AAGF,aAAW,CAAC,QAAQ,SAAS,EAAE,YAAY,CAAC;SACtC"}
1
+ {"version":3,"file":"capture.mjs","names":[],"sources":["../../src/internal/capture.ts"],"sourcesContent":["import { parseReactComponentStack } from \"@interfere/types/sdk/errors\";\nimport type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport type { Kernel } from \"./kernel.js\";\n\n/**\n * Routes a React error through `kernel.recordException()`, attaching the\n * component stack reported by React as additional frames. Dedup of the\n * same `Error` instance caught by both the boundary and `window.onerror`\n * happens inside `recordException`.\n *\n * `error.digest` is React's identifier for an RSC server-side throw that\n * was sanitised and rebuilt on the client — promote it so enrichment can\n * fingerprint the redacted client capture into the same problem as the\n * unredacted server capture.\n *\n * Accepts a nullable kernel — error boundaries can mount outside the\n * `<InterfereProvider>` tree, in which case we silently skip capture and\n * let the host render its fallback / call its `onError` callback.\n */\nexport function captureReactError(\n kernel: Kernel | null,\n error: Error & { digest?: string },\n componentStack: string | null | undefined,\n mechanism: ErrorMechanism\n): void {\n if (!kernel) {\n return;\n }\n kernel.recordException(error, {\n mechanism,\n ...(componentStack\n ? { appendFrames: parseReactComponentStack(componentStack) }\n : {}),\n ...(error.digest ? { errorDigest: error.digest } : {}),\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAoBA,SAAgB,kBACd,QACA,OACA,gBACA,WACM;CACN,IAAI,CAAC,QACH;CAEF,OAAO,gBAAgB,OAAO;EAC5B;EACA,GAAI,iBACA,EAAE,cAAc,yBAAyB,eAAe,EAAE,GAC1D,EAAE;EACN,GAAI,MAAM,SAAS,EAAE,aAAa,MAAM,QAAQ,GAAG,EAAE;EACtD,CAAC"}
@@ -1,10 +1,26 @@
1
- import { IngestTarget } from "../transport/http.mjs";
2
-
3
1
  //#region src/internal/config.d.ts
2
+ interface IngestTarget {
3
+ headers: Headers;
4
+ url: string;
5
+ }
6
+ interface AuthHeaders {
7
+ headers: Headers;
8
+ }
4
9
  declare function resolveTargets(): {
10
+ /**
11
+ * Base URL for OTLP exports — the trace/metric exporters append the
12
+ * sink path themselves. Avoids the kernel having to regex-strip
13
+ * `/v2/sink` off the configured URL to derive a base.
14
+ */
15
+ collectorBaseUrl: string;
5
16
  config: IngestTarget;
6
- ingest: IngestTarget;
17
+ /**
18
+ * Identity headers shared across every collector-bound request
19
+ * (OTLP exporters, replay upload, session sync). One source of
20
+ * truth for auth + force-enable.
21
+ */
22
+ ingest: AuthHeaders;
7
23
  session: IngestTarget;
8
24
  };
9
25
  //#endregion
10
- export { resolveTargets };
26
+ export { AuthHeaders, IngestTarget, resolveTargets };
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.mts","names":[],"sources":["../../src/internal/config.ts"],"mappings":";;;iBAyBgB,cAAA,CAAA;EACd,MAAA,EAAQ,YAAA;EACR,MAAA,EAAQ,YAAA;EACR,OAAA,EAAS,YAAA;AAAA"}
1
+ {"version":3,"file":"config.d.mts","names":[],"sources":["../../src/internal/config.ts"],"mappings":";UAKiB,YAAA;EACf,OAAA,EAAS,OAAA;EACT,GAAA;AAAA;AAAA,UAGe,WAAA;EACf,OAAA,EAAS,OAAA;AAAA;AAAA,iBAsBK,cAAA,CAAA;EA1BX;;AAGL;;;EA6BE,gBAAA;EACA,MAAA,EAAQ,YAAA;EAPM;;;;;EAad,MAAA,EAAQ,WAAA;EACR,OAAA,EAAS,YAAA;AAAA"}
@@ -1,30 +1,30 @@
1
+ import { getGlobal } from "../util/global.mjs";
1
2
  import { API_PATHS, API_URL } from "@interfere/constants/api";
3
+ import { resolveRoutePrefix } from "@interfere/constants/route-prefix";
2
4
  //#region src/internal/config.ts
3
- const DEFAULT_PROXY_URL = "/api/interfere";
4
- const DEFAULT_SESSION_PATH = "/v1/session";
5
- const DEFAULT_CONFIG_PATH = "/v1/config";
6
5
  function resolvePublicKey() {
7
- const injected = globalThis["__INTERFERE_PUBLIC_KEY__"];
6
+ const injected = getGlobal("__INTERFERE_PUBLIC_KEY__");
8
7
  if (injected) return injected;
9
8
  if (typeof process !== "undefined") return process.env["INTERFERE_PUBLIC_KEY"] ?? void 0;
10
9
  }
10
+ function isForceEnableActive() {
11
+ return !!getGlobal("__INTERFERE_FORCE_ENABLE__");
12
+ }
11
13
  function resolveTargets() {
12
14
  const publicKey = resolvePublicKey();
13
15
  const headers = new Headers({ "content-type": "application/json" });
14
16
  if (publicKey) headers.set("x-interfere-pub-token", publicKey);
15
- const baseUrl = publicKey ? API_URL : DEFAULT_PROXY_URL;
16
- const sessionPath = API_PATHS.SESSION ?? DEFAULT_SESSION_PATH;
17
+ if (isForceEnableActive()) headers.set("x-interfere-force-enable", "1");
18
+ const baseUrl = publicKey ? API_URL : resolveRoutePrefix();
17
19
  return {
20
+ collectorBaseUrl: baseUrl,
18
21
  config: {
19
- url: `${baseUrl}${API_PATHS.CONFIG ?? DEFAULT_CONFIG_PATH}`,
20
- headers
21
- },
22
- ingest: {
23
- url: `${baseUrl}${API_PATHS.INGEST}`,
22
+ url: `${baseUrl}${API_PATHS.CONFIG}`,
24
23
  headers
25
24
  },
25
+ ingest: { headers },
26
26
  session: {
27
- url: `${baseUrl}${sessionPath}`,
27
+ url: `${baseUrl}${API_PATHS.SESSION}`,
28
28
  headers
29
29
  }
30
30
  };
@@ -1 +1 @@
1
- {"version":3,"file":"config.mjs","names":[],"sources":["../../src/internal/config.ts"],"sourcesContent":["import { API_PATHS, API_URL } from \"@interfere/constants/api\";\n\nimport type { IngestTarget } from \"../transport/http.js\";\n\nconst DEFAULT_PROXY_URL = \"/api/interfere\";\nconst DEFAULT_SESSION_PATH = \"/v1/session\";\nconst DEFAULT_CONFIG_PATH = \"/v1/config\";\n\nfunction resolvePublicKey(): string | undefined {\n // Vite plugin injects the key onto globalThis at build time\n const injected = (globalThis as Record<string, unknown>)[\n \"__INTERFERE_PUBLIC_KEY__\"\n ] as string | undefined;\n if (injected) {\n return injected;\n }\n\n // Node / webpack / Next.js: read from process.env\n if (typeof process !== \"undefined\") {\n return process.env[\"INTERFERE_PUBLIC_KEY\"] ?? undefined;\n }\n\n return;\n}\n\nexport function resolveTargets(): {\n config: IngestTarget;\n ingest: IngestTarget;\n session: IngestTarget;\n} {\n const publicKey = resolvePublicKey();\n const headers = new Headers({ \"content-type\": \"application/json\" });\n if (publicKey) {\n headers.set(\"x-interfere-pub-token\", publicKey);\n }\n\n const baseUrl = publicKey ? API_URL : DEFAULT_PROXY_URL;\n const sessionPath =\n (API_PATHS as { SESSION?: string }).SESSION ?? DEFAULT_SESSION_PATH;\n const configPath =\n (API_PATHS as { CONFIG?: string }).CONFIG ?? DEFAULT_CONFIG_PATH;\n\n return {\n config: {\n url: `${baseUrl}${configPath}`,\n headers,\n },\n ingest: {\n url: `${baseUrl}${API_PATHS.INGEST}`,\n headers,\n },\n session: {\n url: `${baseUrl}${sessionPath}`,\n headers,\n },\n };\n}\n"],"mappings":";;AAIA,MAAM,oBAAoB;AAC1B,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAE5B,SAAS,mBAAuC;CAE9C,MAAM,WAAY,WAChB;AAEF,KAAI,SACF,QAAO;AAIT,KAAI,OAAO,YAAY,YACrB,QAAO,QAAQ,IAAI,2BAA2B,KAAA;;AAMlD,SAAgB,iBAId;CACA,MAAM,YAAY,kBAAkB;CACpC,MAAM,UAAU,IAAI,QAAQ,EAAE,gBAAgB,oBAAoB,CAAC;AACnE,KAAI,UACF,SAAQ,IAAI,yBAAyB,UAAU;CAGjD,MAAM,UAAU,YAAY,UAAU;CACtC,MAAM,cACH,UAAmC,WAAW;AAIjD,QAAO;EACL,QAAQ;GACN,KAAK,GAAG,UAJT,UAAkC,UAAU;GAK3C;GACD;EACD,QAAQ;GACN,KAAK,GAAG,UAAU,UAAU;GAC5B;GACD;EACD,SAAS;GACP,KAAK,GAAG,UAAU;GAClB;GACD;EACF"}
1
+ {"version":3,"file":"config.mjs","names":[],"sources":["../../src/internal/config.ts"],"sourcesContent":["import { API_PATHS, API_URL } from \"@interfere/constants/api\";\nimport { resolveRoutePrefix } from \"@interfere/constants/route-prefix\";\n\nimport { getGlobal } from \"../util/global.js\";\n\nexport interface IngestTarget {\n headers: Headers;\n url: string;\n}\n\nexport interface AuthHeaders {\n headers: Headers;\n}\n\nfunction resolvePublicKey(): string | undefined {\n // Vite plugin injects the key onto globalThis at build time\n const injected = getGlobal<string>(\"__INTERFERE_PUBLIC_KEY__\");\n if (injected) {\n return injected;\n }\n\n // Node / webpack / Next.js: read from process.env\n if (typeof process !== \"undefined\") {\n return process.env[\"INTERFERE_PUBLIC_KEY\"] ?? undefined;\n }\n\n return;\n}\n\nfunction isForceEnableActive(): boolean {\n return !!getGlobal<boolean>(\"__INTERFERE_FORCE_ENABLE__\");\n}\n\nexport function resolveTargets(): {\n /**\n * Base URL for OTLP exports — the trace/metric exporters append the\n * sink path themselves. Avoids the kernel having to regex-strip\n * `/v2/sink` off the configured URL to derive a base.\n */\n collectorBaseUrl: string;\n config: IngestTarget;\n /**\n * Identity headers shared across every collector-bound request\n * (OTLP exporters, replay upload, session sync). One source of\n * truth for auth + force-enable.\n */\n ingest: AuthHeaders;\n session: IngestTarget;\n} {\n const publicKey = resolvePublicKey();\n const headers = new Headers({ \"content-type\": \"application/json\" });\n if (publicKey) {\n headers.set(\"x-interfere-pub-token\", publicKey);\n }\n // Single chokepoint: every collector request (OTLP exporters, replay\n // upload, session, config) reuses these headers, so the force-enable\n // flag travels everywhere a customer event might. Production\n // collectors drop on this header (ENG-1356).\n if (isForceEnableActive()) {\n headers.set(\"x-interfere-force-enable\", \"1\");\n }\n\n const baseUrl = publicKey ? API_URL : resolveRoutePrefix();\n\n return {\n collectorBaseUrl: baseUrl,\n config: {\n url: `${baseUrl}${API_PATHS.CONFIG}`,\n headers,\n },\n ingest: { headers },\n session: {\n url: `${baseUrl}${API_PATHS.SESSION}`,\n headers,\n },\n };\n}\n"],"mappings":";;;;AAcA,SAAS,mBAAuC;CAE9C,MAAM,WAAW,UAAkB,2BAA2B;CAC9D,IAAI,UACF,OAAO;CAIT,IAAI,OAAO,YAAY,aACrB,OAAO,QAAQ,IAAI,2BAA2B,KAAA;;AAMlD,SAAS,sBAA+B;CACtC,OAAO,CAAC,CAAC,UAAmB,6BAA6B;;AAG3D,SAAgB,iBAed;CACA,MAAM,YAAY,kBAAkB;CACpC,MAAM,UAAU,IAAI,QAAQ,EAAE,gBAAgB,oBAAoB,CAAC;CACnE,IAAI,WACF,QAAQ,IAAI,yBAAyB,UAAU;CAMjD,IAAI,qBAAqB,EACvB,QAAQ,IAAI,4BAA4B,IAAI;CAG9C,MAAM,UAAU,YAAY,UAAU,oBAAoB;CAE1D,OAAO;EACL,kBAAkB;EAClB,QAAQ;GACN,KAAK,GAAG,UAAU,UAAU;GAC5B;GACD;EACD,QAAQ,EAAE,SAAS;EACnB,SAAS;GACP,KAAK,GAAG,UAAU,UAAU;GAC5B;GACD;EACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"consent.d.mts","names":[],"sources":["../../src/internal/consent.ts"],"mappings":";;;;cAQa,eAAA;EAAA,SAGoB,SAAA;EAAA,SAAA,MAAA;AAAA;AAAA,iBAYjB,wBAAA,CAAyB,GAAA,EAAK,SAAA,GAAY,eAAA;AAAA,iBAI1C,gBAAA,CACd,QAAA,EAAU,eAAA,EACV,YAAA,EAAc,YAAA;AAAA,iBASA,kBAAA,CACd,IAAA,EAAM,SAAA,EACN,YAAA,EAAc,YAAA;AAAA,iBAKA,qBAAA,CAAsB,OAAA,GAAU,YAAA,GAAe,YAAA;AAAA,iBAI/C,iBAAA,CACd,OAAA,EAAS,YAAA,SACT,IAAA,EAAM,YAAA"}
1
+ {"version":3,"file":"consent.d.mts","names":[],"sources":["../../src/internal/consent.ts"],"mappings":";;;;cASa,eAAA;EAAA,SAGoB,SAAA;EAAA,SAAA,MAAA;AAAA;AAAA,iBAcjB,wBAAA,CAAyB,GAAA,EAAK,SAAA,GAAY,eAAA;AAAA,iBAI1C,gBAAA,CACd,QAAA,EAAU,eAAA,EACV,YAAA,EAAc,YAAA;AAAA,iBASA,kBAAA,CACd,IAAA,EAAM,SAAA,EACN,YAAA,EAAc,YAAA;AAAA,iBAKA,qBAAA,CAAsB,OAAA,GAAU,YAAA,GAAe,YAAA;AAAA,iBAI/C,iBAAA,CACd,OAAA,EAAS,YAAA,SACT,IAAA,EAAM,YAAA"}
@@ -6,6 +6,7 @@ const DEFAULT_CONSENT = {
6
6
  };
7
7
  const PLUGIN_CONSENT_BY_KEY = Object.fromEntries(PLUGIN_MANIFEST.map((plugin) => [plugin.name, plugin.consentCategory]));
8
8
  const EVENT_CONSENT_BY_TYPE = Object.fromEntries(PLUGIN_MANIFEST.flatMap((plugin) => plugin.events.map((event) => [event.name, plugin.consentCategory])));
9
+ const GATEABLE_CATEGORIES = Object.keys(DEFAULT_CONSENT);
9
10
  function getPluginConsentCategory(key) {
10
11
  return PLUGIN_CONSENT_BY_KEY[key];
11
12
  }
@@ -19,7 +20,8 @@ function resolveGrantedConsent(consent) {
19
20
  return consent ?? { ...DEFAULT_CONSENT };
20
21
  }
21
22
  function hasConsentChanged(current, next) {
22
- return current?.analytics !== next?.analytics || current?.replay !== next?.replay;
23
+ for (const category of GATEABLE_CATEGORIES) if (current?.[category] !== next?.[category]) return true;
24
+ return false;
23
25
  }
24
26
  //#endregion
25
27
  export { DEFAULT_CONSENT, getPluginConsentCategory, hasConsentChanged, isConsentAllowed, resolveGrantedConsent, shouldCaptureEvent };
@@ -1 +1 @@
1
- {"version":3,"file":"consent.mjs","names":[],"sources":["../../src/internal/consent.ts"],"sourcesContent":["import type { EventType } from \"@interfere/types/sdk/envelope\";\nimport {\n type ConsentCategory,\n type ConsentState,\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\n\nexport const DEFAULT_CONSENT = {\n analytics: true,\n replay: true,\n} as const satisfies ConsentState;\n\nconst PLUGIN_CONSENT_BY_KEY = Object.fromEntries(\n PLUGIN_MANIFEST.map((plugin) => [plugin.name, plugin.consentCategory])\n) as Record<PluginKey, ConsentCategory>;\n\nconst EVENT_CONSENT_BY_TYPE = Object.fromEntries(\n PLUGIN_MANIFEST.flatMap((plugin) =>\n plugin.events.map((event) => [event.name, plugin.consentCategory] as const)\n )\n) as Record<EventType, ConsentCategory>;\n\nexport function getPluginConsentCategory(key: PluginKey): ConsentCategory {\n return PLUGIN_CONSENT_BY_KEY[key];\n}\n\nexport function isConsentAllowed(\n category: ConsentCategory,\n consentState: ConsentState | null\n): boolean {\n return (\n category === \"necessary\" ||\n consentState === null ||\n consentState[category] === true\n );\n}\n\nexport function shouldCaptureEvent(\n type: EventType,\n consentState: ConsentState | null\n): boolean {\n return isConsentAllowed(EVENT_CONSENT_BY_TYPE[type], consentState);\n}\n\nexport function resolveGrantedConsent(consent?: ConsentState): ConsentState {\n return consent ?? { ...DEFAULT_CONSENT };\n}\n\nexport function hasConsentChanged(\n current: ConsentState | null,\n next: ConsentState | null\n): boolean {\n return (\n current?.analytics !== next?.analytics || current?.replay !== next?.replay\n );\n}\n"],"mappings":";;AAQA,MAAa,kBAAkB;CAC7B,WAAW;CACX,QAAQ;CACT;AAED,MAAM,wBAAwB,OAAO,YACnC,gBAAgB,KAAK,WAAW,CAAC,OAAO,MAAM,OAAO,gBAAgB,CAAC,CACvE;AAED,MAAM,wBAAwB,OAAO,YACnC,gBAAgB,SAAS,WACvB,OAAO,OAAO,KAAK,UAAU,CAAC,MAAM,MAAM,OAAO,gBAAgB,CAAU,CAC5E,CACF;AAED,SAAgB,yBAAyB,KAAiC;AACxE,QAAO,sBAAsB;;AAG/B,SAAgB,iBACd,UACA,cACS;AACT,QACE,aAAa,eACb,iBAAiB,QACjB,aAAa,cAAc;;AAI/B,SAAgB,mBACd,MACA,cACS;AACT,QAAO,iBAAiB,sBAAsB,OAAO,aAAa;;AAGpE,SAAgB,sBAAsB,SAAsC;AAC1E,QAAO,WAAW,EAAE,GAAG,iBAAiB;;AAG1C,SAAgB,kBACd,SACA,MACS;AACT,QACE,SAAS,cAAc,MAAM,aAAa,SAAS,WAAW,MAAM"}
1
+ {"version":3,"file":"consent.mjs","names":[],"sources":["../../src/internal/consent.ts"],"sourcesContent":["import type { EventType } from \"@interfere/types/sdk/envelope\";\nimport {\n type ConsentCategory,\n type ConsentState,\n type GateableCategory,\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\n\nexport const DEFAULT_CONSENT = {\n analytics: true,\n replay: true,\n} as const satisfies ConsentState;\n\nconst PLUGIN_CONSENT_BY_KEY = Object.fromEntries(\n PLUGIN_MANIFEST.map((plugin) => [plugin.name, plugin.consentCategory])\n) as Record<PluginKey, ConsentCategory>;\n\nconst EVENT_CONSENT_BY_TYPE = Object.fromEntries(\n PLUGIN_MANIFEST.flatMap((plugin) =>\n plugin.events.map((event) => [event.name, plugin.consentCategory] as const)\n )\n) as Record<EventType, ConsentCategory>;\n\nconst GATEABLE_CATEGORIES = Object.keys(DEFAULT_CONSENT) as GateableCategory[];\n\nexport function getPluginConsentCategory(key: PluginKey): ConsentCategory {\n return PLUGIN_CONSENT_BY_KEY[key];\n}\n\nexport function isConsentAllowed(\n category: ConsentCategory,\n consentState: ConsentState | null\n): boolean {\n return (\n category === \"necessary\" ||\n consentState === null ||\n consentState[category] === true\n );\n}\n\nexport function shouldCaptureEvent(\n type: EventType,\n consentState: ConsentState | null\n): boolean {\n return isConsentAllowed(EVENT_CONSENT_BY_TYPE[type], consentState);\n}\n\nexport function resolveGrantedConsent(consent?: ConsentState): ConsentState {\n return consent ?? { ...DEFAULT_CONSENT };\n}\n\nexport function hasConsentChanged(\n current: ConsentState | null,\n next: ConsentState | null\n): boolean {\n for (const category of GATEABLE_CATEGORIES) {\n if (current?.[category] !== next?.[category]) {\n return true;\n }\n }\n return false;\n}\n"],"mappings":";;AASA,MAAa,kBAAkB;CAC7B,WAAW;CACX,QAAQ;CACT;AAED,MAAM,wBAAwB,OAAO,YACnC,gBAAgB,KAAK,WAAW,CAAC,OAAO,MAAM,OAAO,gBAAgB,CAAC,CACvE;AAED,MAAM,wBAAwB,OAAO,YACnC,gBAAgB,SAAS,WACvB,OAAO,OAAO,KAAK,UAAU,CAAC,MAAM,MAAM,OAAO,gBAAgB,CAAU,CAC5E,CACF;AAED,MAAM,sBAAsB,OAAO,KAAK,gBAAgB;AAExD,SAAgB,yBAAyB,KAAiC;CACxE,OAAO,sBAAsB;;AAG/B,SAAgB,iBACd,UACA,cACS;CACT,OACE,aAAa,eACb,iBAAiB,QACjB,aAAa,cAAc;;AAI/B,SAAgB,mBACd,MACA,cACS;CACT,OAAO,iBAAiB,sBAAsB,OAAO,aAAa;;AAGpE,SAAgB,sBAAsB,SAAsC;CAC1E,OAAO,WAAW,EAAE,GAAG,iBAAiB;;AAG1C,SAAgB,kBACd,SACA,MACS;CACT,KAAK,MAAM,YAAY,qBACrB,IAAI,UAAU,cAAc,OAAO,WACjC,OAAO;CAGX,OAAO"}