@interfere/react 9.0.2 → 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 (189) 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 +1 -0
  5. package/dist/api.mjs.map +1 -0
  6. package/dist/error-boundary.d.mts +10 -4
  7. package/dist/error-boundary.d.mts.map +1 -1
  8. package/dist/error-boundary.mjs +1 -39
  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 +1 -0
  13. package/dist/internal/browser-context.mjs.map +1 -0
  14. package/dist/internal/capture-boundary.d.mts +4 -1
  15. package/dist/internal/capture-boundary.d.mts.map +1 -1
  16. package/dist/internal/capture-boundary.mjs +1 -44
  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 +1 -23
  21. package/dist/internal/capture.mjs.map +1 -1
  22. package/dist/internal/config.d.mts +22 -4
  23. package/dist/internal/config.d.mts.map +1 -1
  24. package/dist/internal/config.mjs +1 -33
  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 +1 -25
  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 +1 -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 +1 -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 +1 -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 +1 -0
  44. package/dist/internal/kernel.mjs.map +1 -0
  45. package/dist/internal/otel/exporter.d.mts +85 -0
  46. package/dist/internal/otel/exporter.d.mts.map +1 -0
  47. package/dist/internal/otel/exporter.mjs +1 -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 +1 -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 +1 -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 +1 -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 +1 -0
  62. package/dist/internal/otel/propagation.mjs.map +1 -0
  63. package/dist/internal/otel/provider.d.mts +106 -0
  64. package/dist/internal/otel/provider.d.mts.map +1 -0
  65. package/dist/internal/otel/provider.mjs +1 -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 +1 -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 +1 -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 -107
  78. package/dist/internal/plugin-runtime.mjs.map +1 -1
  79. package/dist/internal/react-context.d.mts +44 -0
  80. package/dist/internal/react-context.d.mts.map +1 -0
  81. package/dist/internal/react-context.mjs +1 -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 +1 -10
  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 +1 -5
  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 +1 -0
  94. package/dist/internal/wrapper-singleton.mjs.map +1 -0
  95. package/dist/package.mjs +1 -5
  96. package/dist/plugins/errors.d.mts.map +1 -1
  97. package/dist/plugins/errors.mjs +1 -91
  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 +1 -43
  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/lib/types.mjs +1 -1
  106. package/dist/plugins/logs.d.mts +13 -0
  107. package/dist/plugins/logs.d.mts.map +1 -0
  108. package/dist/plugins/logs.mjs +1 -0
  109. package/dist/plugins/logs.mjs.map +1 -0
  110. package/dist/plugins/rage-clicks.d.mts.map +1 -1
  111. package/dist/plugins/rage-clicks.mjs +1 -53
  112. package/dist/plugins/rage-clicks.mjs.map +1 -1
  113. package/dist/plugins/replay.d.mts.map +1 -1
  114. package/dist/plugins/replay.mjs +1 -62
  115. package/dist/plugins/replay.mjs.map +1 -1
  116. package/dist/provider.d.mts +11 -20
  117. package/dist/provider.d.mts.map +1 -1
  118. package/dist/provider.mjs +1 -32
  119. package/dist/provider.mjs.map +1 -1
  120. package/dist/react-error-handler.d.mts +21 -5
  121. package/dist/react-error-handler.d.mts.map +1 -1
  122. package/dist/react-error-handler.mjs +1 -54
  123. package/dist/react-error-handler.mjs.map +1 -1
  124. package/dist/sw.d.mts +2 -0
  125. package/dist/sw.mjs +2 -0
  126. package/dist/tracking/api.d.mts +41 -15
  127. package/dist/tracking/api.d.mts.map +1 -1
  128. package/dist/tracking/api.mjs +1 -134
  129. package/dist/tracking/api.mjs.map +1 -1
  130. package/dist/tracking/device.d.mts +30 -7
  131. package/dist/tracking/device.d.mts.map +1 -1
  132. package/dist/tracking/device.mjs +1 -80
  133. package/dist/tracking/device.mjs.map +1 -1
  134. package/dist/tracking/geo.d.mts +11 -3
  135. package/dist/tracking/geo.d.mts.map +1 -1
  136. package/dist/tracking/geo.mjs +2 -44
  137. package/dist/tracking/geo.mjs.map +1 -1
  138. package/dist/tracking/session.d.mts +3 -1
  139. package/dist/tracking/session.d.mts.map +1 -1
  140. package/dist/tracking/session.mjs +1 -75
  141. package/dist/tracking/session.mjs.map +1 -1
  142. package/dist/util/bot.d.mts +10 -0
  143. package/dist/util/bot.d.mts.map +1 -0
  144. package/dist/util/bot.mjs +1 -0
  145. package/dist/util/bot.mjs.map +1 -0
  146. package/dist/util/global.d.mts +10 -0
  147. package/dist/util/global.d.mts.map +1 -0
  148. package/dist/util/global.mjs +1 -0
  149. package/dist/util/global.mjs.map +1 -0
  150. package/dist/util/log.d.mts.map +1 -1
  151. package/dist/util/log.mjs +1 -37
  152. package/dist/util/log.mjs.map +1 -1
  153. package/dist/util/stringify.d.mts +9 -0
  154. package/dist/util/stringify.d.mts.map +1 -0
  155. package/dist/util/stringify.mjs +1 -0
  156. package/dist/util/stringify.mjs.map +1 -0
  157. package/package.json +79 -25
  158. package/dist/internal/client.d.mts +0 -48
  159. package/dist/internal/client.d.mts.map +0 -1
  160. package/dist/internal/client.mjs +0 -146
  161. package/dist/internal/client.mjs.map +0 -1
  162. package/dist/internal/context.d.mts +0 -6
  163. package/dist/internal/context.d.mts.map +0 -1
  164. package/dist/internal/context.mjs +0 -32
  165. package/dist/internal/context.mjs.map +0 -1
  166. package/dist/internal/envelope.d.mts +0 -15
  167. package/dist/internal/envelope.d.mts.map +0 -1
  168. package/dist/internal/envelope.mjs +0 -24
  169. package/dist/internal/envelope.mjs.map +0 -1
  170. package/dist/internal/errors.d.mts +0 -4
  171. package/dist/internal/errors.d.mts.map +0 -1
  172. package/dist/internal/errors.mjs +0 -4
  173. package/dist/internal/errors.mjs.map +0 -1
  174. package/dist/plugins/device.d.mts +0 -6
  175. package/dist/plugins/device.d.mts.map +0 -1
  176. package/dist/plugins/device.mjs +0 -13
  177. package/dist/plugins/device.mjs.map +0 -1
  178. package/dist/plugins/pages.d.mts +0 -6
  179. package/dist/plugins/pages.d.mts.map +0 -1
  180. package/dist/plugins/pages.mjs +0 -102
  181. package/dist/plugins/pages.mjs.map +0 -1
  182. package/dist/transport/http.d.mts +0 -25
  183. package/dist/transport/http.d.mts.map +0 -1
  184. package/dist/transport/http.mjs +0 -80
  185. package/dist/transport/http.mjs.map +0 -1
  186. package/dist/transport/queue.d.mts +0 -34
  187. package/dist/transport/queue.d.mts.map +0 -1
  188. package/dist/transport/queue.mjs +0 -100
  189. package/dist/transport/queue.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ "use client";import{createContext}from"react";const NULL_CONTEXT_VALUE={consent:{get:()=>null,set:()=>void 0},device:{getDeviceId:()=>null,getFpHash:()=>null},identity:{get:()=>null,set:()=>Promise.resolve()},kernel:null,session:{getId:()=>null,getWindowId:()=>null}},InterfereContext=createContext(null);export{InterfereContext,NULL_CONTEXT_VALUE};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-context.mjs","names":[],"sources":["../../src/internal/react-context.ts"],"sourcesContent":["\"use client\";\n\nimport type { IdentifyParams } from \"@interfere/types/sdk/identify\";\nimport type { ConsentState } from \"@interfere/types/sdk/plugins/manifest\";\n\nimport { createContext } from \"react\";\n\nimport type { Kernel } from \"./kernel.js\";\n\nexport interface InterfereContextValue {\n consent: {\n get(): ConsentState | null;\n set(state?: ConsentState): void;\n };\n device: {\n getDeviceId(): string | null;\n getFpHash(): string | null;\n };\n identity: {\n get(): IdentifyParams | null;\n set(params: IdentifyParams): Promise<void>;\n };\n /**\n * The active kernel, or `null` when the SDK isn't initialized yet — for\n * example during server-side prerender, when `init()` is client-only.\n * Accessor objects (consent/device/identity/session) above stay safe to\n * call in either state; they no-op when the kernel is null.\n */\n kernel: Kernel | null;\n session: {\n getId(): string | null;\n getWindowId(): string | null;\n };\n}\n\nconst NULL_CONSENT = { get: () => null, set: () => undefined };\nconst NULL_DEVICE = { getDeviceId: () => null, getFpHash: () => null };\nconst NULL_IDENTITY = {\n get: () => null,\n set: () => Promise.resolve(),\n};\nconst NULL_SESSION = { getId: () => null, getWindowId: () => null };\n\n/**\n * Context value used when the provider is mounted without a kernel — every\n * accessor returns `null` and mutations are no-ops. Lets `useInterfere()`\n * work seamlessly across SSR/CSR boundaries (server gets nulls, client\n * gets real values once the kernel resolves).\n */\nexport const NULL_CONTEXT_VALUE: InterfereContextValue = {\n consent: NULL_CONSENT,\n device: NULL_DEVICE,\n identity: NULL_IDENTITY,\n kernel: null,\n session: NULL_SESSION,\n};\n\n/**\n * Lives in its own module so `provider.tsx` and `internal/capture-boundary.tsx`\n * can both import it without forming an import cycle.\n */\nexport const InterfereContext = createContext<InterfereContextValue | null>(\n null\n);\n"],"mappings":"8CAiDA,MAAa,mBAA4C,CACvD,QAAS,CAfY,QAAW,KAAM,QAAW,IAAA,EAexC,EACT,OAAQ,CAfY,gBAAmB,KAAM,cAAiB,IAetD,EACR,SAAU,CAdV,QAAW,KACX,QAAW,QAAQ,QAAQ,CAajB,EACV,OAAQ,KACR,QAAS,CAbY,UAAa,KAAM,gBAAmB,IAalD,CACX,EAMa,iBAAmB,cAC9B,IACF"}
@@ -1,4 +1,24 @@
1
1
  //#region src/internal/sw.d.ts
2
- declare function registerServiceWorker(): void;
2
+ interface SwRegistrationOptions {
3
+ /**
4
+ * Optional hook to bridge SW → kernel telemetry. The SW posts
5
+ * `{ type: "interfere.sw.queued" | ... }` messages back to the
6
+ * page when it queues, replays, or drops a request; if provided,
7
+ * this fires for each one so the kernel can record an OTel span
8
+ * event.
9
+ */
10
+ onMessage?: (message: SwMessage) => void;
11
+ }
12
+ interface SwMessage {
13
+ reason?: string;
14
+ type: "interfere.sw.queued" | "interfere.sw.replayed" | "interfere.sw.dropped";
15
+ url: string;
16
+ }
17
+ /**
18
+ * Best-effort: never throws, never blocks. SW registration is a
19
+ * page-load-time durability bonus; if it fails, the SDK falls back to
20
+ * direct fetch and behaves exactly like 9.x did.
21
+ */
22
+ declare function registerServiceWorker(opts?: SwRegistrationOptions): Promise<void>;
3
23
  //#endregion
