@interfere/react 9.0.2 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 -25
  181. package/dist/transport/http.d.mts.map +0 -1
  182. package/dist/transport/http.mjs +0 -80
  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 -100
  187. package/dist/transport/queue.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-runtime.mjs","names":[],"sources":["../../src/internal/plugin-runtime.ts"],"sourcesContent":["import type { EventType } from \"@interfere/types/sdk/envelope\";\nimport {\n type ConsentState,\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\nimport {\n EVENT_TYPE_TO_PLUGIN,\n type RemotePluginConfig,\n} from \"@interfere/types/sdk/remote-config\";\n\nimport errorsPlugin from \"../plugins/errors.js\";\nimport {\n loadPlugin,\n type PluginOverrides,\n resolveFeatures,\n} from \"../plugins/lib/loader.js\";\nimport type { PluginCleanup, PluginContext } from \"../plugins/lib/types.js\";\nimport { createLogger } from \"../util/log.js\";\nimport {\n getPluginConsentCategory,\n hasConsentChanged,\n isConsentAllowed,\n resolveGrantedConsent,\n shouldCaptureEvent,\n} from \"./consent.js\";\n\nconst log = createLogger(\"plugin-runtime\");\n\nexport class PluginRuntime {\n private readonly activeCleanups = new Map<PluginKey, PluginCleanup>();\n private readonly pending = new Set<Promise<void>>();\n private readonly context: PluginContext;\n private readonly features: Record<PluginKey, boolean>;\n private remoteConfig: RemotePluginConfig = {};\n private consentState: ConsentState | null;\n private syncVersion = 0;\n private disposed = false;\n\n constructor(\n context: PluginContext,\n overrides: PluginOverrides | undefined,\n initialConsent: ConsentState | undefined\n ) {\n this.context = context;\n this.features = resolveFeatures(overrides);\n this.consentState = initialConsent ?? null;\n }\n\n getConsent(): ConsentState | null {\n return this.consentState;\n }\n\n setConsent(nextConsent?: ConsentState): void {\n const nextState = resolveGrantedConsent(nextConsent);\n if (!hasConsentChanged(this.consentState, nextState)) {\n return;\n }\n\n this.consentState = nextState;\n this.sync();\n }\n\n resetConsent(): void {\n if (!hasConsentChanged(this.consentState, null)) {\n return;\n }\n\n this.consentState = null;\n this.sync();\n }\n\n applyRemoteConfig(config: RemotePluginConfig): void {\n this.remoteConfig = config;\n this.sync();\n }\n\n canCapture(type: EventType): boolean {\n const plugin = EVENT_TYPE_TO_PLUGIN[type];\n if (plugin && this.remoteConfig[plugin] === false) {\n return false;\n }\n return shouldCaptureEvent(type, this.consentState);\n }\n\n start(): void {\n if (this.shouldEnablePlugin(\"errors\")) {\n const cleanup = errorsPlugin.setup(this.context);\n if (cleanup) {\n this.activeCleanups.set(\"errors\", cleanup);\n }\n }\n\n this.sync();\n }\n\n async dispose(): Promise<void> {\n this.disposed = true;\n this.syncVersion += 1;\n\n for (const key of this.activeCleanups.keys()) {\n this.deactivate(key);\n }\n\n await Promise.allSettled(this.pending);\n }\n\n private shouldEnablePlugin(key: PluginKey): boolean {\n return (\n this.features[key] &&\n this.remoteConfig[key] !== false &&\n isConsentAllowed(getPluginConsentCategory(key), this.consentState)\n );\n }\n\n private deactivate(key: PluginKey): void {\n const cleanup = this.activeCleanups.get(key);\n if (!cleanup) {\n return;\n }\n\n try {\n cleanup();\n } catch {\n log.warn(\"cleanup failed for %s\", key);\n }\n\n this.activeCleanups.delete(key);\n }\n\n private async activate(key: PluginKey): Promise<void> {\n if (this.activeCleanups.has(key) || !this.shouldEnablePlugin(key)) {\n return;\n }\n\n const version = this.syncVersion;\n const cleanup = await loadPlugin(key, this.context);\n if (!cleanup) {\n return;\n }\n\n const staleSync = version !== this.syncVersion;\n if (staleSync || !this.shouldEnablePlugin(key)) {\n cleanup();\n return;\n }\n\n this.activeCleanups.set(key, cleanup);\n }\n\n private sync(): void {\n if (this.disposed) {\n return;\n }\n\n this.syncVersion += 1;\n\n for (const plugin of PLUGIN_MANIFEST) {\n if (this.shouldEnablePlugin(plugin.name)) {\n if (plugin.name === \"errors\") {\n if (!this.activeCleanups.has(\"errors\")) {\n const cleanup = errorsPlugin.setup(this.context);\n if (cleanup) {\n this.activeCleanups.set(\"errors\", cleanup);\n }\n }\n continue;\n }\n\n const p = this.activate(plugin.name).catch(() => {\n log.warn(\"non-critical plugin loading failed\");\n });\n this.pending.add(p);\n p.finally(() => this.pending.delete(p));\n continue;\n }\n\n this.deactivate(plugin.name);\n }\n }\n}\n"],"mappings":";;;;;;;AA2BA,MAAM,MAAM,aAAa,iBAAiB;AAE1C,IAAa,gBAAb,MAA2B;CACzB,iCAAkC,IAAI,KAA+B;CACrE,0BAA2B,IAAI,KAAoB;CACnD;CACA;CACA,eAA2C,EAAE;CAC7C;CACA,cAAsB;CACtB,WAAmB;CAEnB,YACE,SACA,WACA,gBACA;AACA,OAAK,UAAU;AACf,OAAK,WAAW,gBAAgB,UAAU;AAC1C,OAAK,eAAe,kBAAkB;;CAGxC,aAAkC;AAChC,SAAO,KAAK;;CAGd,WAAW,aAAkC;EAC3C,MAAM,YAAY,sBAAsB,YAAY;AACpD,MAAI,CAAC,kBAAkB,KAAK,cAAc,UAAU,CAClD;AAGF,OAAK,eAAe;AACpB,OAAK,MAAM;;CAGb,eAAqB;AACnB,MAAI,CAAC,kBAAkB,KAAK,cAAc,KAAK,CAC7C;AAGF,OAAK,eAAe;AACpB,OAAK,MAAM;;CAGb,kBAAkB,QAAkC;AAClD,OAAK,eAAe;AACpB,OAAK,MAAM;;CAGb,WAAW,MAA0B;EACnC,MAAM,SAAS,qBAAqB;AACpC,MAAI,UAAU,KAAK,aAAa,YAAY,MAC1C,QAAO;AAET,SAAO,mBAAmB,MAAM,KAAK,aAAa;;CAGpD,QAAc;AACZ,MAAI,KAAK,mBAAmB,SAAS,EAAE;GACrC,MAAM,UAAU,aAAa,MAAM,KAAK,QAAQ;AAChD,OAAI,QACF,MAAK,eAAe,IAAI,UAAU,QAAQ;;AAI9C,OAAK,MAAM;;CAGb,MAAM,UAAyB;AAC7B,OAAK,WAAW;AAChB,OAAK,eAAe;AAEpB,OAAK,MAAM,OAAO,KAAK,eAAe,MAAM,CAC1C,MAAK,WAAW,IAAI;AAGtB,QAAM,QAAQ,WAAW,KAAK,QAAQ;;CAGxC,mBAA2B,KAAyB;AAClD,SACE,KAAK,SAAS,QACd,KAAK,aAAa,SAAS,SAC3B,iBAAiB,yBAAyB,IAAI,EAAE,KAAK,aAAa;;CAItE,WAAmB,KAAsB;EACvC,MAAM,UAAU,KAAK,eAAe,IAAI,IAAI;AAC5C,MAAI,CAAC,QACH;AAGF,MAAI;AACF,YAAS;UACH;AACN,OAAI,KAAK,yBAAyB,IAAI;;AAGxC,OAAK,eAAe,OAAO,IAAI;;CAGjC,MAAc,SAAS,KAA+B;AACpD,MAAI,KAAK,eAAe,IAAI,IAAI,IAAI,CAAC,KAAK,mBAAmB,IAAI,CAC/D;EAGF,MAAM,UAAU,KAAK;EACrB,MAAM,UAAU,MAAM,WAAW,KAAK,KAAK,QAAQ;AACnD,MAAI,CAAC,QACH;AAIF,MADkB,YAAY,KAAK,eAClB,CAAC,KAAK,mBAAmB,IAAI,EAAE;AAC9C,YAAS;AACT;;AAGF,OAAK,eAAe,IAAI,KAAK,QAAQ;;CAGvC,OAAqB;AACnB,MAAI,KAAK,SACP;AAGF,OAAK,eAAe;AAEpB,OAAK,MAAM,UAAU,iBAAiB;AACpC,OAAI,KAAK,mBAAmB,OAAO,KAAK,EAAE;AACxC,QAAI,OAAO,SAAS,UAAU;AAC5B,SAAI,CAAC,KAAK,eAAe,IAAI,SAAS,EAAE;MACtC,MAAM,UAAU,aAAa,MAAM,KAAK,QAAQ;AAChD,UAAI,QACF,MAAK,eAAe,IAAI,UAAU,QAAQ;;AAG9C;;IAGF,MAAM,IAAI,KAAK,SAAS,OAAO,KAAK,CAAC,YAAY;AAC/C,SAAI,KAAK,qCAAqC;MAC9C;AACF,SAAK,QAAQ,IAAI,EAAE;AACnB,MAAE,cAAc,KAAK,QAAQ,OAAO,EAAE,CAAC;AACvC;;AAGF,QAAK,WAAW,OAAO,KAAK"}