4
- export { registerServiceWorker };
24
+ export { SwMessage, SwRegistrationOptions, registerServiceWorker };
@@ -1 +1 @@
1
- {"version":3,"file":"sw.d.mts","names":[],"sources":["../../src/internal/sw.ts"],"mappings":";iBAMgB,qBAAA,CAAA"}
1
+ {"version":3,"file":"sw.d.mts","names":[],"sources":["../../src/internal/sw.ts"],"mappings":";UAMiB,qBAAA;EAAA;;;;;;;EAQf,SAAA,IAAa,OAAA,EAAS,SAAS;AAAA;AAAA,UAGhB,SAAA;EACf,MAAA;EACA,IAAA;EAIA,GAAA;AAAA;;;;AAAG;AAQL;iBAAsB,qBAAA,CACpB,IAAA,GAAM,qBAAA,GACL,OAAO"}
@@ -1,10 +1 @@
1
- import { createLogger } from "../util/log.mjs";
2
- //#region src/internal/sw.ts
3
- const log = createLogger("sw");
4
- const SW_PATH = "/api/interfere/sw";
5
- function registerServiceWorker() {
6
- if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return;
7
- navigator.serviceWorker.register(SW_PATH, { scope: "/api/interfere/" }).then(() => log.debug("registered")).catch(() => log.warn("registration failed, using direct fetch"));
8
- }
9
- //#endregion
10
- export { registerServiceWorker };
1
+ import{createLogger}from"../util/log.mjs";import{resolveRoutePrefix}from"@interfere/constants/route-prefix";const log=createLogger(`sw`);async function registerServiceWorker(opts={}){if(typeof navigator>`u`||!(`serviceWorker`in navigator))return;let prefix=resolveRoutePrefix(),swPath=`${prefix}/sw`,swScope=`${prefix}/`;try{await navigator.serviceWorker.register(swPath,{scope:swScope}),log.debug(`registered at %s (scope %s)`,swPath,swScope)}catch(err){log.warn(`registration failed, falling back to direct fetch`,err);return}opts.onMessage&&navigator.serviceWorker.addEventListener(`message`,event=>{let data=event.data;isSwMessage(data)&&opts.onMessage?.(data)})}function isSwMessage(value){if(!value||typeof value!=`object`)return!1;let v=value;return(v.type===`interfere.sw.queued`||v.type===`interfere.sw.replayed`||v.type===`interfere.sw.dropped`)&&typeof v.url==`string`}export{registerServiceWorker};
@@ -1 +1 @@
1
- {"version":3,"file":"sw.mjs","names":[],"sources":["../../src/internal/sw.ts"],"sourcesContent":["import { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"sw\");\n\nconst SW_PATH = \"/api/interfere/sw\";\n\nexport function registerServiceWorker(): void {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return;\n }\n\n navigator.serviceWorker\n .register(SW_PATH, { scope: \"/api/interfere/\" })\n .then(() => log.debug(\"registered\"))\n .catch(() => log.warn(\"registration failed, using direct fetch\"));\n}\n"],"mappings":";;AAEA,MAAM,MAAM,aAAa,KAAK;AAE9B,MAAM,UAAU;AAEhB,SAAgB,wBAA8B;AAC5C,KAAI,OAAO,cAAc,eAAe,EAAE,mBAAmB,WAC3D;AAGF,WAAU,cACP,SAAS,SAAS,EAAE,OAAO,mBAAmB,CAAC,CAC/C,WAAW,IAAI,MAAM,aAAa,CAAC,CACnC,YAAY,IAAI,KAAK,0CAA0C,CAAC"}
1
+ {"version":3,"file":"sw.mjs","names":[],"sources":["../../src/internal/sw.ts"],"sourcesContent":["import { resolveRoutePrefix } from \"@interfere/constants/route-prefix\";\n\nimport { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"sw\");\n\nexport interface SwRegistrationOptions {\n /**\n * Optional hook to bridge SW → kernel telemetry. The SW posts\n * `{ type: \"interfere.sw.queued\" | ... }` messages back to the\n * page when it queues, replays, or drops a request; if provided,\n * this fires for each one so the kernel can record an OTel span\n * event.\n */\n onMessage?: (message: SwMessage) => void;\n}\n\nexport interface SwMessage {\n reason?: string;\n type:\n | \"interfere.sw.queued\"\n | \"interfere.sw.replayed\"\n | \"interfere.sw.dropped\";\n url: string;\n}\n\n/**\n * Best-effort: never throws, never blocks. SW registration is a\n * page-load-time durability bonus; if it fails, the SDK falls back to\n * direct fetch and behaves exactly like 9.x did.\n */\nexport async function registerServiceWorker(\n opts: SwRegistrationOptions = {}\n): Promise<void> {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return;\n }\n\n // Customers can override the proxy mount point via\n // `NEXT_PUBLIC_INTERFERE_ROUTE_PREFIX` (homepage / dashboard both do\n // this). Hardcoding `/api/interfere/sw` would 404 on those surfaces\n // and Chrome stalls the install handler when the SW script can't be\n // fetched, so we resolve the same prefix the rest of the SDK uses.\n const prefix = resolveRoutePrefix();\n const swPath = `${prefix}/sw`;\n const swScope = `${prefix}/`;\n\n try {\n await navigator.serviceWorker.register(swPath, { scope: swScope });\n log.debug(\"registered at %s (scope %s)\", swPath, swScope);\n } catch (err) {\n log.warn(\"registration failed, falling back to direct fetch\", err);\n return;\n }\n\n if (opts.onMessage) {\n const handler = (event: MessageEvent<unknown>) => {\n const data = event.data;\n if (!isSwMessage(data)) {\n return;\n }\n opts.onMessage?.(data);\n };\n navigator.serviceWorker.addEventListener(\"message\", handler);\n }\n}\n\nfunction isSwMessage(value: unknown): value is SwMessage {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const v = value as Record<string, unknown>;\n return (\n (v[\"type\"] === \"interfere.sw.queued\" ||\n v[\"type\"] === \"interfere.sw.replayed\" ||\n v[\"type\"] === \"interfere.sw.dropped\") &&\n typeof v[\"url\"] === \"string\"\n );\n}\n"],"mappings":"4GAIA,MAAM,IAAM,aAAa,IAAI,EA2B7B,eAAsB,sBACpB,KAA8B,CAAC,EAChB,CACf,GAAI,OAAO,UAAc,KAAe,EAAE,kBAAmB,WAC3D,OAQF,IAAM,OAAS,mBAAmB,EAC5B,OAAS,GAAG,OAAO,KACnB,QAAU,GAAG,OAAO,GAE1B,GAAI,CACF,MAAM,UAAU,cAAc,SAAS,OAAQ,CAAE,MAAO,OAAQ,CAAC,EACjE,IAAI,MAAM,8BAA+B,OAAQ,OAAO,CAC1D,OAAS,IAAK,CACZ,IAAI,KAAK,oDAAqD,GAAG,EACjE,MACF,CAEI,KAAK,WAQP,UAAU,cAAc,iBAAiB,UAPxB,OAAiC,CAChD,IAAM,KAAO,MAAM,KACd,YAAY,IAAI,GAGrB,KAAK,YAAY,IAAI,CACvB,CAC2D,CAE/D,CAEA,SAAS,YAAY,MAAoC,CACvD,GAAI,CAAC,OAAS,OAAO,OAAU,SAC7B,MAAO,GAET,IAAM,EAAI,MACV,OACG,EAAE,OAAY,uBACb,EAAE,OAAY,yBACd,EAAE,OAAY,yBAChB,OAAO,EAAE,KAAW,QAExB"}
@@ -1,4 +1,6 @@
1
1
  //#region src/internal/version.d.ts
2
+ declare const SDK_NAME: string;
3
+ declare const SDK_VERSION: string;
2
4
  declare const PRODUCER_VERSION: string;
3
5
  //#endregion