1
+ {"version":3,"file":"plugin-runtime.mjs","names":[],"sources":["../../src/internal/plugin-runtime.ts"],"sourcesContent":["import {\n type ConsentState,\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\nimport type { RemotePluginConfig } from \"@interfere/types/sdk/remote-config\";\n\nimport errorsPlugin from \"../plugins/errors.js\";\nimport {\n loadPlugin,\n type PluginOverrides,\n resolveFeatures,\n} from \"../plugins/lib/loader.js\";\nimport type { PluginCleanup, PluginContext } from \"../plugins/lib/types.js\";\nimport { createLogger } from \"../util/log.js\";\nimport {\n getPluginConsentCategory,\n hasConsentChanged,\n isConsentAllowed,\n resolveGrantedConsent,\n} from \"./consent.js\";\n\nconst log = createLogger(\"plugin-runtime\");\n\nexport class PluginRuntime {\n private readonly activeCleanups = new Map<PluginKey, PluginCleanup>();\n private readonly pending = new Set<Promise<void>>();\n private readonly context: PluginContext;\n private readonly features: Record<PluginKey, boolean>;\n private remoteConfig: RemotePluginConfig = {};\n private consentState: ConsentState | null;\n private syncVersion = 0;\n private disposed = false;\n\n constructor(\n context: PluginContext,\n overrides: PluginOverrides | undefined,\n initialConsent: ConsentState | undefined\n ) {\n this.context = context;\n this.features = resolveFeatures(overrides);\n this.consentState = initialConsent ?? null;\n }\n\n getConsent(): ConsentState | null {\n return this.consentState;\n }\n\n setConsent(nextConsent?: ConsentState): void {\n const nextState = resolveGrantedConsent(nextConsent);\n if (!hasConsentChanged(this.consentState, nextState)) {\n return;\n }\n\n this.consentState = nextState;\n this.sync();\n }\n\n resetConsent(): void {\n if (!hasConsentChanged(this.consentState, null)) {\n return;\n }\n\n this.consentState = null;\n this.sync();\n }\n\n applyRemoteConfig(config: RemotePluginConfig): void {\n this.remoteConfig = config;\n this.sync();\n }\n\n start(): void {\n if (this.shouldEnablePlugin(\"errors\")) {\n const cleanup = errorsPlugin.setup(this.context);\n if (cleanup) {\n this.activeCleanups.set(\"errors\", cleanup);\n }\n }\n\n this.sync();\n }\n\n async dispose(): Promise<void> {\n this.disposed = true;\n this.syncVersion += 1;\n\n for (const key of this.activeCleanups.keys()) {\n this.deactivate(key);\n }\n\n await Promise.allSettled(this.pending);\n }\n\n private shouldEnablePlugin(key: PluginKey): boolean {\n return (\n this.features[key] &&\n this.remoteConfig[key] !== false &&\n isConsentAllowed(getPluginConsentCategory(key), this.consentState)\n );\n }\n\n private deactivate(key: PluginKey): void {\n const cleanup = this.activeCleanups.get(key);\n if (!cleanup) {\n return;\n }\n\n try {\n cleanup();\n } catch {\n log.warn(\"cleanup failed for %s\", key);\n }\n\n this.activeCleanups.delete(key);\n }\n\n private async activate(key: PluginKey): Promise<void> {\n if (this.activeCleanups.has(key) || !this.shouldEnablePlugin(key)) {\n return;\n }\n\n const version = this.syncVersion;\n const cleanup = await loadPlugin(key, this.context);\n if (!cleanup) {\n return;\n }\n\n const staleSync = version !== this.syncVersion;\n if (staleSync || !this.shouldEnablePlugin(key)) {\n cleanup();\n return;\n }\n\n this.activeCleanups.set(key, cleanup);\n }\n\n private sync(): void {\n if (this.disposed) {\n return;\n }\n\n this.syncVersion += 1;\n\n for (const plugin of PLUGIN_MANIFEST) {\n if (this.shouldEnablePlugin(plugin.name)) {\n if (plugin.name === \"errors\") {\n if (!this.activeCleanups.has(\"errors\")) {\n const cleanup = errorsPlugin.setup(this.context);\n if (cleanup) {\n this.activeCleanups.set(\"errors\", cleanup);\n }\n }\n continue;\n }\n\n const p = this.activate(plugin.name).catch(() => {\n log.warn(\"non-critical plugin loading failed\");\n });\n this.pending.add(p);\n p.finally(() => this.pending.delete(p));\n continue;\n }\n\n this.deactivate(plugin.name);\n }\n }\n}\n"],"mappings":";;;;;;AAsBA,MAAM,MAAM,aAAa,iBAAiB;AAE1C,IAAa,gBAAb,MAA2B;CACzB,iCAAkC,IAAI,KAA+B;CACrE,0BAA2B,IAAI,KAAoB;CACnD;CACA;CACA,eAA2C,EAAE;CAC7C;CACA,cAAsB;CACtB,WAAmB;CAEnB,YACE,SACA,WACA,gBACA;EACA,KAAK,UAAU;EACf,KAAK,WAAW,gBAAgB,UAAU;EAC1C,KAAK,eAAe,kBAAkB;;CAGxC,aAAkC;EAChC,OAAO,KAAK;;CAGd,WAAW,aAAkC;EAC3C,MAAM,YAAY,sBAAsB,YAAY;EACpD,IAAI,CAAC,kBAAkB,KAAK,cAAc,UAAU,EAClD;EAGF,KAAK,eAAe;EACpB,KAAK,MAAM;;CAGb,eAAqB;EACnB,IAAI,CAAC,kBAAkB,KAAK,cAAc,KAAK,EAC7C;EAGF,KAAK,eAAe;EACpB,KAAK,MAAM;;CAGb,kBAAkB,QAAkC;EAClD,KAAK,eAAe;EACpB,KAAK,MAAM;;CAGb,QAAc;EACZ,IAAI,KAAK,mBAAmB,SAAS,EAAE;GACrC,MAAM,UAAU,aAAa,MAAM,KAAK,QAAQ;GAChD,IAAI,SACF,KAAK,eAAe,IAAI,UAAU,QAAQ;;EAI9C,KAAK,MAAM;;CAGb,MAAM,UAAyB;EAC7B,KAAK,WAAW;EAChB,KAAK,eAAe;EAEpB,KAAK,MAAM,OAAO,KAAK,eAAe,MAAM,EAC1C,KAAK,WAAW,IAAI;EAGtB,MAAM,QAAQ,WAAW,KAAK,QAAQ;;CAGxC,mBAA2B,KAAyB;EAClD,OACE,KAAK,SAAS,QACd,KAAK,aAAa,SAAS,SAC3B,iBAAiB,yBAAyB,IAAI,EAAE,KAAK,aAAa;;CAItE,WAAmB,KAAsB;EACvC,MAAM,UAAU,KAAK,eAAe,IAAI,IAAI;EAC5C,IAAI,CAAC,SACH;EAGF,IAAI;GACF,SAAS;UACH;GACN,IAAI,KAAK,yBAAyB,IAAI;;EAGxC,KAAK,eAAe,OAAO,IAAI;;CAGjC,MAAc,SAAS,KAA+B;EACpD,IAAI,KAAK,eAAe,IAAI,IAAI,IAAI,CAAC,KAAK,mBAAmB,IAAI,EAC/D;EAGF,MAAM,UAAU,KAAK;EACrB,MAAM,UAAU,MAAM,WAAW,KAAK,KAAK,QAAQ;EACnD,IAAI,CAAC,SACH;EAIF,IADkB,YAAY,KAAK,eAClB,CAAC,KAAK,mBAAmB,IAAI,EAAE;GAC9C,SAAS;GACT;;EAGF,KAAK,eAAe,IAAI,KAAK,QAAQ;;CAGvC,OAAqB;EACnB,IAAI,KAAK,UACP;EAGF,KAAK,eAAe;EAEpB,KAAK,MAAM,UAAU,iBAAiB;GACpC,IAAI,KAAK,mBAAmB,OAAO,KAAK,EAAE;IACxC,IAAI,OAAO,SAAS,UAAU;KAC5B,IAAI,CAAC,KAAK,eAAe,IAAI,SAAS,EAAE;MACtC,MAAM,UAAU,aAAa,MAAM,KAAK,QAAQ;MAChD,IAAI,SACF,KAAK,eAAe,IAAI,UAAU,QAAQ;;KAG9C;;IAGF,MAAM,IAAI,KAAK,SAAS,OAAO,KAAK,CAAC,YAAY;KAC/C,IAAI,KAAK,qCAAqC;MAC9C;IACF,KAAK,QAAQ,IAAI,EAAE;IACnB,EAAE,cAAc,KAAK,QAAQ,OAAO,EAAE,CAAC;IACvC;;GAGF,KAAK,WAAW,OAAO,KAAK"}
@@ -0,0 +1,45 @@
1
+ import { Kernel } from "./kernel.mjs";
2
+ import * as _$react from "react";
3
+ import { ConsentState } from "@interfere/types/sdk/plugins/manifest";
4
+ import { IdentifyParams } from "@interfere/types/sdk/identify";
5
+
6
+ //#region src/internal/react-context.d.ts
7
+ interface InterfereContextValue {
8
+ consent: {
9
+ get(): ConsentState | null;
10
+ set(state?: ConsentState): void;
11
+ };
12
+ device: {
13
+ getDeviceId(): string | null;
14
+ getFpHash(): string | null;
15
+ };
16
+ identity: {
17
+ get(): IdentifyParams | null;
18
+ set(params: IdentifyParams): Promise<void>;
19
+ };
20
+ /**
21
+ * The active kernel, or `null` when the SDK isn't initialized yet — for
22
+ * example during server-side prerender, when `init()` is client-only.
23
+ * Accessor objects (consent/device/identity/session) above stay safe to
24
+ * call in either state; they no-op when the kernel is null.
25
+ */
26
+ kernel: Kernel | null;
27
+ session: {
28
+ getId(): string | null;
29
+ getWindowId(): string | null;
30
+ };
31
+ }
32
+ /**
33
+ * Context value used when the provider is mounted without a kernel — every
34
+ * accessor returns `null` and mutations are no-ops. Lets `useInterfere()`
35
+ * work seamlessly across SSR/CSR boundaries (server gets nulls, client
36
+ * gets real values once the kernel resolves).
37
+ */
38
+ declare const NULL_CONTEXT_VALUE: InterfereContextValue;
39
+ /**
40
+ * Lives in its own module so `provider.tsx` and `internal/capture-boundary.tsx`
41
+ * can both import it without forming an import cycle.
42
+ */
43
+ declare const InterfereContext: _$react.Context<InterfereContextValue | null>;
44
+ //#endregion
45
+ export { InterfereContext, InterfereContextValue, NULL_CONTEXT_VALUE };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-context.d.mts","names":[],"sources":["../../src/internal/react-context.ts"],"mappings":";;;;;;UASiB,qBAAA;EACf,OAAA;IACE,GAAA,IAAO,YAAA;IACP,GAAA,CAAI,KAAA,GAAQ,YAAA;EAAA;EAEd,MAAA;IACE,WAAA;IACA,SAAA;EAAA;EAEF,QAAA;IACE,GAAA,IAAO,cAAA;IACP,GAAA,CAAI,MAAA,EAAQ,cAAA,GAAiB,OAAA;EAAA;EAQjB;;;;;;EAAd,MAAA,EAAQ,MAAA;EACR,OAAA;IACE,KAAA;IACA,WAAA;EAAA;AAAA;;;;;;;cAkBS,kBAAA,EAAoB,qBAAA;;;;;cAYpB,gBAAA,EAAgB,OAAA,CAAA,OAAA,CAAA,qBAAA"}
@@ -0,0 +1,34 @@
1
+ "use client";
2
+ import { createContext } from "react";
3
+ /**
4
+ * Context value used when the provider is mounted without a kernel — every
5
+ * accessor returns `null` and mutations are no-ops. Lets `useInterfere()`
6
+ * work seamlessly across SSR/CSR boundaries (server gets nulls, client
7
+ * gets real values once the kernel resolves).
8
+ */
9
+ const NULL_CONTEXT_VALUE = {
10
+ consent: {
11
+ get: () => null,
12
+ set: () => void 0
13
+ },
14
+ device: {
15
+ getDeviceId: () => null,
16
+ getFpHash: () => null
17
+ },
18
+ identity: {
19
+ get: () => null,
20
+ set: () => Promise.resolve()
21
+ },
22
+ kernel: null,
23
+ session: {
24
+ getId: () => null,
25
+ getWindowId: () => null
26
+ }
27
+ };
28
+ /**
29
+ * Lives in its own module so `provider.tsx` and `internal/capture-boundary.tsx`
30
+ * can both import it without forming an import cycle.
31
+ */
32
+ const InterfereContext = createContext(null);
33
+ //#endregion
34
+ 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":";;;;;;;;AAiDA,MAAa,qBAA4C;CACvD,SAAS;EAfY,WAAW;EAAM,WAAW,KAAA;EAexC;CACT,QAAQ;EAfY,mBAAmB;EAAM,iBAAiB;EAetD;CACR,UAAU;EAdV,WAAW;EACX,WAAW,QAAQ,SAAS;EAalB;CACV,QAAQ;CACR,SAAS;EAbY,aAAa;EAAM,mBAAmB;EAalD;CACV;;;;;AAMD,MAAa,mBAAmB,cAC9B,KACD"}
@@ -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,SAAA;AAAA;AAAA,UAGP,SAAA;EACf,MAAA;EACA,IAAA;EAIA,GAAA;AAAA;;;;;AAQF;iBAAsB,qBAAA,CACpB,IAAA,GAAM,qBAAA,GACL,OAAA"}
@@ -1,10 +1,37 @@
1
1
  import { createLogger } from "../util/log.mjs";
2
+ import { resolveRoutePrefix } from "@interfere/constants/route-prefix";
2
3
  //#region src/internal/sw.ts
3
4
  const log = createLogger("sw");
4
- const SW_PATH = "/api/interfere/sw";
5
- function registerServiceWorker() {
5
+ /**
6
+ * Best-effort: never throws, never blocks. SW registration is a
7
+ * page-load-time durability bonus; if it fails, the SDK falls back to
8
+ * direct fetch and behaves exactly like 9.x did.
9
+ */
10
+ async function registerServiceWorker(opts = {}) {
6
11
  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"));
12
+ const prefix = resolveRoutePrefix();
13
+ const swPath = `${prefix}/sw`;
14
+ const swScope = `${prefix}/`;
15
+ try {
16
+ await navigator.serviceWorker.register(swPath, { scope: swScope });
17
+ log.debug("registered at %s (scope %s)", swPath, swScope);
18
+ } catch (err) {
19
+ log.warn("registration failed, falling back to direct fetch", err);
20
+ return;
21
+ }
22
+ if (opts.onMessage) {
23
+ const handler = (event) => {
24
+ const data = event.data;
25
+ if (!isSwMessage(data)) return;
26
+ opts.onMessage?.(data);
27
+ };
28
+ navigator.serviceWorker.addEventListener("message", handler);
29
+ }
30
+ }
31
+ function isSwMessage(value) {
32
+ if (!value || typeof value !== "object") return false;
33
+ const v = value;
34
+ return (v["type"] === "interfere.sw.queued" || v["type"] === "interfere.sw.replayed" || v["type"] === "interfere.sw.dropped") && typeof v["url"] === "string";
8
35
  }
9
36
  //#endregion
10
37
  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":";;;AAIA,MAAM,MAAM,aAAa,KAAK;;;;;;AA2B9B,eAAsB,sBACpB,OAA8B,EAAE,EACjB;CACf,IAAI,OAAO,cAAc,eAAe,EAAE,mBAAmB,YAC3D;CAQF,MAAM,SAAS,oBAAoB;CACnC,MAAM,SAAS,GAAG,OAAO;CACzB,MAAM,UAAU,GAAG,OAAO;CAE1B,IAAI;EACF,MAAM,UAAU,cAAc,SAAS,QAAQ,EAAE,OAAO,SAAS,CAAC;EAClE,IAAI,MAAM,+BAA+B,QAAQ,QAAQ;UAClD,KAAK;EACZ,IAAI,KAAK,qDAAqD,IAAI;EAClE;;CAGF,IAAI,KAAK,WAAW;EAClB,MAAM,WAAW,UAAiC;GAChD,MAAM,OAAO,MAAM;GACnB,IAAI,CAAC,YAAY,KAAK,EACpB;GAEF,KAAK,YAAY,KAAK;;EAExB,UAAU,cAAc,iBAAiB,WAAW,QAAQ;;;AAIhE,SAAS,YAAY,OAAoC;CACvD,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO;CAET,MAAM,IAAI;CACV,QACG,EAAE,YAAY,yBACb,EAAE,YAAY,2BACd,EAAE,YAAY,2BAChB,OAAO,EAAE,WAAW"}
@@ -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,7 @@
1
1
  import { name, version } from "../package.mjs";
2
2
  //#region src/internal/version.ts
3
- const PRODUCER_VERSION = `${name}@${version}`;
3
+ const SDK_NAME = name;
4
+ const SDK_VERSION = version;
5
+ const PRODUCER_VERSION = `${SDK_NAME}@${SDK_VERSION}`;
4
6
  //#endregion
5
- export { PRODUCER_VERSION };
7
+ 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":";;AAEA,MAAa,WAAWA;AACxB,MAAa,cAAcC;AAC3B,MAAa,mBAAmB,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,eAAA;AAAA;;;;;;;;;;;iBAac,sBAAA,CACd,KAAA,EAAO,qBAAA,GACN,gBAAA"}
@@ -0,0 +1,73 @@
1
+ import { registerKernel, unregisterKernel } from "./kernel-registry.mjs";
2
+ import { createKernel, isEnabledByEnvironment } from "./kernel.mjs";
3
+ import { isBotUserAgent } from "../util/bot.mjs";
4
+ //#region src/internal/wrapper-singleton.ts
5
+ /**
6
+ * Per-wrapper kernel-singleton lifecycle. The next/vite/etc wrappers used
7
+ * to copy the same ~100 lines verbatim (init/close/getKernel/subscribe
8
+ * /consent/identity); this is the single source of truth so a fix once
9
+ * applies everywhere.
10
+ *
11
+ * Each wrapper holds its own `WrapperSingleton` in module scope so the
12
+ * kernel is per-wrapper, not per-process. (Microfrontends can run two
13
+ * different wrappers on the same page; each gets its own kernel.)
14
+ */
15
+ function createWrapperSingleton(input) {
16
+ let kernel = null;
17
+ let pending = null;
18
+ const listeners = /* @__PURE__ */ new Set();
19
+ function emit() {
20
+ for (const listener of listeners) listener();
21
+ }
22
+ return {
23
+ init(opts = {}) {
24
+ if (kernel) return Promise.resolve(kernel);
25
+ if (pending) return pending;
26
+ if (!(opts.enabled ?? isEnabledByEnvironment())) return Promise.resolve(null);
27
+ if (isBotUserAgent()) return Promise.resolve(null);
28
+ pending = createKernel({ opts: {
29
+ ...opts,
30
+ _wrapperVersions: [input.producerVersion]
31
+ } }).then((k) => {
32
+ kernel = k;
33
+ if (k) registerKernel(k);
34
+ emit();
35
+ return k;
36
+ }).finally(() => {
37
+ pending = null;
38
+ });
39
+ return pending;
40
+ },
41
+ getKernel() {
42
+ if (!kernel) throw new Error(`Interfere SDK not initialized. Call init() from your ${input.initEntryName} entrypoint.`);
43
+ return kernel;
44
+ },
45
+ getKernelOrNull() {
46
+ return kernel;
47
+ },
48
+ async close() {
49
+ if (!kernel) return;
50
+ const previous = kernel;
51
+ await previous.dispose();
52
+ kernel = null;
53
+ unregisterKernel(previous);
54
+ emit();
55
+ },
56
+ subscribeToKernel(listener) {
57
+ listeners.add(listener);
58
+ return () => {
59
+ listeners.delete(listener);
60
+ };
61
+ },
62
+ consent: {
63
+ get: () => kernel?.consent.get() ?? null,
64
+ set: (value) => kernel?.consent.set(value)
65
+ },
66
+ identity: {
67
+ get: () => kernel?.identity.get() ?? null,
68
+ set: (params) => kernel?.identity.set(params) ?? Promise.resolve()
69
+ }
70
+ };
71
+ }
72
+ //#endregion
73
+ 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":";;;;;;;;;;;;;;AAsDA,SAAgB,uBACd,OACkB;CAClB,IAAI,SAAwB;CAC5B,IAAI,UAAyC;CAC7C,MAAM,4BAAY,IAAI,KAAiB;CAEvC,SAAS,OAAa;EACpB,KAAK,MAAM,YAAY,WACrB,UAAU;;CAId,OAAO;EACL,KAAK,OAAsB,EAAE,EAA0B;GACrD,IAAI,QACF,OAAO,QAAQ,QAAQ,OAAO;GAEhC,IAAI,SACF,OAAO;GAET,IAAI,EAAE,KAAK,WAAW,wBAAwB,GAC5C,OAAO,QAAQ,QAAQ,KAAK;GAE9B,IAAI,gBAAgB,EAClB,OAAO,QAAQ,QAAQ,KAAK;GAO9B,UAAU,aAAa,EAAE,MAAM;IAH7B,GAAG;IACH,kBAAkB,CAAC,MAAM,gBAAgB;IAEA,EAAE,CAAC,CAC3C,MAAM,MAAM;IACX,SAAS;IACT,IAAI,GACF,eAAe,EAAE;IAEnB,MAAM;IACN,OAAO;KACP,CACD,cAAc;IACb,UAAU;KACV;GAEJ,OAAO;;EAGT,YAAoB;GAClB,IAAI,CAAC,QACH,MAAM,IAAI,MACR,wDAAwD,MAAM,cAAc,cAC7E;GAEH,OAAO;;EAGT,kBAAiC;GAC/B,OAAO;;EAGT,MAAM,QAAuB;GAC3B,IAAI,CAAC,QACH;GAEF,MAAM,WAAW;GACjB,MAAM,SAAS,SAAS;GACxB,SAAS;GACT,iBAAiB,SAAS;GAC1B,MAAM;;EAGR,kBAAkB,UAAkC;GAClD,UAAU,IAAI,SAAS;GACvB,aAAa;IACX,UAAU,OAAO,SAAS;;;EAI9B,SAAS;GACP,WAAW,QAAQ,QAAQ,KAAK,IAAI;GACpC,MAAM,UAAU,QAAQ,QAAQ,IAAI,MAAM;GAC3C;EAED,UAAU;GACR,WAAW,QAAQ,SAAS,KAAK,IAAI;GACrC,MAAM,WAAW,QAAQ,SAAS,IAAI,OAAO,IAAI,QAAQ,SAAS;GACnE;EACF"}
package/dist/package.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
2
  var name = "@interfere/react";
3
- var version = "9.0.2";
3
+ var version = "10.0.0";
4
4
  //#endregion
5
5
  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,MAAA"}
@@ -1,5 +1,5 @@
1
- import { seen } from "../internal/errors.mjs";
2
- import { MECHANISM_TYPE, shouldDropBrowserExtensionNoise, shouldDropUnresolvableStack, toError, toExceptions } from "@interfere/types/sdk/errors";
1
+ import { onConsoleCall } from "../internal/console-patch.mjs";
2
+ import { MECHANISM_TYPE, isNonErrorException, toException } from "@interfere/types/sdk/errors";
3
3
  //#region src/plugins/errors.ts
4
4
  /**
5
5
  * V8's default stack limit is 10. Deep React trees routinely exceed that
@@ -7,19 +7,11 @@ import { MECHANISM_TYPE, shouldDropBrowserExtensionNoise, shouldDropUnresolvable
7
7
  * internals in the captured stack. Matches Sentry's browser SDK.
8
8
  */
9
9
  const STACK_TRACE_LIMIT = 50;
10
- let capturing = false;
11
10
  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
- }
11
+ ctx.recordException(opts.error, {
12
+ mechanism: opts.mechanism,
13
+ ...opts.fallbackFrame && !isNonErrorException(opts.error) ? { fallbackFrames: [opts.fallbackFrame] } : {}
14
+ });
23
15
  }