4
- export { PRODUCER_VERSION };
6
+ export { PRODUCER_VERSION, SDK_NAME, SDK_VERSION };
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.mts","names":[],"sources":["../../src/internal/version.ts"],"mappings":";cAEa,gBAAA"}
1
+ {"version":3,"file":"version.d.mts","names":[],"sources":["../../src/internal/version.ts"],"mappings":";cAEa,QAAA;AAAA,cACA,WAAA;AAAA,cACA,gBAAA"}
@@ -1,5 +1 @@
1
- import { name, version } from "../package.mjs";
2
- //#region src/internal/version.ts
3
- const PRODUCER_VERSION = `${name}@${version}`;
4
- //#endregion
5
- export { PRODUCER_VERSION };
1
+ import{name,version}from"../package.mjs";const SDK_NAME=name,SDK_VERSION=version,PRODUCER_VERSION=`${SDK_NAME}@${SDK_VERSION}`;export{PRODUCER_VERSION,SDK_NAME,SDK_VERSION};
@@ -1 +1 @@
1
- {"version":3,"file":"version.mjs","names":["pkg.name","pkg.version"],"sources":["../../src/internal/version.ts"],"sourcesContent":["import pkg from \"../../package.json\" with { type: \"json\" };\n\nexport const PRODUCER_VERSION = `${pkg.name}@${pkg.version}`;\n"],"mappings":";;AAEA,MAAa,mBAAmB,GAAGA,KAAS,GAAGC"}
1
+ {"version":3,"file":"version.mjs","names":["pkg.name","pkg.version"],"sources":["../../src/internal/version.ts"],"sourcesContent":["import pkg from \"../../package.json\" with { type: \"json\" };\n\nexport const SDK_NAME = pkg.name;\nexport const SDK_VERSION = pkg.version;\nexport const PRODUCER_VERSION = `${SDK_NAME}@${SDK_VERSION}`;\n"],"mappings":"yCAEA,MAAa,SAAWA,KACX,YAAcC,QACd,iBAAmB,GAAG,SAAS,GAAG"}
@@ -0,0 +1,47 @@
1
+ import { Kernel, KernelOptions } from "./kernel.mjs";
2
+ import { ConsentState } from "@interfere/types/sdk/plugins/manifest";
3
+ import { IdentifyParams } from "@interfere/types/sdk/identify";
4
+
5
+ //#region src/internal/wrapper-singleton.d.ts
6
+ interface WrapperSingleton {
7
+ close(): Promise<void>;
8
+ consent: {
9
+ get(): ConsentState | null;
10
+ set(value?: ConsentState): void;
11
+ };
12
+ getKernel(): Kernel;
13
+ getKernelOrNull(): Kernel | null;
14
+ identity: {
15
+ get(): IdentifyParams | null;
16
+ set(params: IdentifyParams): Promise<void>;
17
+ };
18
+ init(opts?: KernelOptions): Promise<Kernel | null>;
19
+ subscribeToKernel(listener: () => void): () => void;
20
+ }
21
+ interface WrapperSingletonInput {
22
+ /**
23
+ * Surfaced in the "kernel not initialized" error so the message points
24
+ * customers at the right entry file (e.g. "instrumentation-client" /
25
+ * "main.ts"). Single concrete fact — no other behavior change.
26
+ */
27
+ initEntryName: string;
28
+ /**
29
+ * Wrapper SDK identifier injected as the first entry in
30
+ * `__INTERFERE_SDK_STACK__` (e.g. `@interfere/next@10.0.0`). Threaded
31
+ * through to the kernel so the OTel resource carries the full stack.
32
+ */
33
+ producerVersion: string;
34
+ }
35
+ /**
36
+ * Per-wrapper kernel-singleton lifecycle. The next/vite/etc wrappers used
37
+ * to copy the same ~100 lines verbatim (init/close/getKernel/subscribe
38
+ * /consent/identity); this is the single source of truth so a fix once
39
+ * applies everywhere.
40
+ *
41
+ * Each wrapper holds its own `WrapperSingleton` in module scope so the
42
+ * kernel is per-wrapper, not per-process. (Microfrontends can run two
43
+ * different wrappers on the same page; each gets its own kernel.)
44
+ */
45
+ declare function createWrapperSingleton(input: WrapperSingletonInput): WrapperSingleton;
46
+ //#endregion
47
+ export { WrapperSingleton, WrapperSingletonInput, createWrapperSingleton };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapper-singleton.d.mts","names":[],"sources":["../../src/internal/wrapper-singleton.ts"],"mappings":";;;;;UAaiB,gBAAA;EACf,KAAA,IAAS,OAAA;EACT,OAAA;IACE,GAAA,IAAO,YAAA;IACP,GAAA,CAAI,KAAA,GAAQ,YAAA;EAAA;EAEd,SAAA,IAAa,MAAA;EACb,eAAA,IAAmB,MAAA;EACnB,QAAA;IACE,GAAA,IAAO,cAAA;IACP,GAAA,CAAI,MAAA,EAAQ,cAAA,GAAiB,OAAA;EAAA;EAE/B,IAAA,CAAK,IAAA,GAAO,aAAA,GAAgB,OAAA,CAAQ,MAAA;EACpC,iBAAA,CAAkB,QAAA;AAAA;AAAA,UAGH,qBAAA;EAJoB;;;;;EAUnC,aAAA;EAnBS;;;;;EAyBT,eAAe;AAAA;;;;;;;;;;;iBAaD,sBAAA,CACd,KAAA,EAAO,qBAAA,GACN,gBAAgB"}
@@ -0,0 +1 @@
1
+ import{registerKernel,unregisterKernel}from"./kernel-registry.mjs";import{createKernel,isEnabledByEnvironment}from"./kernel.mjs";import{isBotUserAgent}from"../util/bot.mjs";function createWrapperSingleton(input){let kernel=null,pending=null,listeners=new Set;function emit(){for(let listener of listeners)listener()}return{init(opts={}){return kernel?Promise.resolve(kernel):pending||(!(opts.enabled??isEnabledByEnvironment())||isBotUserAgent()?Promise.resolve(null):(pending=createKernel({opts:{...opts,_wrapperVersions:[input.producerVersion]}}).then(k=>(kernel=k,k&&registerKernel(k),emit(),k)).finally(()=>{pending=null}),pending))},getKernel(){if(!kernel)throw Error(`Interfere SDK not initialized. Call init() from your ${input.initEntryName} entrypoint.`);return kernel},getKernelOrNull(){return kernel},async close(){if(!kernel)return;let previous=kernel;await previous.dispose(),kernel=null,unregisterKernel(previous),emit()},subscribeToKernel(listener){return listeners.add(listener),()=>{listeners.delete(listener)}},consent:{get:()=>kernel?.consent.get()??null,set:value=>kernel?.consent.set(value)},identity:{get:()=>kernel?.identity.get()??null,set:params=>kernel?.identity.set(params)??Promise.resolve()}}}export{createWrapperSingleton};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapper-singleton.mjs","names":[],"sources":["../../src/internal/wrapper-singleton.ts"],"sourcesContent":["import type { IdentifyParams } from \"@interfere/types/sdk/identify\";\nimport type { ConsentState } from \"@interfere/types/sdk/plugins/manifest\";\n\nimport { isBotUserAgent } from \"../util/bot.js\";\nimport {\n createKernel,\n isEnabledByEnvironment,\n type Kernel,\n type KernelInternalOptions,\n type KernelOptions,\n} from \"./kernel.js\";\nimport { registerKernel, unregisterKernel } from \"./kernel-registry.js\";\n\nexport interface WrapperSingleton {\n close(): Promise<void>;\n consent: {\n get(): ConsentState | null;\n set(value?: ConsentState): void;\n };\n getKernel(): Kernel;\n getKernelOrNull(): Kernel | null;\n identity: {\n get(): IdentifyParams | null;\n set(params: IdentifyParams): Promise<void>;\n };\n init(opts?: KernelOptions): Promise<Kernel | null>;\n subscribeToKernel(listener: () => void): () => void;\n}\n\nexport interface WrapperSingletonInput {\n /**\n * Surfaced in the \"kernel not initialized\" error so the message points\n * customers at the right entry file (e.g. \"instrumentation-client\" /\n * \"main.ts\"). Single concrete fact — no other behavior change.\n */\n initEntryName: string;\n /**\n * Wrapper SDK identifier injected as the first entry in\n * `__INTERFERE_SDK_STACK__` (e.g. `@interfere/next@10.0.0`). Threaded\n * through to the kernel so the OTel resource carries the full stack.\n */\n producerVersion: string;\n}\n\n/**\n * Per-wrapper kernel-singleton lifecycle. The next/vite/etc wrappers used\n * to copy the same ~100 lines verbatim (init/close/getKernel/subscribe\n * /consent/identity); this is the single source of truth so a fix once\n * applies everywhere.\n *\n * Each wrapper holds its own `WrapperSingleton` in module scope so the\n * kernel is per-wrapper, not per-process. (Microfrontends can run two\n * different wrappers on the same page; each gets its own kernel.)\n */\nexport function createWrapperSingleton(\n input: WrapperSingletonInput\n): WrapperSingleton {\n let kernel: Kernel | null = null;\n let pending: Promise<Kernel | null> | null = null;\n const listeners = new Set<() => void>();\n\n function emit(): void {\n for (const listener of listeners) {\n listener();\n }\n }\n\n return {\n init(opts: KernelOptions = {}): Promise<Kernel | null> {\n if (kernel) {\n return Promise.resolve(kernel);\n }\n if (pending) {\n return pending;\n }\n if (!(opts.enabled ?? isEnabledByEnvironment())) {\n return Promise.resolve(null);\n }\n if (isBotUserAgent()) {\n return Promise.resolve(null);\n }\n\n const internalOpts: KernelInternalOptions = {\n ...opts,\n _wrapperVersions: [input.producerVersion],\n };\n pending = createKernel({ opts: internalOpts })\n .then((k) => {\n kernel = k;\n if (k) {\n registerKernel(k);\n }\n emit();\n return k;\n })\n .finally(() => {\n pending = null;\n });\n\n return pending;\n },\n\n getKernel(): Kernel {\n if (!kernel) {\n throw new Error(\n `Interfere SDK not initialized. Call init() from your ${input.initEntryName} entrypoint.`\n );\n }\n return kernel;\n },\n\n getKernelOrNull(): Kernel | null {\n return kernel;\n },\n\n async close(): Promise<void> {\n if (!kernel) {\n return;\n }\n const previous = kernel;\n await previous.dispose();\n kernel = null;\n unregisterKernel(previous);\n emit();\n },\n\n subscribeToKernel(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n\n consent: {\n get: () => kernel?.consent.get() ?? null,\n set: (value) => kernel?.consent.set(value),\n },\n\n identity: {\n get: () => kernel?.identity.get() ?? null,\n set: (params) => kernel?.identity.set(params) ?? Promise.resolve(),\n },\n };\n}\n"],"mappings":"6KAsDA,SAAgB,uBACd,MACkB,CAClB,IAAI,OAAwB,KACxB,QAAyC,KACvC,UAAY,IAAI,IAEtB,SAAS,MAAa,CACpB,IAAK,IAAM,YAAY,UACrB,SAAS,CAEb,CAEA,MAAO,CACL,KAAK,KAAsB,CAAC,EAA2B,CA+BrD,OA9BI,OACK,QAAQ,QAAQ,MAAM,EAE3B,UAGA,EAAE,KAAK,SAAW,uBAAuB,IAGzC,eAAe,EACV,QAAQ,QAAQ,IAAI,GAO7B,QAAU,aAAa,CAAE,KAAM,CAH7B,GAAG,KACH,iBAAkB,CAAC,MAAM,eAAe,CAEA,CAAE,CAAC,EAC1C,KAAM,IACL,OAAS,EACL,GACF,eAAe,CAAC,EAElB,KAAK,EACE,EACR,EACA,YAAc,CACb,QAAU,IACZ,CAAC,EAEI,SACT,EAEA,WAAoB,CAClB,GAAI,CAAC,OACH,MAAU,MACR,wDAAwD,MAAM,cAAc,aAC9E,EAEF,OAAO,MACT,EAEA,iBAAiC,CAC/B,OAAO,MACT,EAEA,MAAM,OAAuB,CAC3B,GAAI,CAAC,OACH,OAEF,IAAM,SAAW,OACjB,MAAM,SAAS,QAAQ,EACvB,OAAS,KACT,iBAAiB,QAAQ,EACzB,KAAK,CACP,EAEA,kBAAkB,SAAkC,CAElD,OADA,UAAU,IAAI,QAAQ,MACT,CACX,UAAU,OAAO,QAAQ,CAC3B,CACF,EAEA,QAAS,CACP,QAAW,QAAQ,QAAQ,IAAI,GAAK,KACpC,IAAM,OAAU,QAAQ,QAAQ,IAAI,KAAK,CAC3C,EAEA,SAAU,CACR,QAAW,QAAQ,SAAS,IAAI,GAAK,KACrC,IAAM,QAAW,QAAQ,SAAS,IAAI,MAAM,GAAK,QAAQ,QAAQ,CACnE,CACF,CACF"}
package/dist/package.mjs CHANGED
@@ -1,5 +1 @@
1
- //#region package.json
2
- var name = "@interfere/react";
3
- var version = "9.0.2";
4
- //#endregion
5
- export { name, version };
1
+ var name=`@interfere/react`,version=`10.0.1-canary.0`;export{name,version};
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.mts","names":[],"sources":["../../src/plugins/errors.ts"],"mappings":";;;cA2Ea,YAAA,EAAc,MAAA"}
1
+ {"version":3,"file":"errors.d.mts","names":[],"sources":["../../src/plugins/errors.ts"],"mappings":";;;cAwDa,YAAA,EAAc,MA0F1B"}
@@ -1,91 +1 @@
1
- import { seen } from "../internal/errors.mjs";
2
- import { MECHANISM_TYPE, shouldDropBrowserExtensionNoise, shouldDropUnresolvableStack, toError, toExceptions } from "@interfere/types/sdk/errors";
3
- //#region src/plugins/errors.ts
4
- /**
5
- * V8's default stack limit is 10. Deep React trees routinely exceed that
6
- * before reaching the actual application frames, leaving only react-dom
7
- * internals in the captured stack. Matches Sentry's browser SDK.
8
- */
9
- const STACK_TRACE_LIMIT = 50;
10
- let capturing = false;
11
- function capture(ctx, opts) {
12
- if (capturing || seen.has(opts.error)) return;
13
- seen.add(opts.error);
14
- capturing = true;
15
- try {
16
- const exceptions = toExceptions(opts.error, opts.mechanism);
17
- if (opts.fallbackFrame && exceptions[0]?.frames.length === 0) exceptions[0].frames.push(opts.fallbackFrame);
18
- if (shouldDropBrowserExtensionNoise(exceptions) || shouldDropUnresolvableStack(exceptions)) return;
19
- ctx.capture("error", { exceptions });
20
- } finally {
21
- capturing = false;
22
- }
23
- }
24
- /**
25
- * Finds the first `Error` instance in a list of `console.error` arguments.
26
- * React 18+ in production logs errors as
27
- * `console.error("The above error occurred in ...", error, componentStack)`,
28
- * so scanning only `args[0]` misses React's own uncaught-error reports.
29
- */
30
- function findErrorArg(args) {
31
- for (const arg of args) if (arg instanceof Error) return arg;
32
- return null;
33
- }
34
- const errorsPlugin = {
35
- name: "errors",
36
- setup(ctx) {
37
- const originalOnError = globalThis.onerror;
38
- const originalConsoleError = globalThis.console.error;
39
- const originalStackTraceLimit = Error.stackTraceLimit;
40
- if (Error.stackTraceLimit < STACK_TRACE_LIMIT) Error.stackTraceLimit = STACK_TRACE_LIMIT;
41
- globalThis.onerror = (msg, source, line, col, error) => {
42
- if (error instanceof Error) {
43
- const fallbackFrame = typeof source === "string" ? {
44
- fileName: source,
45
- ...typeof line === "number" ? { lineNumber: line } : {},
46
- ...typeof col === "number" ? { columnNumber: col } : {}
47
- } : null;
48
- capture(ctx, {
49
- error,
50
- mechanism: {
51
- type: MECHANISM_TYPE.browser.onerror,
52
- handled: false
53
- },
54
- ...fallbackFrame ? { fallbackFrame } : {}
55
- });
56
- }
57
- if (typeof originalOnError === "function") return originalOnError.call(globalThis, msg, source, line, col, error);
58
- return false;
59
- };
60
- const onUnhandledRejection = (event) => {
61
- capture(ctx, {
62
- error: toError(event.reason),
63
- mechanism: {
64
- type: MECHANISM_TYPE.browser.onunhandledrejection,
65
- handled: false,
66
- ...event.reason instanceof Error ? {} : { synthetic: true }
67
- }
68
- });
69
- };
70
- globalThis.addEventListener("unhandledrejection", onUnhandledRejection);
71
- globalThis.console.error = (...args) => {
72
- originalConsoleError.apply(globalThis.console, args);
73
- const error = findErrorArg(args);
74
- if (error) capture(ctx, {
75
- error,
76
- mechanism: {
77
- type: MECHANISM_TYPE.browser.consoleError,
78
- handled: true
79
- }
80
- });
81
- };
82
- return () => {
83
- Error.stackTraceLimit = originalStackTraceLimit;
84
- globalThis.onerror = originalOnError;
85
- globalThis.removeEventListener("unhandledrejection", onUnhandledRejection);
86
- globalThis.console.error = originalConsoleError;
87
- };
88
- }
89
- };
90
- //#endregion
91
- export { errorsPlugin as default, errorsPlugin };
1
+ import{onConsoleCall}from"../internal/console-patch.mjs";import{MECHANISM_TYPE,isNonErrorException,toException}from"@interfere/types/sdk/errors";function capture(ctx,opts){ctx.recordException(opts.error,{mechanism:opts.mechanism,...opts.fallbackFrame&&!isNonErrorException(opts.error)?{fallbackFrames:[opts.fallbackFrame]}:{}})}function findErrorArg(args){for(let arg of args)if(arg instanceof Error)return arg;return null}const errorsPlugin={name:`errors`,setup(ctx){let originalOnError=globalThis.onerror,originalStackTraceLimit=Error.stackTraceLimit;Error.stackTraceLimit<50&&(Error.stackTraceLimit=50),globalThis.onerror=(msg,source,line,col,error)=>{if(error instanceof Error){let fallbackFrame=typeof source==`string`?{id:`fallback-0`,file:source,...typeof line==`number`?{line}:{},...typeof col==`number`?{column:col}:{}}:null;capture(ctx,{error,mechanism:{type:MECHANISM_TYPE.browser.onerror,handled:!1},...fallbackFrame?{fallbackFrame}:{}})}return typeof originalOnError==`function`?originalOnError.call(globalThis,msg,source,line,col,error):!1};let onUnhandledRejection=event=>{capture(ctx,{error:toException(event.reason),mechanism:{type:MECHANISM_TYPE.browser.onunhandledrejection,handled:!1,...event.reason instanceof Error?{}:{synthetic:!0}}})},supportsEventTarget=typeof globalThis.addEventListener==`function`&&typeof globalThis.removeEventListener==`function`;supportsEventTarget&&globalThis.addEventListener(`unhandledrejection`,onUnhandledRejection);let unsubscribeConsole=onConsoleCall((level,args)=>{if(level!==`error`)return;let error=findErrorArg(args);error&&capture(ctx,{error,mechanism:{type:MECHANISM_TYPE.browser.consoleError,handled:!0}})});return()=>{Error.stackTraceLimit=originalStackTraceLimit,globalThis.onerror=originalOnError,supportsEventTarget&&globalThis.removeEventListener(`unhandledrejection`,onUnhandledRejection),unsubscribeConsole()}}};export{errorsPlugin as default,errorsPlugin};
@@ -1 +1 @@
1
- {"version":3,"file":"errors.mjs","names":[],"sources":["../../src/plugins/errors.ts"],"sourcesContent":["import type { IngestedFrame } from \"@interfere/types/data/frame\";\nimport {\n MECHANISM_TYPE,\n shouldDropBrowserExtensionNoise,\n shouldDropUnresolvableStack,\n toError,\n toExceptions,\n} from \"@interfere/types/sdk/errors\";\nimport type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport { seen } from \"../internal/errors.js\";\nimport type { Plugin, PluginContext } from \"./lib/types.js\";\n\n/**\n * V8's default stack limit is 10. Deep React trees routinely exceed that\n * before reaching the actual application frames, leaving only react-dom\n * internals in the captured stack. Matches Sentry's browser SDK.\n */\nconst STACK_TRACE_LIMIT = 50;\n\nlet capturing = false;\n\ninterface CaptureOpts {\n readonly error: Error;\n /**\n * Fallback frame to inject when the parsed stack of the root exception is\n * empty. Used for `window.onerror` calls where the browser provides\n * `source`/`line`/`col` even though the Error object itself has a\n * degenerate stack.\n */\n readonly fallbackFrame?: IngestedFrame;\n readonly mechanism: ErrorMechanism;\n}\n\nfunction capture(ctx: PluginContext, opts: CaptureOpts) {\n if (capturing || seen.has(opts.error)) {\n return;\n }\n\n seen.add(opts.error);\n capturing = true;\n try {\n const exceptions = toExceptions(opts.error, opts.mechanism);\n\n if (opts.fallbackFrame && exceptions[0]?.frames.length === 0) {\n exceptions[0].frames.push(opts.fallbackFrame);\n }\n\n if (\n shouldDropBrowserExtensionNoise(exceptions) ||\n shouldDropUnresolvableStack(exceptions)\n ) {\n return;\n }\n ctx.capture(\"error\", { exceptions });\n } finally {\n capturing = false;\n }\n}\n\n/**\n * Finds the first `Error` instance in a list of `console.error` arguments.\n * React 18+ in production logs errors as\n * `console.error(\"The above error occurred in ...\", error, componentStack)`,\n * so scanning only `args[0]` misses React's own uncaught-error reports.\n */\nfunction findErrorArg(args: readonly unknown[]): Error | null {\n for (const arg of args) {\n if (arg instanceof Error) {\n return arg;\n }\n }\n return null;\n}\n\nexport const errorsPlugin: Plugin = {\n name: \"errors\",\n\n setup(ctx) {\n const originalOnError = globalThis.onerror;\n const originalConsoleError = globalThis.console.error;\n const originalStackTraceLimit = Error.stackTraceLimit;\n if (Error.stackTraceLimit < STACK_TRACE_LIMIT) {\n Error.stackTraceLimit = STACK_TRACE_LIMIT;\n }\n\n globalThis.onerror = (msg, source, line, col, error) => {\n if (error instanceof Error) {\n const fallbackFrame =\n typeof source === \"string\"\n ? {\n fileName: source,\n ...(typeof line === \"number\" ? { lineNumber: line } : {}),\n ...(typeof col === \"number\" ? { columnNumber: col } : {}),\n }\n : null;\n\n capture(ctx, {\n error,\n mechanism: { type: MECHANISM_TYPE.browser.onerror, handled: false },\n ...(fallbackFrame ? { fallbackFrame } : {}),\n });\n }\n if (typeof originalOnError === \"function\") {\n return originalOnError.call(globalThis, msg, source, line, col, error);\n }\n return false;\n };\n\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n capture(ctx, {\n error: toError(event.reason),\n mechanism: {\n type: MECHANISM_TYPE.browser.onunhandledrejection,\n handled: false,\n ...(event.reason instanceof Error ? {} : { synthetic: true }),\n },\n });\n };\n globalThis.addEventListener(\"unhandledrejection\", onUnhandledRejection);\n\n globalThis.console.error = (...args: unknown[]) => {\n originalConsoleError.apply(globalThis.console, args);\n const error = findErrorArg(args);\n if (error) {\n capture(ctx, {\n error,\n mechanism: {\n type: MECHANISM_TYPE.browser.consoleError,\n handled: true,\n },\n });\n }\n };\n\n return () => {\n Error.stackTraceLimit = originalStackTraceLimit;\n globalThis.onerror = originalOnError;\n globalThis.removeEventListener(\n \"unhandledrejection\",\n onUnhandledRejection\n );\n globalThis.console.error = originalConsoleError;\n };\n },\n};\n\nexport default errorsPlugin;\n"],"mappings":";;;;;;;;AAkBA,MAAM,oBAAoB;AAE1B,IAAI,YAAY;AAchB,SAAS,QAAQ,KAAoB,MAAmB;AACtD,KAAI,aAAa,KAAK,IAAI,KAAK,MAAM,CACnC;AAGF,MAAK,IAAI,KAAK,MAAM;AACpB,aAAY;AACZ,KAAI;EACF,MAAM,aAAa,aAAa,KAAK,OAAO,KAAK,UAAU;AAE3D,MAAI,KAAK,iBAAiB,WAAW,IAAI,OAAO,WAAW,EACzD,YAAW,GAAG,OAAO,KAAK,KAAK,cAAc;AAG/C,MACE,gCAAgC,WAAW,IAC3C,4BAA4B,WAAW,CAEvC;AAEF,MAAI,QAAQ,SAAS,EAAE,YAAY,CAAC;WAC5B;AACR,cAAY;;;;;;;;;AAUhB,SAAS,aAAa,MAAwC;AAC5D,MAAK,MAAM,OAAO,KAChB,KAAI,eAAe,MACjB,QAAO;AAGX,QAAO;;AAGT,MAAa,eAAuB;CAClC,MAAM;CAEN,MAAM,KAAK;EACT,MAAM,kBAAkB,WAAW;EACnC,MAAM,uBAAuB,WAAW,QAAQ;EAChD,MAAM,0BAA0B,MAAM;AACtC,MAAI,MAAM,kBAAkB,kBAC1B,OAAM,kBAAkB;AAG1B,aAAW,WAAW,KAAK,QAAQ,MAAM,KAAK,UAAU;AACtD,OAAI,iBAAiB,OAAO;IAC1B,MAAM,gBACJ,OAAO,WAAW,WACd;KACE,UAAU;KACV,GAAI,OAAO,SAAS,WAAW,EAAE,YAAY,MAAM,GAAG,EAAE;KACxD,GAAI,OAAO,QAAQ,WAAW,EAAE,cAAc,KAAK,GAAG,EAAE;KACzD,GACD;AAEN,YAAQ,KAAK;KACX;KACA,WAAW;MAAE,MAAM,eAAe,QAAQ;MAAS,SAAS;MAAO;KACnE,GAAI,gBAAgB,EAAE,eAAe,GAAG,EAAE;KAC3C,CAAC;;AAEJ,OAAI,OAAO,oBAAoB,WAC7B,QAAO,gBAAgB,KAAK,YAAY,KAAK,QAAQ,MAAM,KAAK,MAAM;AAExE,UAAO;;EAGT,MAAM,wBAAwB,UAAiC;AAC7D,WAAQ,KAAK;IACX,OAAO,QAAQ,MAAM,OAAO;IAC5B,WAAW;KACT,MAAM,eAAe,QAAQ;KAC7B,SAAS;KACT,GAAI,MAAM,kBAAkB,QAAQ,EAAE,GAAG,EAAE,WAAW,MAAM;KAC7D;IACF,CAAC;;AAEJ,aAAW,iBAAiB,sBAAsB,qBAAqB;AAEvE,aAAW,QAAQ,SAAS,GAAG,SAAoB;AACjD,wBAAqB,MAAM,WAAW,SAAS,KAAK;GACpD,MAAM,QAAQ,aAAa,KAAK;AAChC,OAAI,MACF,SAAQ,KAAK;IACX;IACA,WAAW;KACT,MAAM,eAAe,QAAQ;KAC7B,SAAS;KACV;IACF,CAAC;;AAIN,eAAa;AACX,SAAM,kBAAkB;AACxB,cAAW,UAAU;AACrB,cAAW,oBACT,sBACA,qBACD;AACD,cAAW,QAAQ,QAAQ;;;CAGhC"}
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../../src/plugins/errors.ts"],"sourcesContent":["import type { IngestedFrame } from \"@interfere/types/data/frame\";\nimport {\n isNonErrorException,\n MECHANISM_TYPE,\n type NonErrorException,\n toException,\n} from \"@interfere/types/sdk/errors\";\nimport type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport { onConsoleCall } from \"../internal/console-patch.js\";\nimport type { Plugin, PluginContext } from \"./lib/types.js\";\n\n/**\n * V8's default stack limit is 10. Deep React trees routinely exceed that\n * before reaching the actual application frames, leaving only react-dom\n * internals in the captured stack. Matches Sentry's browser SDK.\n */\nconst STACK_TRACE_LIMIT = 50;\n\ninterface CaptureOpts {\n readonly error: Error | NonErrorException;\n /**\n * Fallback frame to inject when the parsed stack of the root exception is\n * empty. Used for `window.onerror` calls where the browser provides\n * `source`/`line`/`col` even though the Error object itself has a\n * degenerate stack. Ignored for non-Error captures — there's no stack\n * to fall back into.\n */\n readonly fallbackFrame?: IngestedFrame;\n readonly mechanism: ErrorMechanism;\n}\n\nfunction capture(ctx: PluginContext, opts: CaptureOpts) {\n ctx.recordException(opts.error, {\n mechanism: opts.mechanism,\n ...(opts.fallbackFrame && !isNonErrorException(opts.error)\n ? { fallbackFrames: [opts.fallbackFrame] }\n : {}),\n });\n}\n\n/**\n * Finds the first `Error` instance in a list of `console.error` arguments.\n * React 18+ in production logs errors as\n * `console.error(\"The above error occurred in ...\", error, componentStack)`,\n * so scanning only `args[0]` misses React's own uncaught-error reports.\n */\nfunction findErrorArg(args: readonly unknown[]): Error | null {\n for (const arg of args) {\n if (arg instanceof Error) {\n return arg;\n }\n }\n return null;\n}\n\nexport const errorsPlugin: Plugin = {\n name: \"errors\",\n\n setup(ctx) {\n const originalOnError = globalThis.onerror;\n const originalStackTraceLimit = Error.stackTraceLimit;\n if (Error.stackTraceLimit < STACK_TRACE_LIMIT) {\n Error.stackTraceLimit = STACK_TRACE_LIMIT;\n }\n\n globalThis.onerror = (msg, source, line, col, error) => {\n if (error instanceof Error) {\n const fallbackFrame =\n typeof source === \"string\"\n ? {\n id: \"fallback-0\",\n file: source,\n ...(typeof line === \"number\" ? { line } : {}),\n ...(typeof col === \"number\" ? { column: col } : {}),\n }\n : null;\n\n capture(ctx, {\n error,\n mechanism: { type: MECHANISM_TYPE.browser.onerror, handled: false },\n ...(fallbackFrame ? { fallbackFrame } : {}),\n });\n }\n if (typeof originalOnError === \"function\") {\n return originalOnError.call(globalThis, msg, source, line, col, error);\n }\n return false;\n };\n\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n // `toException` returns a real Error when the rejection carries a\n // recoverable one (direct Error, nested Error inside an object, or\n // a plain string), or a `NonErrorException` carrying the original\n // structured payload otherwise. The `synthetic: true` flag was\n // historically a \"we lied about the shape\" tag; with structured\n // capture it now means \"this isn't a JS Error\" — same intent,\n // honest signal.\n capture(ctx, {\n error: toException(event.reason),\n mechanism: {\n type: MECHANISM_TYPE.browser.onunhandledrejection,\n handled: false,\n ...(event.reason instanceof Error ? {} : { synthetic: true }),\n },\n });\n };\n // Some non-browser runtimes (notably Node test forks vitest spawns under\n // `pool: \"forks\"`) don't expose `addEventListener` on globalThis. Skip the\n // unhandledrejection hook there rather than crashing plugin setup; the\n // browser path is unaffected.\n const supportsEventTarget =\n typeof globalThis.addEventListener === \"function\" &&\n typeof globalThis.removeEventListener === \"function\";\n if (supportsEventTarget) {\n globalThis.addEventListener(\"unhandledrejection\", onUnhandledRejection);\n }\n\n const unsubscribeConsole = onConsoleCall((level, args) => {\n if (level !== \"error\") {\n return;\n }\n const error = findErrorArg(args);\n if (error) {\n capture(ctx, {\n error,\n mechanism: {\n type: MECHANISM_TYPE.browser.consoleError,\n handled: true,\n },\n });\n }\n });\n\n return () => {\n Error.stackTraceLimit = originalStackTraceLimit;\n globalThis.onerror = originalOnError;\n if (supportsEventTarget) {\n globalThis.removeEventListener(\n \"unhandledrejection\",\n onUnhandledRejection\n );\n }\n unsubscribeConsole();\n };\n },\n};\n\nexport default errorsPlugin;\n"],"mappings":"iJAgCA,SAAS,QAAQ,IAAoB,KAAmB,CACtD,IAAI,gBAAgB,KAAK,MAAO,CAC9B,UAAW,KAAK,UAChB,GAAI,KAAK,eAAiB,CAAC,oBAAoB,KAAK,KAAK,EACrD,CAAE,eAAgB,CAAC,KAAK,aAAa,CAAE,EACvC,CAAC,CACP,CAAC,CACH,CAQA,SAAS,aAAa,KAAwC,CAC5D,IAAK,IAAM,OAAO,KAChB,GAAI,eAAe,MACjB,OAAO,IAGX,OAAO,IACT,CAEA,MAAa,aAAuB,CAClC,KAAM,SAEN,MAAM,IAAK,CACT,IAAM,gBAAkB,WAAW,QAC7B,wBAA0B,MAAM,gBAClC,MAAM,gBAAkB,KAC1B,MAAM,gBAAkB,IAG1B,WAAW,SAAW,IAAK,OAAQ,KAAM,IAAK,QAAU,CACtD,GAAI,iBAAiB,MAAO,CAC1B,IAAM,cACJ,OAAO,QAAW,SACd,CACE,GAAI,aACJ,KAAM,OACN,GAAI,OAAO,MAAS,SAAW,CAAE,IAAK,EAAI,CAAC,EAC3C,GAAI,OAAO,KAAQ,SAAW,CAAE,OAAQ,GAAI,EAAI,CAAC,CACnD,EACA,KAEN,QAAQ,IAAK,CACX,MACA,UAAW,CAAE,KAAM,eAAe,QAAQ,QAAS,QAAS,EAAM,EAClE,GAAI,cAAgB,CAAE,aAAc,EAAI,CAAC,CAC3C,CAAC,CACH,CAIA,OAHI,OAAO,iBAAoB,WACtB,gBAAgB,KAAK,WAAY,IAAK,OAAQ,KAAM,IAAK,KAAK,EAEhE,EACT,EAEA,IAAM,qBAAwB,OAAiC,CAQ7D,QAAQ,IAAK,CACX,MAAO,YAAY,MAAM,MAAM,EAC/B,UAAW,CACT,KAAM,eAAe,QAAQ,qBAC7B,QAAS,GACT,GAAI,MAAM,kBAAkB,MAAQ,CAAC,EAAI,CAAE,UAAW,EAAK,CAC7D,CACF,CAAC,CACH,EAKM,oBACJ,OAAO,WAAW,kBAAqB,YACvC,OAAO,WAAW,qBAAwB,WACxC,qBACF,WAAW,iBAAiB,qBAAsB,oBAAoB,EAGxE,IAAM,mBAAqB,eAAe,MAAO,OAAS,CACxD,GAAI,QAAU,QACZ,OAEF,IAAM,MAAQ,aAAa,IAAI,EAC3B,OACF,QAAQ,IAAK,CACX,MACA,UAAW,CACT,KAAM,eAAe,QAAQ,aAC7B,QAAS,EACX,CACF,CAAC,CAEL,CAAC,EAED,UAAa,CACX,MAAM,gBAAkB,wBACxB,WAAW,QAAU,gBACjB,qBACF,WAAW,oBACT,qBACA,oBACF,EAEF,mBAAmB,CACrB,CACF,CACF"}
@@ -5,6 +5,5 @@ import { PluginKey } from "@interfere/types/sdk/plugins/manifest";
5
5
  type PluginOverrides = Partial<Record<PluginKey, boolean>>;
6
6
  declare function resolveFeatures(overrides?: PluginOverrides): Record<PluginKey, boolean>;
7
7
  declare function loadPlugin(key: PluginKey, context: PluginContext): Promise<PluginCleanup | null>;
8
- declare function loadPlugins(overrides: PluginOverrides | undefined, context: PluginContext): Promise<PluginCleanup[]>;
9
8
  //#endregion
10
- export { PluginOverrides, loadPlugin, loadPlugins, resolveFeatures };
9
+ export { PluginOverrides, loadPlugin, resolveFeatures };
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.mts","names":[],"sources":["../../../src/plugins/lib/loader.ts"],"mappings":";;;;KAwBY,eAAA,GAAkB,OAAA,CAAQ,MAAA,CAAO,SAAA;AAAA,iBAE7B,eAAA,CACd,SAAA,GAAY,eAAA,GACX,MAAA,CAAO,SAAA;AAAA,iBAUY,UAAA,CACpB,GAAA,EAAK,SAAA,EACL,OAAA,EAAS,aAAA,GACR,OAAA,CAAQ,aAAA;AAAA,iBAkBW,WAAA,CACpB,SAAA,EAAW,eAAA,cACX,OAAA,EAAS,aAAA,GACR,OAAA,CAAQ,aAAA"}
1
+ {"version":3,"file":"loader.d.mts","names":[],"sources":["../../../src/plugins/lib/loader.ts"],"mappings":";;;;KA4BY,eAAA,GAAkB,OAAA,CAAQ,MAAA,CAAO,SAAA;AAAA,iBAE7B,eAAA,CACd,SAAA,GAAY,eAAA,GACX,MAAA,CAAO,SAAA;AAAA,iBAUY,UAAA,CACpB,GAAA,EAAK,SAAA,EACL,OAAA,EAAS,aAAA,GACR,OAAA,CAAQ,aAAA"}
@@ -1,43 +1 @@
1
- import { createLogger } from "../../util/log.mjs";
2
- import { PLUGIN_MANIFEST } from "@interfere/types/sdk/plugins/manifest";
3
- //#region src/plugins/lib/loader.ts
4
- const log = createLogger("plugins");
5
- const LOADERS = {
6
- errors: () => import("../errors.mjs"),
7
- device: () => import("../device.mjs"),
8
- pageEvents: () => import("../pages.mjs"),
9
- rageClick: () => import("../rage-clicks.mjs"),
10
- replay: () => import("../replay.mjs")
11
- };
12
- const DEFAULTS = Object.fromEntries(PLUGIN_MANIFEST.map((p) => [p.name, p.defaultEnabled]));
13
- function resolveFeatures(overrides) {
14
- return {
15
- ...DEFAULTS,
16
- ...overrides
17
- };
18
- }
19
- function resolvePlugin(mod) {
20
- return "default" in mod && typeof mod.default.setup === "function" ? mod.default : mod;
21
- }
22
- async function loadPlugin(key, context) {
23
- const loader = LOADERS[key];
24
- if (!loader) return null;
25
- try {
26
- const cleanup = resolvePlugin(await loader()).setup(context);
27
- log.debug("loaded %s", key);
28
- return typeof cleanup === "function" ? cleanup : null;
29
- } catch {
30
- log.error("failed to load plugin %s", key);
31
- return null;
32
- }
33
- }
34
- async function loadPlugins(overrides, context) {
35
- const resolved = {
36
- ...DEFAULTS,
37
- ...overrides
38
- };
39
- const keys = Object.entries(resolved).filter(([key, enabled]) => enabled && key in LOADERS).map(([key]) => key);
40
- return (await Promise.all(keys.map(async (key) => loadPlugin(key, context)))).filter((cleanup) => cleanup !== null);
41
- }
42
- //#endregion
43
- export { loadPlugin, loadPlugins, resolveFeatures };
1
+ import{createLogger}from"../../util/log.mjs";import{PLUGIN_MANIFEST}from"@interfere/types/sdk/plugins/manifest";const log=createLogger(`plugins`),LOADERS={errors:()=>import(`../errors.mjs`),logs:()=>import(`../logs.mjs`),rageClick:()=>import(`../rage-clicks.mjs`),replay:()=>import(`../replay.mjs`)},DEFAULTS=Object.fromEntries(PLUGIN_MANIFEST.map(p=>[p.name,p.defaultEnabled]));function resolveFeatures(overrides){return{...DEFAULTS,...overrides}}function resolvePlugin(mod){return`default`in mod&&typeof mod.default.setup==`function`?mod.default:mod}async function loadPlugin(key,context){let loader=LOADERS[key];if(!loader)return null;try{let cleanup=resolvePlugin(await loader()).setup(context);return log.debug(`loaded %s`,key),typeof cleanup==`function`?cleanup:null}catch{return log.error(`failed to load plugin %s`,key),null}}export{loadPlugin,resolveFeatures};
@@ -1 +1 @@
1
- {"version":3,"file":"loader.mjs","names":[],"sources":["../../../src/plugins/lib/loader.ts"],"sourcesContent":["import {\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\n\nimport { createLogger } from \"../../util/log.js\";\nimport type { Plugin, PluginCleanup, PluginContext } from \"./types.js\";\n\nconst log = createLogger(\"plugins\");\n\ntype PluginLoader = () => Promise<{ default: Plugin } | Plugin>;\n\nconst LOADERS: Partial<Record<PluginKey, PluginLoader>> = {\n errors: () => import(\"../errors.js\"),\n device: () => import(\"../device.js\"),\n pageEvents: () => import(\"../pages.js\"),\n rageClick: () => import(\"../rage-clicks.js\"),\n replay: () => import(\"../replay.js\"),\n};\n\nconst DEFAULTS: Record<PluginKey, boolean> = Object.fromEntries(\n PLUGIN_MANIFEST.map((p) => [p.name, p.defaultEnabled])\n) as Record<PluginKey, boolean>;\n\nexport type PluginOverrides = Partial<Record<PluginKey, boolean>>;\n\nexport function resolveFeatures(\n overrides?: PluginOverrides\n): Record<PluginKey, boolean> {\n return { ...DEFAULTS, ...overrides };\n}\n\nfunction resolvePlugin(mod: { default: Plugin } | Plugin): Plugin {\n return \"default\" in mod && typeof (mod.default as Plugin).setup === \"function\"\n ? mod.default\n : (mod as Plugin);\n}\n\nexport async function loadPlugin(\n key: PluginKey,\n context: PluginContext\n): Promise<PluginCleanup | null> {\n const loader = LOADERS[key];\n if (!loader) {\n return null;\n }\n\n try {\n const mod = await loader();\n const plugin = resolvePlugin(mod);\n const cleanup = plugin.setup(context);\n log.debug(\"loaded %s\", key);\n return typeof cleanup === \"function\" ? cleanup : null;\n } catch {\n log.error(\"failed to load plugin %s\", key);\n return null;\n }\n}\n\nexport async function loadPlugins(\n overrides: PluginOverrides | undefined,\n context: PluginContext\n): Promise<PluginCleanup[]> {\n const resolved = { ...DEFAULTS, ...overrides };\n const keys = (Object.entries(resolved) as [PluginKey, boolean][])\n .filter(([key, enabled]) => enabled && key in LOADERS)\n .map(([key]) => key);\n\n const cleanups = await Promise.all(\n keys.map(async (key) => loadPlugin(key, context))\n );\n return cleanups.filter((cleanup) => cleanup !== null);\n}\n"],"mappings":";;;AAQA,MAAM,MAAM,aAAa,UAAU;AAInC,MAAM,UAAoD;CACxD,cAAc,OAAO;CACrB,cAAc,OAAO;CACrB,kBAAkB,OAAO;CACzB,iBAAiB,OAAO;CACxB,cAAc,OAAO;CACtB;AAED,MAAM,WAAuC,OAAO,YAClD,gBAAgB,KAAK,MAAM,CAAC,EAAE,MAAM,EAAE,eAAe,CAAC,CACvD;AAID,SAAgB,gBACd,WAC4B;AAC5B,QAAO;EAAE,GAAG;EAAU,GAAG;EAAW;;AAGtC,SAAS,cAAc,KAA2C;AAChE,QAAO,aAAa,OAAO,OAAQ,IAAI,QAAmB,UAAU,aAChE,IAAI,UACH;;AAGP,eAAsB,WACpB,KACA,SAC+B;CAC/B,MAAM,SAAS,QAAQ;AACvB,KAAI,CAAC,OACH,QAAO;AAGT,KAAI;EAGF,MAAM,UADS,cAAc,MADX,QAAQ,CAEJ,CAAC,MAAM,QAAQ;AACrC,MAAI,MAAM,aAAa,IAAI;AAC3B,SAAO,OAAO,YAAY,aAAa,UAAU;SAC3C;AACN,MAAI,MAAM,4BAA4B,IAAI;AAC1C,SAAO;;;AAIX,eAAsB,YACpB,WACA,SAC0B;CAC1B,MAAM,WAAW;EAAE,GAAG;EAAU,GAAG;EAAW;CAC9C,MAAM,OAAQ,OAAO,QAAQ,SAAS,CACnC,QAAQ,CAAC,KAAK,aAAa,WAAW,OAAO,QAAQ,CACrD,KAAK,CAAC,SAAS,IAAI;AAKtB,SAAO,MAHgB,QAAQ,IAC7B,KAAK,IAAI,OAAO,QAAQ,WAAW,KAAK,QAAQ,CAAC,CAClD,EACe,QAAQ,YAAY,YAAY,KAAK"}
1
+ {"version":3,"file":"loader.mjs","names":[],"sources":["../../../src/plugins/lib/loader.ts"],"sourcesContent":["import {\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\n\nimport { createLogger } from \"../../util/log.js\";\nimport type { Plugin, PluginCleanup, PluginContext } from \"./types.js\";\n\nconst log = createLogger(\"plugins\");\n\ntype PluginLoader = () => Promise<{ default: Plugin } | Plugin>;\n\n// `pageEvents` from PLUGIN_MANIFEST has no loader: pageviews / clicks /\n// pageleaves are covered by the OTel `BrowserNavigationInstrumentation` +\n// `UserInteractionInstrumentation` auto-instruments instead. The\n// manifest entry stays for the legacy `/v1/ingest` server-side validators\n// (old SDK 9.x clients still emit these envelopes).\nconst LOADERS: Partial<Record<PluginKey, PluginLoader>> = {\n errors: () => import(\"../errors.js\"),\n logs: () => import(\"../logs.js\"),\n rageClick: () => import(\"../rage-clicks.js\"),\n replay: () => import(\"../replay.js\"),\n};\n\nconst DEFAULTS: Record<PluginKey, boolean> = Object.fromEntries(\n PLUGIN_MANIFEST.map((p) => [p.name, p.defaultEnabled])\n) as Record<PluginKey, boolean>;\n\nexport type PluginOverrides = Partial<Record<PluginKey, boolean>>;\n\nexport function resolveFeatures(\n overrides?: PluginOverrides\n): Record<PluginKey, boolean> {\n return { ...DEFAULTS, ...overrides };\n}\n\nfunction resolvePlugin(mod: { default: Plugin } | Plugin): Plugin {\n return \"default\" in mod && typeof (mod.default as Plugin).setup === \"function\"\n ? mod.default\n : (mod as Plugin);\n}\n\nexport async function loadPlugin(\n key: PluginKey,\n context: PluginContext\n): Promise<PluginCleanup | null> {\n const loader = LOADERS[key];\n if (!loader) {\n return null;\n }\n\n try {\n const mod = await loader();\n const plugin = resolvePlugin(mod);\n const cleanup = plugin.setup(context);\n log.debug(\"loaded %s\", key);\n return typeof cleanup === \"function\" ? cleanup : null;\n } catch {\n log.error(\"failed to load plugin %s\", key);\n return null;\n }\n}\n"],"mappings":"gHAQA,MAAM,IAAM,aAAa,SAAS,EAS5B,QAAoD,CACxD,WAAc,OAAO,iBACrB,SAAY,OAAO,eACnB,cAAiB,OAAO,sBACxB,WAAc,OAAO,gBACvB,EAEM,SAAuC,OAAO,YAClD,gBAAgB,IAAK,GAAM,CAAC,EAAE,KAAM,EAAE,cAAc,CAAC,CACvD,EAIA,SAAgB,gBACd,UAC4B,CAC5B,MAAO,CAAE,GAAG,SAAU,GAAG,SAAU,CACrC,CAEA,SAAS,cAAc,IAA2C,CAChE,MAAO,YAAa,KAAO,OAAQ,IAAI,QAAmB,OAAU,WAChE,IAAI,QACH,GACP,CAEA,eAAsB,WACpB,IACA,QAC+B,CAC/B,IAAM,OAAS,QAAQ,KACvB,GAAI,CAAC,OACH,OAAO,KAGT,GAAI,CAGF,IAAM,QADS,cAAc,MADX,OAAO,CAEJ,EAAE,MAAM,OAAO,EAEpC,OADA,IAAI,MAAM,YAAa,GAAG,EACnB,OAAO,SAAY,WAAa,QAAU,IACnD,MAAQ,CAEN,OADA,IAAI,MAAM,2BAA4B,GAAG,EAClC,IACT,CACF"}
@@ -1,10 +1,11 @@
1
- import { EnvelopePayload, EventType } from "@interfere/types/sdk/envelope";
1
+ import { RecordExceptionOpts } from "../../internal/kernel.mjs";
2
+ import { NonErrorException } from "@interfere/types/sdk/errors";
2
3
 