24
16
  /**
25
17
  * Finds the first `Error` instance in a list of `console.error` arguments.
@@ -35,15 +27,15 @@ const errorsPlugin = {
35
27
  name: "errors",
36
28
  setup(ctx) {
37
29
  const originalOnError = globalThis.onerror;
38
- const originalConsoleError = globalThis.console.error;
39
30
  const originalStackTraceLimit = Error.stackTraceLimit;
40
31
  if (Error.stackTraceLimit < STACK_TRACE_LIMIT) Error.stackTraceLimit = STACK_TRACE_LIMIT;
41
32
  globalThis.onerror = (msg, source, line, col, error) => {
42
33
  if (error instanceof Error) {
43
34
  const fallbackFrame = typeof source === "string" ? {
44
- fileName: source,
45
- ...typeof line === "number" ? { lineNumber: line } : {},
46
- ...typeof col === "number" ? { columnNumber: col } : {}
35
+ id: "fallback-0",
36
+ file: source,
37
+ ...typeof line === "number" ? { line } : {},
38
+ ...typeof col === "number" ? { column: col } : {}
47
39
  } : null;
48
40
  capture(ctx, {
49
41
  error,
@@ -59,7 +51,7 @@ const errorsPlugin = {
59
51
  };
60
52
  const onUnhandledRejection = (event) => {
61
53
  capture(ctx, {
62
- error: toError(event.reason),
54
+ error: toException(event.reason),
63
55
  mechanism: {
64
56
  type: MECHANISM_TYPE.browser.onunhandledrejection,
65
57
  handled: false,
@@ -67,9 +59,10 @@ const errorsPlugin = {
67
59
  }
68
60
  });
69
61
  };
70
- globalThis.addEventListener("unhandledrejection", onUnhandledRejection);
71
- globalThis.console.error = (...args) => {
72
- originalConsoleError.apply(globalThis.console, args);
62
+ const supportsEventTarget = typeof globalThis.addEventListener === "function" && typeof globalThis.removeEventListener === "function";
63
+ if (supportsEventTarget) globalThis.addEventListener("unhandledrejection", onUnhandledRejection);
64
+ const unsubscribeConsole = onConsoleCall((level, args) => {
65
+ if (level !== "error") return;
73
66
  const error = findErrorArg(args);
74
67
  if (error) capture(ctx, {
75
68
  error,
@@ -78,12 +71,12 @@ const errorsPlugin = {
78
71
  handled: true
79
72
  }
80
73
  });
81
- };
74
+ });
82
75
  return () => {
83
76
  Error.stackTraceLimit = originalStackTraceLimit;
84
77
  globalThis.onerror = originalOnError;
85
- globalThis.removeEventListener("unhandledrejection", onUnhandledRejection);
86
- globalThis.console.error = originalConsoleError;
78
+ if (supportsEventTarget) globalThis.removeEventListener("unhandledrejection", onUnhandledRejection);
79
+ unsubscribeConsole();
87
80
  };
88
81
  }
89
82
  };
@@ -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":";;;;;;;;AAiBA,MAAM,oBAAoB;AAe1B,SAAS,QAAQ,KAAoB,MAAmB;CACtD,IAAI,gBAAgB,KAAK,OAAO;EAC9B,WAAW,KAAK;EAChB,GAAI,KAAK,iBAAiB,CAAC,oBAAoB,KAAK,MAAM,GACtD,EAAE,gBAAgB,CAAC,KAAK,cAAc,EAAE,GACxC,EAAE;EACP,CAAC;;;;;;;;AASJ,SAAS,aAAa,MAAwC;CAC5D,KAAK,MAAM,OAAO,MAChB,IAAI,eAAe,OACjB,OAAO;CAGX,OAAO;;AAGT,MAAa,eAAuB;CAClC,MAAM;CAEN,MAAM,KAAK;EACT,MAAM,kBAAkB,WAAW;EACnC,MAAM,0BAA0B,MAAM;EACtC,IAAI,MAAM,kBAAkB,mBAC1B,MAAM,kBAAkB;EAG1B,WAAW,WAAW,KAAK,QAAQ,MAAM,KAAK,UAAU;GACtD,IAAI,iBAAiB,OAAO;IAC1B,MAAM,gBACJ,OAAO,WAAW,WACd;KACE,IAAI;KACJ,MAAM;KACN,GAAI,OAAO,SAAS,WAAW,EAAE,MAAM,GAAG,EAAE;KAC5C,GAAI,OAAO,QAAQ,WAAW,EAAE,QAAQ,KAAK,GAAG,EAAE;KACnD,GACD;IAEN,QAAQ,KAAK;KACX;KACA,WAAW;MAAE,MAAM,eAAe,QAAQ;MAAS,SAAS;MAAO;KACnE,GAAI,gBAAgB,EAAE,eAAe,GAAG,EAAE;KAC3C,CAAC;;GAEJ,IAAI,OAAO,oBAAoB,YAC7B,OAAO,gBAAgB,KAAK,YAAY,KAAK,QAAQ,MAAM,KAAK,MAAM;GAExE,OAAO;;EAGT,MAAM,wBAAwB,UAAiC;GAQ7D,QAAQ,KAAK;IACX,OAAO,YAAY,MAAM,OAAO;IAChC,WAAW;KACT,MAAM,eAAe,QAAQ;KAC7B,SAAS;KACT,GAAI,MAAM,kBAAkB,QAAQ,EAAE,GAAG,EAAE,WAAW,MAAM;KAC7D;IACF,CAAC;;EAMJ,MAAM,sBACJ,OAAO,WAAW,qBAAqB,cACvC,OAAO,WAAW,wBAAwB;EAC5C,IAAI,qBACF,WAAW,iBAAiB,sBAAsB,qBAAqB;EAGzE,MAAM,qBAAqB,eAAe,OAAO,SAAS;GACxD,IAAI,UAAU,SACZ;GAEF,MAAM,QAAQ,aAAa,KAAK;GAChC,IAAI,OACF,QAAQ,KAAK;IACX;IACA,WAAW;KACT,MAAM,eAAe,QAAQ;KAC7B,SAAS;KACV;IACF,CAAC;IAEJ;EAEF,aAAa;GACX,MAAM,kBAAkB;GACxB,WAAW,UAAU;GACrB,IAAI,qBACF,WAAW,oBACT,sBACA,qBACD;GAEH,oBAAoB;;;CAGzB"}
@@ -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"}
@@ -4,8 +4,7 @@ import { PLUGIN_MANIFEST } from "@interfere/types/sdk/plugins/manifest";
4
4
  const log = createLogger("plugins");
5
5
  const LOADERS = {
6
6
  errors: () => import("../errors.mjs"),
7
- device: () => import("../device.mjs"),
8
- pageEvents: () => import("../pages.mjs"),
7
+ logs: () => import("../logs.mjs"),
9
8
  rageClick: () => import("../rage-clicks.mjs"),
10
9
  replay: () => import("../replay.mjs")
11
10
  };
@@ -31,13 +30,5 @@ async function loadPlugin(key, context) {
31
30
  return null;
32
31
  }
33
32
  }
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
33
  //#endregion
43
- export { loadPlugin, loadPlugins, resolveFeatures };
34
+ 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":";;;AAQA,MAAM,MAAM,aAAa,UAAU;AASnC,MAAM,UAAoD;CACxD,cAAc,OAAO;CACrB,YAAY,OAAO;CACnB,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;CAC5B,OAAO;EAAE,GAAG;EAAU,GAAG;EAAW;;AAGtC,SAAS,cAAc,KAA2C;CAChE,OAAO,aAAa,OAAO,OAAQ,IAAI,QAAmB,UAAU,aAChE,IAAI,UACH;;AAGP,eAAsB,WACpB,KACA,SAC+B;CAC/B,MAAM,SAAS,QAAQ;CACvB,IAAI,CAAC,QACH,OAAO;CAGT,IAAI;EAGF,MAAM,UADS,cAAc,MADX,QAAQ,CAEJ,CAAC,MAAM,QAAQ;EACrC,IAAI,MAAM,aAAa,IAAI;EAC3B,OAAO,OAAO,YAAY,aAAa,UAAU;SAC3C;EACN,IAAI,MAAM,4BAA4B,IAAI;EAC1C,OAAO"}
@@ -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"}