3
4
  //#region src/plugins/lib/types.d.ts
4
5
  type PluginCleanup = () => void;
5
6
  interface PluginContext {
6
- capture<T extends EventType>(type: T, payload: EnvelopePayload<T>): void;
7
7
  getSessionId(): string;
8
+ recordException(value: Error | NonErrorException, opts: RecordExceptionOpts): void;
8
9
  }
9
10
  interface Plugin {
10
11
  readonly name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/plugins/lib/types.ts"],"mappings":";;;KAEY,aAAA;AAAA,UAEK,aAAA;EACf,OAAA,WAAkB,SAAA,EAAW,IAAA,EAAM,CAAA,EAAG,OAAA,EAAS,eAAA,CAAgB,CAAA;EAC/D,YAAA;AAAA;AAAA,UAGe,MAAA;EAAA,SACN,IAAA;EACT,KAAA,CAAM,GAAA,EAAK,aAAA,GAAgB,aAAA;AAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/plugins/lib/types.ts"],"mappings":";;;;KAIY,aAAA;AAAA,UAEK,aAAA;EACf,YAAA;EACA,eAAA,CACE,KAAA,EAAO,KAAA,GAAQ,iBAAA,EACf,IAAA,EAAM,mBAAA;AAAA;AAAA,UAIO,MAAA;EAAA,SACN,IAAA;EACT,KAAA,CAAM,GAAA,EAAK,aAAA,GAAgB,aAAa;AAAA"}
@@ -1 +1 @@
1
- export {};
1
+ export{};
@@ -0,0 +1,13 @@
1
+ import { Plugin } from "./lib/types.mjs";
2
+
3
+ //#region src/plugins/logs.d.ts
4
+ /**
5
+ * Subscribes to the central console patch so string-only console calls
6
+ * land as OTel `LogRecord`s. Error-bearing calls are skipped: the
7
+ * `errors` plugin's subscriber on the same patch handles those as
8
+ * exceptions. Class boundary stays firm: errors flow as exceptions,
9
+ * strings flow as logs.
10
+ */
11
+ declare const logsPlugin: Plugin;
12
+ //#endregion
13
+ export { logsPlugin as default, logsPlugin };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.mts","names":[],"sources":["../../src/plugins/logs.ts"],"mappings":";;;;;AAyBA;;;;AA0BC;cA1BY,UAAA,EAAY,MA0BxB"}
@@ -0,0 +1 @@
1
+ import{activeKernel}from"../internal/kernel-registry.mjs";import{onConsoleCall}from"../internal/console-patch.mjs";import{safeStringify}from"../util/stringify.mjs";import{SeverityNumber}from"@opentelemetry/api-logs";const LEVEL_TO_SEVERITY={debug:{number:SeverityNumber.DEBUG,text:`DEBUG`},info:{number:SeverityNumber.INFO,text:`INFO`},log:{number:SeverityNumber.INFO,text:`INFO`},warn:{number:SeverityNumber.WARN,text:`WARN`},error:{number:SeverityNumber.ERROR,text:`ERROR`}},logsPlugin={name:`logs`,setup(){return onConsoleCall((level,args)=>{if(args.some(a=>a instanceof Error))return;let kernel=activeKernel();if(!kernel)return;let sev=LEVEL_TO_SEVERITY[level];kernel.recordLog({severityText:sev.text,severityNumber:sev.number,body:args.length===1?safeStringify(args[0]):args.map(safeStringify).join(` `),attributes:{"console.level":level}})})}};export{logsPlugin as default,logsPlugin};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.mjs","names":[],"sources":["../../src/plugins/logs.ts"],"sourcesContent":["import { SeverityNumber } from \"@opentelemetry/api-logs\";\n\nimport { type ConsoleLevel, onConsoleCall } from \"../internal/console-patch.js\";\nimport { activeKernel } from \"../internal/kernel-registry.js\";\nimport { safeStringify } from \"../util/stringify.js\";\nimport type { Plugin } from \"./lib/types.js\";\n\nconst LEVEL_TO_SEVERITY: Record<\n ConsoleLevel,\n { number: SeverityNumber; text: string }\n> = {\n debug: { number: SeverityNumber.DEBUG, text: \"DEBUG\" },\n info: { number: SeverityNumber.INFO, text: \"INFO\" },\n log: { number: SeverityNumber.INFO, text: \"INFO\" },\n warn: { number: SeverityNumber.WARN, text: \"WARN\" },\n error: { number: SeverityNumber.ERROR, text: \"ERROR\" },\n};\n\n/**\n * Subscribes to the central console patch so string-only console calls\n * land as OTel `LogRecord`s. Error-bearing calls are skipped: the\n * `errors` plugin's subscriber on the same patch handles those as\n * exceptions. Class boundary stays firm: errors flow as exceptions,\n * strings flow as logs.\n */\nexport const logsPlugin: Plugin = {\n name: \"logs\",\n\n setup() {\n const unsubscribe = onConsoleCall((level, args) => {\n if (args.some((a) => a instanceof Error)) {\n return;\n }\n const kernel = activeKernel();\n if (!kernel) {\n return;\n }\n const sev = LEVEL_TO_SEVERITY[level];\n kernel.recordLog({\n severityText: sev.text,\n severityNumber: sev.number,\n body:\n args.length === 1\n ? safeStringify(args[0])\n : args.map(safeStringify).join(\" \"),\n attributes: { \"console.level\": level },\n });\n });\n\n return unsubscribe;\n },\n};\n\nexport default logsPlugin;\n"],"mappings":"wNAOA,MAAM,kBAGF,CACF,MAAO,CAAE,OAAQ,eAAe,MAAO,KAAM,OAAQ,EACrD,KAAM,CAAE,OAAQ,eAAe,KAAM,KAAM,MAAO,EAClD,IAAK,CAAE,OAAQ,eAAe,KAAM,KAAM,MAAO,EACjD,KAAM,CAAE,OAAQ,eAAe,KAAM,KAAM,MAAO,EAClD,MAAO,CAAE,OAAQ,eAAe,MAAO,KAAM,OAAQ,CACvD,EASa,WAAqB,CAChC,KAAM,OAEN,OAAQ,CAqBN,OApBoB,eAAe,MAAO,OAAS,CACjD,GAAI,KAAK,KAAM,GAAM,aAAa,KAAK,EACrC,OAEF,IAAM,OAAS,aAAa,EAC5B,GAAI,CAAC,OACH,OAEF,IAAM,IAAM,kBAAkB,OAC9B,OAAO,UAAU,CACf,aAAc,IAAI,KAClB,eAAgB,IAAI,OACpB,KACE,KAAK,SAAW,EACZ,cAAc,KAAK,EAAE,EACrB,KAAK,IAAI,aAAa,EAAE,KAAK,GAAG,EACtC,WAAY,CAAE,gBAAiB,KAAM,CACvC,CAAC,CACH,CAEiB,CACnB,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"rage-clicks.d.mts","names":[],"sources":["../../src/plugins/rage-clicks.ts"],"mappings":";;;cA6Ba,gBAAA,EAAkB,MAAA"}
1
+ {"version":3,"file":"rage-clicks.d.mts","names":[],"sources":["../../src/plugins/rage-clicks.ts"],"mappings":";;;cAgCa,gBAAA,EAAkB,MAqE9B"}
@@ -1,53 +1 @@
1
- //#region src/plugins/rage-clicks.ts
2
- const CLICK_THRESHOLD = 3;
3
- const TIME_WINDOW_MS = 800;
4
- const PROXIMITY_PX = 30;
5
- function distance(a, b) {
6
- return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
7
- }
8
- function selectorFor(el) {
9
- if (!el) return "unknown";
10
- if (el.id) return `#${el.id}`;
11
- const classes = [...el.classList].slice(0, 3).join(".");
12
- const tag = el.tagName.toLowerCase();
13
- return classes ? `${tag}.${classes}` : tag;
14
- }
15
- const rageClicksPlugin = {
16
- name: "rage-clicks",
17
- setup(ctx) {
18
- const clicks = [];
19
- const onClick = (event) => {
20
- const now = Date.now();
21
- clicks.push({
22
- x: event.clientX,
23
- y: event.clientY,
24
- ts: now,
25
- target: event.target instanceof Element ? event.target : null
26
- });
27
- while (clicks.length > 0 && now - (clicks[0]?.ts ?? 0) > TIME_WINDOW_MS) clicks.shift();
28
- if (clicks.length < CLICK_THRESHOLD) return;
29
- const anchor = clicks[0];
30
- if (!anchor) return;
31
- const clustered = clicks.filter((c) => distance(anchor, c) <= PROXIMITY_PX);
32
- if (clustered.length < CLICK_THRESHOLD) return;
33
- const last = clustered.at(-1);
34
- if (!last) return;
35
- ctx.capture("rage_click", {
36
- count: clustered.length,
37
- timeWindow: last.ts - anchor.ts,
38
- selector: selectorFor(last.target),
39
- text: last.target?.textContent?.trim().slice(0, 120) ?? "",
40
- x: last.x,
41
- y: last.y,
42
- timestamp: now
43
- });
44
- clicks.length = 0;
45
- };
46
- document.addEventListener("click", onClick, { capture: true });
47
- return () => {
48
- document.removeEventListener("click", onClick, { capture: true });
49
- };
50
- }
51
- };
52
- //#endregion
53
- export { rageClicksPlugin as default, rageClicksPlugin };
1
+ import{trace}from"@opentelemetry/api";function distance(a,b){return Math.sqrt((a.x-b.x)**2+(a.y-b.y)**2)}function selectorFor(el){if(!el)return`unknown`;if(el.id)return`#${el.id}`;let classes=[...el.classList].slice(0,3).join(`.`),tag=el.tagName.toLowerCase();return classes?`${tag}.${classes}`:tag}const rageClicksPlugin={name:`rage-clicks`,setup(){let clicks=[],onClick=event=>{let now=Date.now();for(clicks.push({x:event.clientX,y:event.clientY,ts:now,target:event.target instanceof Element?event.target:null});clicks.length>0&&now-(clicks[0]?.ts??0)>800;)clicks.shift();if(clicks.length<3)return;let anchor=clicks[0];if(!anchor)return;let clustered=clicks.filter(c=>distance(anchor,c)<=30);if(clustered.length<3)return;let last=clustered.at(-1);last&&(trace.getTracer(`@interfere/react/rage-clicks`).startSpan(`rage_click`,{attributes:{"ui.event_type":`rage_click`,"ui.rage_click.count":clustered.length,"ui.rage_click.time_window_ms":last.ts-anchor.ts,"ui.rage_click.selector":selectorFor(last.target),"ui.rage_click.text":last.target?.textContent?.trim().slice(0,120)??``,"ui.rage_click.x":last.x,"ui.rage_click.y":last.y}}).end(),clicks.length=0)};return document.addEventListener(`click`,onClick,{capture:!0}),()=>{document.removeEventListener(`click`,onClick,{capture:!0})}}};export{rageClicksPlugin as default,rageClicksPlugin};
@@ -1 +1 @@
1
- {"version":3,"file":"rage-clicks.mjs","names":[],"sources":["../../src/plugins/rage-clicks.ts"],"sourcesContent":["import type { Plugin } from \"./lib/types.js\";\n\nconst CLICK_THRESHOLD = 3;\nconst TIME_WINDOW_MS = 800;\nconst PROXIMITY_PX = 30;\n\ninterface Click {\n target: Element | null;\n ts: number;\n x: number;\n y: number;\n}\n\nfunction distance(a: Click, b: Click): number {\n return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);\n}\n\nfunction selectorFor(el: Element | null): string {\n if (!el) {\n return \"unknown\";\n }\n if (el.id) {\n return `#${el.id}`;\n }\n const classes = [...el.classList].slice(0, 3).join(\".\");\n const tag = el.tagName.toLowerCase();\n return classes ? `${tag}.${classes}` : tag;\n}\n\nexport const rageClicksPlugin: Plugin = {\n name: \"rage-clicks\",\n\n setup(ctx) {\n const clicks: Click[] = [];\n\n const onClick = (event: MouseEvent) => {\n const now = Date.now();\n clicks.push({\n x: event.clientX,\n y: event.clientY,\n ts: now,\n target: event.target instanceof Element ? event.target : null,\n });\n\n // Prune stale clicks\n while (clicks.length > 0 && now - (clicks[0]?.ts ?? 0) > TIME_WINDOW_MS) {\n clicks.shift();\n }\n\n if (clicks.length < CLICK_THRESHOLD) {\n return;\n }\n\n // Check proximity — all clicks within PROXIMITY_PX of the first\n const anchor = clicks[0];\n if (!anchor) {\n return;\n }\n const clustered = clicks.filter(\n (c) => distance(anchor, c) <= PROXIMITY_PX\n );\n if (clustered.length < CLICK_THRESHOLD) {\n return;\n }\n\n const last = clustered.at(-1);\n if (!last) {\n return;\n }\n ctx.capture(\"rage_click\", {\n count: clustered.length,\n timeWindow: last.ts - anchor.ts,\n selector: selectorFor(last.target),\n text: last.target?.textContent?.trim().slice(0, 120) ?? \"\",\n x: last.x,\n y: last.y,\n timestamp: now,\n });\n\n clicks.length = 0;\n };\n\n document.addEventListener(\"click\", onClick, { capture: true });\n\n return () => {\n document.removeEventListener(\"click\", onClick, { capture: true });\n };\n },\n};\n\nexport default rageClicksPlugin;\n"],"mappings":";AAEA,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AACvB,MAAM,eAAe;AASrB,SAAS,SAAS,GAAU,GAAkB;AAC5C,QAAO,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE;;AAGvD,SAAS,YAAY,IAA4B;AAC/C,KAAI,CAAC,GACH,QAAO;AAET,KAAI,GAAG,GACL,QAAO,IAAI,GAAG;CAEhB,MAAM,UAAU,CAAC,GAAG,GAAG,UAAU,CAAC,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI;CACvD,MAAM,MAAM,GAAG,QAAQ,aAAa;AACpC,QAAO,UAAU,GAAG,IAAI,GAAG,YAAY;;AAGzC,MAAa,mBAA2B;CACtC,MAAM;CAEN,MAAM,KAAK;EACT,MAAM,SAAkB,EAAE;EAE1B,MAAM,WAAW,UAAsB;GACrC,MAAM,MAAM,KAAK,KAAK;AACtB,UAAO,KAAK;IACV,GAAG,MAAM;IACT,GAAG,MAAM;IACT,IAAI;IACJ,QAAQ,MAAM,kBAAkB,UAAU,MAAM,SAAS;IAC1D,CAAC;AAGF,UAAO,OAAO,SAAS,KAAK,OAAO,OAAO,IAAI,MAAM,KAAK,eACvD,QAAO,OAAO;AAGhB,OAAI,OAAO,SAAS,gBAClB;GAIF,MAAM,SAAS,OAAO;AACtB,OAAI,CAAC,OACH;GAEF,MAAM,YAAY,OAAO,QACtB,MAAM,SAAS,QAAQ,EAAE,IAAI,aAC/B;AACD,OAAI,UAAU,SAAS,gBACrB;GAGF,MAAM,OAAO,UAAU,GAAG,GAAG;AAC7B,OAAI,CAAC,KACH;AAEF,OAAI,QAAQ,cAAc;IACxB,OAAO,UAAU;IACjB,YAAY,KAAK,KAAK,OAAO;IAC7B,UAAU,YAAY,KAAK,OAAO;IAClC,MAAM,KAAK,QAAQ,aAAa,MAAM,CAAC,MAAM,GAAG,IAAI,IAAI;IACxD,GAAG,KAAK;IACR,GAAG,KAAK;IACR,WAAW;IACZ,CAAC;AAEF,UAAO,SAAS;;AAGlB,WAAS,iBAAiB,SAAS,SAAS,EAAE,SAAS,MAAM,CAAC;AAE9D,eAAa;AACX,YAAS,oBAAoB,SAAS,SAAS,EAAE,SAAS,MAAM,CAAC;;;CAGtE"}
1
+ {"version":3,"file":"rage-clicks.mjs","names":[],"sources":["../../src/plugins/rage-clicks.ts"],"sourcesContent":["import { trace } from \"@opentelemetry/api\";\n\nimport type { Plugin } from \"./lib/types.js\";\n\nconst CLICK_THRESHOLD = 3;\nconst TIME_WINDOW_MS = 800;\nconst PROXIMITY_PX = 30;\nconst TRACER_NAME = \"@interfere/react/rage-clicks\";\n\ninterface Click {\n target: Element | null;\n ts: number;\n x: number;\n y: number;\n}\n\nfunction distance(a: Click, b: Click): number {\n return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);\n}\n\nfunction selectorFor(el: Element | null): string {\n if (!el) {\n return \"unknown\";\n }\n if (el.id) {\n return `#${el.id}`;\n }\n const classes = [...el.classList].slice(0, 3).join(\".\");\n const tag = el.tagName.toLowerCase();\n return classes ? `${tag}.${classes}` : tag;\n}\n\nexport const rageClicksPlugin: Plugin = {\n name: \"rage-clicks\",\n\n setup() {\n const clicks: Click[] = [];\n\n const onClick = (event: MouseEvent) => {\n const now = Date.now();\n clicks.push({\n x: event.clientX,\n y: event.clientY,\n ts: now,\n target: event.target instanceof Element ? event.target : null,\n });\n\n // Prune stale clicks\n while (clicks.length > 0 && now - (clicks[0]?.ts ?? 0) > TIME_WINDOW_MS) {\n clicks.shift();\n }\n\n if (clicks.length < CLICK_THRESHOLD) {\n return;\n }\n\n // Check proximity — all clicks within PROXIMITY_PX of the first\n const anchor = clicks[0];\n if (!anchor) {\n return;\n }\n const clustered = clicks.filter(\n (c) => distance(anchor, c) <= PROXIMITY_PX\n );\n if (clustered.length < CLICK_THRESHOLD) {\n return;\n }\n\n const last = clustered.at(-1);\n if (!last) {\n return;\n }\n\n // Emit as a discrete OTel span. The cluster of clicks is a single\n // user-experience event; a span (rather than a span event on\n // whatever's currently active) keeps the rage-click correlatable\n // by `ui.event_type=rage_click` regardless of whether a navigation\n // / interaction span happens to be open at click time.\n const span = trace.getTracer(TRACER_NAME).startSpan(\"rage_click\", {\n attributes: {\n \"ui.event_type\": \"rage_click\",\n \"ui.rage_click.count\": clustered.length,\n \"ui.rage_click.time_window_ms\": last.ts - anchor.ts,\n \"ui.rage_click.selector\": selectorFor(last.target),\n \"ui.rage_click.text\":\n last.target?.textContent?.trim().slice(0, 120) ?? \"\",\n \"ui.rage_click.x\": last.x,\n \"ui.rage_click.y\": last.y,\n },\n });\n span.end();\n\n clicks.length = 0;\n };\n\n document.addEventListener(\"click\", onClick, { capture: true });\n\n return () => {\n document.removeEventListener(\"click\", onClick, { capture: true });\n };\n },\n};\n\nexport default rageClicksPlugin;\n"],"mappings":"sCAgBA,SAAS,SAAS,EAAU,EAAkB,CAC5C,OAAO,KAAK,MAAM,EAAE,EAAI,EAAE,IAAM,GAAK,EAAE,EAAI,EAAE,IAAM,CAAC,CACtD,CAEA,SAAS,YAAY,GAA4B,CAC/C,GAAI,CAAC,GACH,MAAO,UAET,GAAI,GAAG,GACL,MAAO,IAAI,GAAG,KAEhB,IAAM,QAAU,CAAC,GAAG,GAAG,SAAS,EAAE,MAAM,EAAG,CAAC,EAAE,KAAK,GAAG,EAChD,IAAM,GAAG,QAAQ,YAAY,EACnC,OAAO,QAAU,GAAG,IAAI,GAAG,UAAY,GACzC,CAEA,MAAa,iBAA2B,CACtC,KAAM,cAEN,OAAQ,CACN,IAAM,OAAkB,CAAC,EAEnB,QAAW,OAAsB,CACrC,IAAM,IAAM,KAAK,IAAI,EASrB,IARA,OAAO,KAAK,CACV,EAAG,MAAM,QACT,EAAG,MAAM,QACT,GAAI,IACJ,OAAQ,MAAM,kBAAkB,QAAU,MAAM,OAAS,IAC3D,CAAC,EAGM,OAAO,OAAS,GAAK,KAAO,OAAO,IAAI,IAAM,GAAK,KACvD,OAAO,MAAM,EAGf,GAAI,OAAO,OAAS,EAClB,OAIF,IAAM,OAAS,OAAO,GACtB,GAAI,CAAC,OACH,OAEF,IAAM,UAAY,OAAO,OACtB,GAAM,SAAS,OAAQ,CAAC,GAAK,EAChC,EACA,GAAI,UAAU,OAAS,EACrB,OAGF,IAAM,KAAO,UAAU,GAAG,EAAE,EACvB,OAqBL,MAZmB,UAAU,8BAAW,EAAE,UAAU,aAAc,CAChE,WAAY,CACV,gBAAiB,aACjB,sBAAuB,UAAU,OACjC,+BAAgC,KAAK,GAAK,OAAO,GACjD,yBAA0B,YAAY,KAAK,MAAM,EACjD,qBACE,KAAK,QAAQ,aAAa,KAAK,EAAE,MAAM,EAAG,GAAG,GAAK,GACpD,kBAAmB,KAAK,EACxB,kBAAmB,KAAK,CAC1B,CACF,CACG,EAAE,IAAI,EAET,OAAO,OAAS,EAClB,EAIA,OAFA,SAAS,iBAAiB,QAAS,QAAS,CAAE,QAAS,EAAK,CAAC,MAEhD,CACX,SAAS,oBAAoB,QAAS,QAAS,CAAE,QAAS,EAAK,CAAC,CAClE,CACF,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"replay.d.mts","names":[],"sources":["../../src/plugins/replay.ts"],"mappings":";;;cAOa,YAAA,EAAc,MAAA"}
1
+ {"version":3,"file":"replay.d.mts","names":[],"sources":["../../src/plugins/replay.ts"],"mappings":";;;cAyFa,YAAA,EAAc,MA4E1B"}