@interfere/react 10.0.1-canary.1 → 11.0.0-canary.3

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 (129) hide show
  1. package/README.md +1 -1
  2. package/dist/internal/capture-boundary.mjs +1 -1
  3. package/dist/internal/capture.mjs +1 -1
  4. package/dist/internal/hydration.d.mts +10 -0
  5. package/dist/internal/hydration.mjs +1 -0
  6. package/dist/internal/kernel.d.mts +22 -3
  7. package/dist/internal/kernel.mjs +1 -1
  8. package/dist/internal/otel/index.d.mts +2 -2
  9. package/dist/internal/otel/initial-navigation.d.mts +13 -0
  10. package/dist/internal/otel/initial-navigation.mjs +1 -0
  11. package/dist/internal/otel/instrumentations.d.mts +44 -1
  12. package/dist/internal/otel/instrumentations.mjs +1 -1
  13. package/dist/internal/otel/provider.mjs +1 -1
  14. package/dist/internal/page-lifecycle.d.mts +2 -15
  15. package/dist/internal/page-lifecycle.mjs +1 -1
  16. package/dist/internal/react-context.d.mts +1 -0
  17. package/dist/internal/react-context.mjs +1 -1
  18. package/dist/internal/server-digest.d.mts +9 -0
  19. package/dist/internal/server-digest.mjs +1 -0
  20. package/dist/internal/third-party-hosts.d.mts +14 -0
  21. package/dist/internal/third-party-hosts.mjs +1 -0
  22. package/dist/internal/wrapper-singleton.d.mts +1 -0
  23. package/dist/internal/wrapper-singleton.mjs +1 -1
  24. package/dist/package.mjs +1 -1
  25. package/dist/plugins/errors.d.mts +1 -1
  26. package/dist/plugins/errors.mjs +1 -1
  27. package/dist/plugins/lib/loader.mjs +1 -1
  28. package/dist/plugins/logs.d.mts +1 -1
  29. package/dist/plugins/logs.mjs +1 -1
  30. package/dist/plugins/rage-clicks.d.mts +1 -1
  31. package/dist/plugins/rage-clicks.mjs +1 -1
  32. package/dist/plugins/replay/buffer.d.mts +13 -0
  33. package/dist/plugins/replay/buffer.mjs +1 -0
  34. package/dist/plugins/replay/chunking.d.mts +21 -0
  35. package/dist/plugins/replay/chunking.mjs +1 -0
  36. package/dist/plugins/replay/compressor-host.d.mts +9 -0
  37. package/dist/plugins/replay/compressor-host.mjs +1 -0
  38. package/dist/plugins/replay/delivery.d.mts +23 -0
  39. package/dist/plugins/replay/delivery.mjs +1 -0
  40. package/dist/plugins/replay/index.d.mts +6 -0
  41. package/dist/plugins/replay/index.mjs +1 -0
  42. package/dist/plugins/replay/recorder.d.mts +8 -0
  43. package/dist/plugins/replay/recorder.mjs +1 -0
  44. package/dist/sw.mjs +1 -1
  45. package/dist/tracking/api.d.mts +1 -0
  46. package/dist/tracking/api.mjs +1 -1
  47. package/dist/tracking/session.d.mts +1 -0
  48. package/dist/tracking/session.mjs +1 -1
  49. package/package.json +29 -34
  50. package/dist/api.d.mts.map +0 -1
  51. package/dist/api.mjs.map +0 -1
  52. package/dist/error-boundary.d.mts.map +0 -1
  53. package/dist/error-boundary.mjs.map +0 -1
  54. package/dist/internal/browser-context.d.mts.map +0 -1
  55. package/dist/internal/browser-context.mjs.map +0 -1
  56. package/dist/internal/capture-boundary.d.mts.map +0 -1
  57. package/dist/internal/capture-boundary.mjs.map +0 -1
  58. package/dist/internal/capture.d.mts.map +0 -1
  59. package/dist/internal/capture.mjs.map +0 -1
  60. package/dist/internal/config.d.mts.map +0 -1
  61. package/dist/internal/config.mjs.map +0 -1
  62. package/dist/internal/consent.d.mts.map +0 -1
  63. package/dist/internal/consent.mjs.map +0 -1
  64. package/dist/internal/console-patch.d.mts.map +0 -1
  65. package/dist/internal/console-patch.mjs.map +0 -1
  66. package/dist/internal/dom/actionable.d.mts.map +0 -1
  67. package/dist/internal/dom/actionable.mjs.map +0 -1
  68. package/dist/internal/kernel-registry.d.mts.map +0 -1
  69. package/dist/internal/kernel-registry.mjs.map +0 -1
  70. package/dist/internal/kernel.d.mts.map +0 -1
  71. package/dist/internal/kernel.mjs.map +0 -1
  72. package/dist/internal/otel/exporter.d.mts.map +0 -1
  73. package/dist/internal/otel/exporter.mjs.map +0 -1
  74. package/dist/internal/otel/instrumentations.d.mts.map +0 -1
  75. package/dist/internal/otel/instrumentations.mjs.map +0 -1
  76. package/dist/internal/otel/page-scope-context-manager.d.mts.map +0 -1
  77. package/dist/internal/otel/page-scope-context-manager.mjs.map +0 -1
  78. package/dist/internal/otel/propagation.d.mts.map +0 -1
  79. package/dist/internal/otel/propagation.mjs.map +0 -1
  80. package/dist/internal/otel/provider.d.mts.map +0 -1
  81. package/dist/internal/otel/provider.mjs.map +0 -1
  82. package/dist/internal/otel/web-vitals.d.mts.map +0 -1
  83. package/dist/internal/otel/web-vitals.mjs.map +0 -1
  84. package/dist/internal/page-lifecycle.d.mts.map +0 -1
  85. package/dist/internal/page-lifecycle.mjs.map +0 -1
  86. package/dist/internal/plugin-runtime.d.mts.map +0 -1
  87. package/dist/internal/plugin-runtime.mjs.map +0 -1
  88. package/dist/internal/react-context.d.mts.map +0 -1
  89. package/dist/internal/react-context.mjs.map +0 -1
  90. package/dist/internal/sw.d.mts.map +0 -1
  91. package/dist/internal/sw.mjs.map +0 -1
  92. package/dist/internal/version.d.mts.map +0 -1
  93. package/dist/internal/version.mjs.map +0 -1
  94. package/dist/internal/wrapper-singleton.d.mts.map +0 -1
  95. package/dist/internal/wrapper-singleton.mjs.map +0 -1
  96. package/dist/package.mjs.map +0 -1
  97. package/dist/plugins/errors.d.mts.map +0 -1
  98. package/dist/plugins/errors.mjs.map +0 -1
  99. package/dist/plugins/lib/loader.d.mts.map +0 -1
  100. package/dist/plugins/lib/loader.mjs.map +0 -1
  101. package/dist/plugins/lib/types.d.mts.map +0 -1
  102. package/dist/plugins/logs.d.mts.map +0 -1
  103. package/dist/plugins/logs.mjs.map +0 -1
  104. package/dist/plugins/rage-clicks.d.mts.map +0 -1
  105. package/dist/plugins/rage-clicks.mjs.map +0 -1
  106. package/dist/plugins/replay.d.mts +0 -6
  107. package/dist/plugins/replay.d.mts.map +0 -1
  108. package/dist/plugins/replay.mjs +0 -1
  109. package/dist/plugins/replay.mjs.map +0 -1
  110. package/dist/provider.d.mts.map +0 -1
  111. package/dist/provider.mjs.map +0 -1
  112. package/dist/react-error-handler.d.mts.map +0 -1
  113. package/dist/react-error-handler.mjs.map +0 -1
  114. package/dist/tracking/api.d.mts.map +0 -1
  115. package/dist/tracking/api.mjs.map +0 -1
  116. package/dist/tracking/device.d.mts.map +0 -1
  117. package/dist/tracking/device.mjs.map +0 -1
  118. package/dist/tracking/geo.d.mts.map +0 -1
  119. package/dist/tracking/geo.mjs.map +0 -1
  120. package/dist/tracking/session.d.mts.map +0 -1
  121. package/dist/tracking/session.mjs.map +0 -1
  122. package/dist/util/bot.d.mts.map +0 -1
  123. package/dist/util/bot.mjs.map +0 -1
  124. package/dist/util/global.d.mts.map +0 -1
  125. package/dist/util/global.mjs.map +0 -1
  126. package/dist/util/log.d.mts.map +0 -1
  127. package/dist/util/log.mjs.map +0 -1
  128. package/dist/util/stringify.d.mts.map +0 -1
  129. package/dist/util/stringify.mjs.map +0 -1
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://www.npmjs.com/package/@interfere/react"><img src="https://img.shields.io/npm/v/@interfere/react.svg" alt="npm version" /></a>
13
- <a href="https://github.com/interfere-inc/interfere/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@interfere/react.svg" alt="License" /></a>
13
+ <a href="https://interfere.ghe.com/interfere/interfere/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@interfere/react.svg" alt="License" /></a>
14
14
  <a href="https://www.npmjs.com/package/@interfere/react"><img src="https://img.shields.io/npm/dm/@interfere/react.svg" alt="npm downloads" /></a>
15
15
  </p>
16
16
 
@@ -1 +1 @@
1
- "use client";import{captureReactError}from"./capture.mjs";import{InterfereContext}from"./react-context.mjs";import{MECHANISM_TYPE}from"@interfere/types/sdk/errors";import{Component}from"react";var CaptureBoundary=class extends Component{static contextType=InterfereContext;state={error:null};static getDerivedStateFromError(error){return{error}}render(){if(this.state.error)throw captureReactError(this.context?.kernel??null,this.state.error,null,{type:MECHANISM_TYPE.react.captureBoundary,handled:!1}),this.state.error;return this.props.children}};export{CaptureBoundary};
1
+ "use client";import{captureReactError}from"./capture.mjs";import{InterfereContext}from"./react-context.mjs";import{hasServerDigest}from"./server-digest.mjs";import{MECHANISM_TYPE}from"@interfere/types/sdk/errors";import{Component}from"react";var CaptureBoundary=class extends Component{static contextType=InterfereContext;state={error:null};static getDerivedStateFromError(error){return{error}}render(){if(this.state.error)throw hasServerDigest(this.state.error)||captureReactError(this.context?.kernel??null,this.state.error,null,{type:MECHANISM_TYPE.react.captureBoundary,handled:!1}),this.state.error;return this.props.children}};export{CaptureBoundary};
@@ -1 +1 @@
1
- import{parseReactComponentStack}from"@interfere/types/sdk/errors";function captureReactError(kernel,error,componentStack,mechanism){kernel&&kernel.recordException(error,{mechanism,...componentStack?{appendFrames:parseReactComponentStack(componentStack)}:{},...error.digest?{errorDigest:error.digest}:{}})}export{captureReactError};
1
+ import{isHydrationError}from"./hydration.mjs";import{parseReactComponentStack}from"@interfere/types/sdk/errors";function captureReactError(kernel,error,componentStack,mechanism){if(!kernel)return;let hydration=isHydrationError(error);kernel.recordException(error,{mechanism:hydration?{...mechanism,synthetic:!1}:mechanism,...hydration?{subtype:`hydration`}:{},...componentStack?{appendFrames:parseReactComponentStack(componentStack)}:{},...error.digest?{errorDigest:error.digest}:{}})}export{captureReactError};
@@ -0,0 +1,10 @@
1
+ //#region src/internal/hydration.d.ts
2
+ /**
3
+ * True when an error is a React hydration mismatch — detected by the stable
4
+ * `react.dev/errors/{418,423,425}` codes (production) or React's hydration
5
+ * message wording (development). String-matches React internals, so it tracks
6
+ * React's phrasing; the numeric error codes are the durable anchor.
7
+ */
8
+ declare function isHydrationError(error: unknown): boolean;
9
+ //#endregion
10
+ export { isHydrationError };
@@ -0,0 +1 @@
1
+ const HYDRATION_CODE=/(?:Minified React error #|react\.dev\/errors\/)(?:418|423|425)/,HYDRATION_TEXT=/hydration failed|error while hydrating|hydrated but|text content does not match|server[- ]rendered html|did(?:n't| not) match the client/i;function isHydrationError(error){if(!(error instanceof Error))return!1;let message=error.message;return HYDRATION_CODE.test(message)||HYDRATION_TEXT.test(message)}export{isHydrationError};
@@ -1,13 +1,14 @@
1
1
  import { PluginContext } from "../plugins/lib/types.mjs";
2
2
  import { PluginOverrides } from "../plugins/lib/loader.mjs";
3
3
  import { SessionTracker } from "../tracking/api.mjs";
4
+ import { CaptureFailedRequestsOption, CaptureFailedRequestsOptions, HttpStatusCodeRange } from "./otel/instrumentations.mjs";
4
5
  import { OtelProviderHandle } from "./otel/provider.mjs";
5
6
  import { PluginRuntime } from "./plugin-runtime.mjs";
6
7
  import { NonErrorException } from "@interfere/types/sdk/errors";
7
8
  import { ConsentState } from "@interfere/types/sdk/plugins/manifest";
8
9
  import { ReleaseSlug } from "@interfere/types/releases/slug";
9
10
  import { MetricReader } from "@opentelemetry/sdk-metrics";
10
- import { LogRecordProcessor } from "@opentelemetry/sdk-logs";
11
+ import { LogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs";
11
12
  import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
12
13
  import { IngestedFrame } from "@interfere/types/data/frame";
13
14
  import { SessionId } from "@interfere/types/data/session";
@@ -32,7 +33,7 @@ interface KernelOptions {
32
33
  /**
33
34
  * Override the OTel `service.name` resource attribute. Defaults to
34
35
  * `"interfere-sdk"`. Customers running multiple frontends in one
35
- * monitoring backend (e.g. `"@interfere/homepage"` vs
36
+ * monitoring backend (e.g. `"@interfere/website"` vs
36
37
  * `"@interfere/app"`) set this so spans/metrics/logs slice
37
38
  * cleanly by surface.
38
39
  */
@@ -53,6 +54,15 @@ interface KernelOptions {
53
54
  * need to touch any of these — sensible defaults ship out of the box.
54
55
  */
55
56
  interface TracingOptions {
57
+ /**
58
+ * Promote failed `fetch` / `XHR` responses to exception events on the
59
+ * active span. **On by default** — captures same-origin (and
60
+ * `propagateContextUrls`) 4xx + 5xx, deduped by `method+status+path` and
61
+ * rate-limited so a flapping endpoint can't flood. Pass `false` to
62
+ * disable, or `{ statusCodes }` to tune the ranges.
63
+ * See {@link CaptureFailedRequestsOption}.
64
+ */
65
+ captureFailedRequests?: CaptureFailedRequestsOption;
56
66
  /**
57
67
  * Additional URL patterns to skip when creating fetch / XHR spans.
58
68
  * The SDK's own collector endpoint is always ignored; customer-side
@@ -119,6 +129,7 @@ interface KernelConsent {
119
129
  set(value?: ConsentState): void;
120
130
  }
121
131
  interface KernelIdentity {
132
+ clear(): void;
122
133
  get(): IdentifyParams | null;
123
134
  set(params: IdentifyParams): Promise<void>;
124
135
  }
@@ -154,6 +165,12 @@ interface RecordExceptionOpts {
154
165
  */
155
166
  readonly fallbackFrames?: readonly IngestedFrame[];
156
167
  readonly mechanism: ErrorMechanism;
168
+ /**
169
+ * A distinct error sub-classification layered on top of the mechanism —
170
+ * currently `"hydration"` for React hydration mismatches. Lets the agent
171
+ * triage by kind without re-parsing the message.
172
+ */
173
+ readonly subtype?: string;
157
174
  }
158
175
  /**
159
176
  * The replacement for the old `Client` god-object. Pure construction graph
@@ -173,6 +190,7 @@ declare class Kernel implements PluginContext {
173
190
  readonly session: KernelSession;
174
191
  private readonly tracker;
175
192
  private readonly seen;
193
+ private readonly initialNavigationSeen;
176
194
  private runtime;
177
195
  private otel;
178
196
  private otelDispose;
@@ -217,6 +235,7 @@ declare class Kernel implements PluginContext {
217
235
  body: string;
218
236
  attributes?: Record<string, string>;
219
237
  }): void;
238
+ recordInitialNavigation(loggerProvider: LoggerProvider): void;
220
239
  /**
221
240
  * Force-flushes the OTel exporters. Returns once they've settled
222
241
  * (success or failure) so callers driving unload — page-lifecycle
@@ -264,4 +283,4 @@ declare function isEnabledByEnvironment(): boolean;
264
283
  */
265
284
  declare function readReleaseSlugFromGlobals(): ReleaseSlug | null;
266
285
  //#endregion
267
- export { Kernel, KernelConsent, KernelDevice, KernelIdentity, KernelInternalOptions, KernelOptions, KernelSession, RecordExceptionOpts, TracingOptions, buildSdkStack, createKernel, isEnabledByEnvironment, readReleaseSlugFromGlobals };
286
+ export { type CaptureFailedRequestsOption, type CaptureFailedRequestsOptions, type HttpStatusCodeRange, Kernel, KernelConsent, KernelDevice, KernelIdentity, KernelInternalOptions, KernelOptions, KernelSession, RecordExceptionOpts, TracingOptions, buildSdkStack, createKernel, isEnabledByEnvironment, readReleaseSlugFromGlobals };
@@ -1 +1 @@
1
- import{createLogger}from"../util/log.mjs";import{getGlobal}from"../util/global.mjs";import{resolveTargets}from"./config.mjs";import{SessionTracker}from"../tracking/api.mjs";import{safeStringify}from"../util/stringify.mjs";import{onPageHidden}from"./page-lifecycle.mjs";import{PluginRuntime}from"./plugin-runtime.mjs";import{registerServiceWorker}from"./sw.mjs";import{PRODUCER_VERSION}from"./version.mjs";import{isNonErrorException,shouldDropBrowserExtensionNoise,shouldDropUnresolvableStack,toExceptions}from"@interfere/types/sdk/errors";import{trace}from"@opentelemetry/api";import{releaseSlugSchema}from"@interfere/types/releases/slug";const log=createLogger(`kernel`),TRAILING_SLASH_RE=/\/$/;function buildSdkStack(wrapperVersions){return[...wrapperVersions??[],PRODUCER_VERSION]}function buildExceptionEventAttrs(value,opts){let attrs={"interfere.exception.mechanism":opts.mechanism.type,"interfere.exception.handled":String(opts.mechanism.handled)};return opts.errorDigest&&(attrs[`interfere.error.digest`]=opts.errorDigest),isNonErrorException(value)?(attrs[`exception.type`]=value.type,attrs[`exception.message`]=value.value,attrs[`interfere.exception.kind`]=`non-error`,attrs[`interfere.exception.serialized`]=safeStringify(value.serialized),attrs):(attrs[`exception.type`]=value.name,attrs[`exception.message`]=value.message,attrs[`interfere.exception.kind`]=`error`,value.stack&&(attrs[`exception.stacktrace`]=value.stack),attrs[`interfere.exception.chain`]=safeStringify(toExceptions(value,opts.mechanism)),opts.appendFrames&&opts.appendFrames.length>0&&(attrs[`interfere.exception.appended_frames`]=safeStringify(opts.appendFrames)),opts.fallbackFrames&&opts.fallbackFrames.length>0&&(attrs[`interfere.exception.fallback_frames`]=safeStringify(opts.fallbackFrames)),attrs)}var Kernel=class{consent;identity;device;session;tracker;seen=new WeakSet;runtime=null;otel=null;otelDispose=null;unsubscribePageHidden=null;constructor(tracker){this.tracker=tracker,this.consent={get:()=>this.requireRuntime().getConsent(),set:value=>{let runtime=this.requireRuntime();value?runtime.setConsent(value):runtime.resetConsent()}},this.identity={get:()=>this.tracker.getIdentity(),set:params=>this.tracker.identify(params)},this.device={getDeviceId:()=>this.tracker.getDeviceId(),getFpHash:()=>this.tracker.getFpHash()},this.session={getId:()=>this.tracker.sessionId(),getWindowId:()=>this.tracker.windowId()},this.unsubscribePageHidden=onPageHidden(()=>{this.flush().catch(()=>void 0)})}attachRuntime(runtime){this.runtime=runtime}requireRuntime(){if(!this.runtime)throw Error(`Kernel runtime not attached. createKernel must call attachRuntime before any consent/dispose path runs.`);return this.runtime}getSessionId(){return this.tracker.sessionId()??``}recordException(value,opts){if(!isNonErrorException(value)){if(this.seen.has(value))return;this.seen.add(value)}if(!isNonErrorException(value)){let stacks=[],current=value;for(let depth=0;current&&depth<5;depth+=1)current.stack&&stacks.push(current.stack),current=current.cause instanceof Error?current.cause:void 0;if(shouldDropBrowserExtensionNoise(stacks)||shouldDropUnresolvableStack(stacks))return}let span=trace.getActiveSpan();span&&span.addEvent(`exception`,buildExceptionEventAttrs(value,opts))}recordLog(input){this.otel&&this.otel.loggerProvider.getLogger(`@interfere/react`).emit({severityText:input.severityText,severityNumber:input.severityNumber,body:input.body,...input.attributes?{attributes:input.attributes}:{}})}async flush(){this.otel&&await this.otel.flush()}attachOtel(handle,dispose){this.otel=handle,this.otelDispose=dispose}syncConsent(value){let runtime=this.requireRuntime();if(value){runtime.setConsent(value);return}runtime.resetConsent()}async dispose(){this.unsubscribePageHidden?.(),this.unsubscribePageHidden=null,this.runtime&&await this.runtime.dispose(),this.tracker.dispose(),this.otelDispose?.(),this.otelDispose=null,this.otel&&=(await this.otel.shutdown(),null)}};async function createKernel(input={}){let{opts={}}=input,fetcher=opts.fetch??globalThis.fetch.bind(globalThis),targets=resolveTargets();log.info(`collector: %s`,targets.collectorBaseUrl),getGlobal(`__INTERFERE_FORCE_ENABLE__`)&&log.warn(`FORCE_ENABLE is active. Events will be dropped by production collectors. This should only be set during local development.`);let tracker=new SessionTracker({target:targets.session,fetcher});opts._wrapperVersions?.length&&(globalThis.__INTERFERE_SDK_STACK__=opts._wrapperVersions);let kernel=new Kernel(tracker),runtime=new PluginRuntime(kernel,opts.plugins,opts.consent);if(kernel.attachRuntime(runtime),tracker.start(),runtime.start(),opts.tracing!==!1){let tracingOpts=typeof opts.tracing==`object`?opts.tracing:void 0;await wireOtel(kernel,targets,opts._wrapperVersions,{...tracingOpts??{},...opts.serviceName?{serviceName:opts.serviceName}:{},...opts._internalAdditionalSpanProcessors?{additionalSpanProcessors:opts._internalAdditionalSpanProcessors}:{},...opts._internalAdditionalLogRecordProcessors?{additionalLogRecordProcessors:opts._internalAdditionalLogRecordProcessors}:{},...opts._internalAdditionalMetricReaders?{additionalMetricReaders:opts._internalAdditionalMetricReaders}:{}})}return await fetchRemoteConfig(targets.config,fetcher,runtime),registerServiceWorker({onMessage:msg=>recordSwMessage(kernel,msg)}).catch(()=>{}),kernel}function recordSwMessage(kernel,msg){kernel.recordLog({severityText:`info`,severityNumber:9,body:msg.type,attributes:{"url.full":msg.url,...msg.reason?{"interfere.sw.reason":msg.reason}:{}}})}async function wireOtel(kernel,targets,wrapperVersions,tracing){let{buildOtelProvider,captureWebVitals,readPropagationFromDocument,registerBundledInstrumentations}=await import(`./otel/index.mjs`),collectorUrl=targets.collectorBaseUrl.replace(TRAILING_SLASH_RE,``),releaseSlug=readReleaseSlugFromGlobals(),handle=buildOtelProvider({collectorUrl,authHeaders:targets.ingest.headers,sdkStack:buildSdkStack(wrapperVersions),getSessionId:()=>kernel.session.getId(),deviceId:kernel.device.getDeviceId(),releaseSlug,...tracing.serviceName?{serviceName:tracing.serviceName}:{},...tracing.additionalSpanProcessors?{additionalSpanProcessors:tracing.additionalSpanProcessors}:{},...tracing.additionalLogRecordProcessors?{additionalLogRecordProcessors:tracing.additionalLogRecordProcessors}:{},...tracing.additionalMetricReaders?{additionalMetricReaders:tracing.additionalMetricReaders}:{}}),extracted=readPropagationFromDocument();extracted&&handle.contextManager.setPageScope(extracted);let dispose=registerBundledInstrumentations({tracerProvider:handle.tracerProvider,ignoreUrls:[`${collectorUrl}/v2/sink`,targets.config.url,targets.session.url],...tracing.propagateContextUrls?{propagateContextUrls:tracing.propagateContextUrls}:{},...tracing.resolveRoute?{resolveRoute:tracing.resolveRoute}:{}});tracing.webVitals!==!1&&captureWebVitals({meter:handle.meterProvider.getMeter(`@interfere/react/web-vitals`),flush:()=>handle.flush(),...tracing.resolveRoute?{resolveRoute:tracing.resolveRoute}:{}}),kernel.attachOtel(handle,dispose)}async function fetchRemoteConfig(configTarget,fetcher,runtime){try{let headers=Object.fromEntries(configTarget.headers.entries()),res=await fetcher(configTarget.url,{method:`GET`,headers,signal:AbortSignal.timeout(1e4)});if(!res.ok)return;let config=await res.json();config?.plugins&&(runtime.applyRemoteConfig(config.plugins),log.debug(`applied remote config`))}catch{log.warn(`remote config fetch failed, using local defaults`)}}function isEnabledByEnvironment(){if(typeof process>`u`||!process.env)return!0;let nodeEnv=process.env.NODE_ENV;return nodeEnv===`production`||nodeEnv===void 0?!0:!!(getGlobal(`__INTERFERE_FORCE_ENABLE__`)||process.env.INTERFERE_FORCE_ENABLE)}function readReleaseSlugFromGlobals(){let value=getGlobal(`__INTERFERE_RELEASE_SLUG__`);return typeof value==`string`?releaseSlugSchema.parse(value):null}export{Kernel,buildSdkStack,createKernel,isEnabledByEnvironment,readReleaseSlugFromGlobals};
1
+ import{createLogger}from"../util/log.mjs";import{getGlobal}from"../util/global.mjs";import{resolveTargets}from"./config.mjs";import{SessionTracker}from"../tracking/api.mjs";import{safeStringify}from"../util/stringify.mjs";import{recordInitialNavigation}from"./otel/initial-navigation.mjs";import{onPageHidden}from"./page-lifecycle.mjs";import{PluginRuntime}from"./plugin-runtime.mjs";import{registerServiceWorker}from"./sw.mjs";import{PRODUCER_VERSION}from"./version.mjs";import{isNonErrorException,shouldDropBrowserExtensionNoise,shouldDropUnresolvableStack,toExceptions}from"@interfere/types/sdk/errors";import{trace}from"@opentelemetry/api";import{releaseSlugSchema}from"@interfere/types/releases/slug";const log=createLogger(`kernel`),TRAILING_SLASH_RE=/\/$/;function buildSdkStack(wrapperVersions){return[...wrapperVersions??[],PRODUCER_VERSION]}function buildExceptionEventAttrs(value,opts){let attrs={"interfere.exception.mechanism":opts.mechanism.type,"interfere.exception.handled":opts.mechanism.handled};return opts.errorDigest&&(attrs[`interfere.error.digest`]=opts.errorDigest),opts.subtype&&(attrs[`interfere.exception.subtype`]=opts.subtype),isNonErrorException(value)?(attrs[`exception.type`]=value.type,attrs[`exception.message`]=value.value,attrs[`interfere.exception.kind`]=`non-error`,attrs[`interfere.exception.serialized`]=safeStringify(value.serialized),attrs):(attrs[`exception.type`]=value.name,attrs[`exception.message`]=value.message,attrs[`interfere.exception.kind`]=`error`,value.stack&&(attrs[`exception.stacktrace`]=value.stack),attrs[`interfere.exception.chain`]=safeStringify(toExceptions(value,opts.mechanism)),opts.appendFrames&&opts.appendFrames.length>0&&(attrs[`interfere.exception.appended_frames`]=safeStringify(opts.appendFrames)),opts.fallbackFrames&&opts.fallbackFrames.length>0&&(attrs[`interfere.exception.fallback_frames`]=safeStringify(opts.fallbackFrames)),attrs)}var Kernel=class{consent;identity;device;session;tracker;seen=new WeakSet;initialNavigationSeen=new Set;runtime=null;otel=null;otelDispose=null;unsubscribePageHidden=null;constructor(tracker){this.tracker=tracker,this.consent={get:()=>this.requireRuntime().getConsent(),set:value=>{let runtime=this.requireRuntime();value?runtime.setConsent(value):runtime.resetConsent()}},this.identity={clear:()=>this.tracker.reset(),get:()=>this.tracker.getIdentity(),set:params=>this.tracker.identify(params)},this.device={getDeviceId:()=>this.tracker.getDeviceId(),getFpHash:()=>this.tracker.getFpHash()},this.session={getId:()=>this.tracker.sessionId(),getWindowId:()=>this.tracker.windowId()},this.unsubscribePageHidden=onPageHidden(()=>{this.flush().catch(()=>void 0)})}attachRuntime(runtime){this.runtime=runtime}requireRuntime(){if(!this.runtime)throw Error(`Kernel runtime not attached. createKernel must call attachRuntime before any consent/dispose path runs.`);return this.runtime}getSessionId(){return this.tracker.sessionId()??``}recordException(value,opts){if(!isNonErrorException(value)){if(this.seen.has(value))return;this.seen.add(value)}if(!isNonErrorException(value)){let stacks=[],current=value;for(let depth=0;current&&depth<5;depth+=1)current.stack&&stacks.push(current.stack),current=current.cause instanceof Error?current.cause:void 0;if(shouldDropBrowserExtensionNoise(stacks)||shouldDropUnresolvableStack(stacks))return}let attrs=buildExceptionEventAttrs(value,opts),active=trace.getActiveSpan();if(active?.isRecording()){active.addEvent(`exception`,attrs);return}let span=this.otel?.tracerProvider.getTracer(`@interfere/react/exception`).startSpan(`interfere.exception.capture`);span&&(span.addEvent(`exception`,attrs),span.end())}recordLog(input){this.otel&&this.otel.loggerProvider.getLogger(`@interfere/react`).emit({severityText:input.severityText,severityNumber:input.severityNumber,body:input.body,...input.attributes?{attributes:input.attributes}:{}})}recordInitialNavigation(loggerProvider){recordInitialNavigation({getSessionId:()=>this.session.getId(),getWindowId:()=>this.session.getWindowId(),loggerProvider,seen:this.initialNavigationSeen})}async flush(){this.otel&&await this.otel.flush()}attachOtel(handle,dispose){this.otel=handle,this.otelDispose=dispose}syncConsent(value){let runtime=this.requireRuntime();if(value){runtime.setConsent(value);return}runtime.resetConsent()}async dispose(){this.unsubscribePageHidden?.(),this.unsubscribePageHidden=null,this.runtime&&await this.runtime.dispose(),this.tracker.dispose(),this.otelDispose?.(),this.otelDispose=null,this.otel&&=(await this.otel.shutdown(),null)}};async function createKernel(input={}){let{opts={}}=input,fetcher=opts.fetch??globalThis.fetch.bind(globalThis),targets=resolveTargets();log.info(`collector: %s`,targets.collectorBaseUrl),getGlobal(`__INTERFERE_FORCE_ENABLE__`)&&log.warn(`FORCE_ENABLE is active. Events will be dropped by production collectors. This should only be set during local development.`);let tracker=new SessionTracker({target:targets.session,fetcher});opts._wrapperVersions?.length&&(globalThis.__INTERFERE_SDK_STACK__=opts._wrapperVersions);let kernel=new Kernel(tracker),runtime=new PluginRuntime(kernel,opts.plugins,opts.consent);if(kernel.attachRuntime(runtime),tracker.start(),runtime.start(),opts.tracing!==!1){let tracingOpts=typeof opts.tracing==`object`?opts.tracing:void 0;await wireOtel(kernel,targets,opts._wrapperVersions,{...tracingOpts??{},...opts.serviceName?{serviceName:opts.serviceName}:{},...opts._internalAdditionalSpanProcessors?{additionalSpanProcessors:opts._internalAdditionalSpanProcessors}:{},...opts._internalAdditionalLogRecordProcessors?{additionalLogRecordProcessors:opts._internalAdditionalLogRecordProcessors}:{},...opts._internalAdditionalMetricReaders?{additionalMetricReaders:opts._internalAdditionalMetricReaders}:{}})}return await fetchRemoteConfig(targets.config,fetcher,runtime),registerServiceWorker({onMessage:msg=>recordSwMessage(kernel,msg)}).catch(()=>void 0),kernel}function recordSwMessage(kernel,msg){kernel.recordLog({severityText:`info`,severityNumber:9,body:msg.type,attributes:{"url.full":msg.url,...msg.reason?{"interfere.sw.reason":msg.reason}:{}}})}async function wireOtel(kernel,targets,wrapperVersions,tracing){let{buildOtelProvider,captureWebVitals,readPropagationFromDocument,registerBundledInstrumentations}=await import(`./otel/index.mjs`),collectorUrl=targets.collectorBaseUrl.replace(TRAILING_SLASH_RE,``),releaseSlug=readReleaseSlugFromGlobals(),handle=buildOtelProvider({collectorUrl,authHeaders:targets.ingest.headers,sdkStack:buildSdkStack(wrapperVersions),getSessionId:()=>kernel.session.getId(),deviceId:kernel.device.getDeviceId(),releaseSlug,...tracing.serviceName?{serviceName:tracing.serviceName}:{},...tracing.additionalSpanProcessors?{additionalSpanProcessors:tracing.additionalSpanProcessors}:{},...tracing.additionalLogRecordProcessors?{additionalLogRecordProcessors:tracing.additionalLogRecordProcessors}:{},...tracing.additionalMetricReaders?{additionalMetricReaders:tracing.additionalMetricReaders}:{}}),extracted=readPropagationFromDocument();extracted&&handle.contextManager.setPageScope(extracted);let dispose=registerBundledInstrumentations({tracerProvider:handle.tracerProvider,loggerProvider:handle.loggerProvider,ignoreUrls:[`${collectorUrl}/v2/sink`,targets.config.url,targets.session.url],...tracing.propagateContextUrls?{propagateContextUrls:tracing.propagateContextUrls}:{},...tracing.resolveRoute?{resolveRoute:tracing.resolveRoute}:{},...tracing.captureFailedRequests===void 0?{}:{captureFailedRequests:tracing.captureFailedRequests}});tracing.webVitals!==!1&&captureWebVitals({meter:handle.meterProvider.getMeter(`@interfere/react/web-vitals`),flush:()=>handle.flush(),...tracing.resolveRoute?{resolveRoute:tracing.resolveRoute}:{}}),kernel.attachOtel(handle,dispose),kernel.recordInitialNavigation(handle.loggerProvider)}async function fetchRemoteConfig(configTarget,fetcher,runtime){try{let headers=Object.fromEntries(configTarget.headers.entries()),res=await fetcher(configTarget.url,{method:`GET`,headers,signal:AbortSignal.timeout(1e4)});if(!res.ok)return;let config=await res.json();config?.plugins&&(runtime.applyRemoteConfig(config.plugins),log.debug(`applied remote config`))}catch{log.warn(`remote config fetch failed, using local defaults`)}}function isEnabledByEnvironment(){if(typeof process>`u`||!process.env)return!0;let nodeEnv=process.env.NODE_ENV;return nodeEnv===`production`||nodeEnv===void 0?!0:!!(getGlobal(`__INTERFERE_FORCE_ENABLE__`)||process.env.INTERFERE_FORCE_ENABLE)}function readReleaseSlugFromGlobals(){let value=getGlobal(`__INTERFERE_RELEASE_SLUG__`);return typeof value==`string`?releaseSlugSchema.parse(value):null}export{Kernel,buildSdkStack,createKernel,isEnabledByEnvironment,readReleaseSlugFromGlobals};
@@ -1,6 +1,6 @@
1
1
  import { createBeaconMetricExporter, createBeaconTraceExporter } from "./exporter.mjs";
2
- import { InstrumentationsInput, registerBundledInstrumentations } from "./instrumentations.mjs";
2
+ import { CaptureFailedRequestsOption, CaptureFailedRequestsOptions, HttpStatusCodeRange, InstrumentationsInput, registerBundledInstrumentations } from "./instrumentations.mjs";
3
3
  import { readPropagationFromDocument } from "./propagation.mjs";
4
4
  import { OtelProviderHandle, OtelProviderInput, buildOtelProvider } from "./provider.mjs";
5
5
  import { captureWebVitals } from "./web-vitals.mjs";
6
- export { type InstrumentationsInput, type OtelProviderHandle, type OtelProviderInput, buildOtelProvider, captureWebVitals, createBeaconMetricExporter, createBeaconTraceExporter, readPropagationFromDocument, registerBundledInstrumentations };
6
+ export { type CaptureFailedRequestsOption, type CaptureFailedRequestsOptions, type HttpStatusCodeRange, type InstrumentationsInput, type OtelProviderHandle, type OtelProviderInput, buildOtelProvider, captureWebVitals, createBeaconMetricExporter, createBeaconTraceExporter, readPropagationFromDocument, registerBundledInstrumentations };
@@ -0,0 +1,13 @@
1
+ import { LoggerProvider } from "@opentelemetry/sdk-logs";
2
+
3
+ //#region src/internal/otel/initial-navigation.d.ts
4
+ interface InitialNavigationInput {
5
+ getLocationHref?(): string | undefined;
6
+ getSessionId(): string | null;
7
+ getWindowId(): string | null;
8
+ loggerProvider: LoggerProvider;
9
+ seen: Set<string>;
10
+ }
11
+ declare function recordInitialNavigation(input: InitialNavigationInput): void;
12
+ //#endregion
13
+ export { InitialNavigationInput, recordInitialNavigation };
@@ -0,0 +1 @@
1
+ function recordInitialNavigation(input){let url=input.getLocationHref?input.getLocationHref():globalThis.location?.href;if(!url)return;let sessionId=input.getSessionId();if(!sessionId)return;let key=`${sessionId}:${input.getWindowId()??``}:${url}`;input.seen.has(key)||(input.seen.add(key),input.loggerProvider.getLogger(`@opentelemetry/instrumentation-browser-navigation`).emit({severityNumber:0,attributes:{"browser.navigation.type":`pageload`,"url.full":url}}))}export{recordInitialNavigation};
@@ -1,7 +1,49 @@
1
+ import { Span } from "@opentelemetry/api";
2
+ import { LoggerProvider } from "@opentelemetry/sdk-logs";
1
3
  import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
2
4
 
3
5
  //#region src/internal/otel/instrumentations.d.ts
6
+ interface FetchErrorShape {
7
+ message: string;
8
+ status?: number;
9
+ }
10
+ type FetchSpanRequest = Request | RequestInfo | RequestInit | URL;
11
+ /**
12
+ * Inclusive HTTP status range. `[400, 499]` matches every 4xx;
13
+ * a bare number matches that exact code.
14
+ */
15
+ type HttpStatusCodeRange = number | readonly [number, number];
16
+ interface CaptureFailedRequestsOptions {
17
+ /** Defaults to `[[400, 599]]` (4xx + 5xx) when not specified. */
18
+ statusCodes?: readonly HttpStatusCodeRange[];
19
+ }
20
+ /**
21
+ * Promote tracked failed fetch/XHR responses to captured exception events.
22
+ * On by default (same-origin 4xx + 5xx, deduped + rate-limited); `false`
23
+ * disables, `{ statusCodes }` tunes the matched ranges.
24
+ */
25
+ type CaptureFailedRequestsOption = boolean | CaptureFailedRequestsOptions;
26
+ declare function resolveFailureRanges(opt: CaptureFailedRequestsOption | undefined): readonly HttpStatusCodeRange[] | null;
27
+ /**
28
+ * Per-instrumentation gate that keeps failed-request capture low-noise:
29
+ * dedupes by `method+status+path` (so a flapping endpoint is captured once)
30
+ * and rate-limits to {@link FAILURE_RATE_MAX} captures per
31
+ * {@link FAILURE_RATE_WINDOW_MS} as a flood backstop. State lives in the
32
+ * closure — one gate per kernel instance, no module-level mutable state.
33
+ */
34
+ declare function createFailureGate(): (key: string) => boolean;
35
+ type NetworkErrorKind = "abort" | "network" | "timeout";
36
+ declare function classifyNetworkError(message: string): NetworkErrorKind;
37
+ declare function applyFetchSpanAttrs(span: Span, request: FetchSpanRequest, result: Response | FetchErrorShape, failureRanges: readonly HttpStatusCodeRange[] | null, isTrackedUrl: (url: string) => boolean, resolveRoute: ((pathname: string) => string | undefined) | undefined, gate?: (key: string) => boolean): void;
38
+ declare function applyXhrSpanAttrs(span: Span, xhr: Pick<XMLHttpRequest, "responseURL" | "status">, failureRanges: readonly HttpStatusCodeRange[] | null, isTrackedUrl: (url: string) => boolean, gate?: (key: string) => boolean): void;
39
+ declare const _testOnlyApplyFetchSpanAttrs: typeof applyFetchSpanAttrs;
40
+ declare const _testOnlyApplyXhrSpanAttrs: typeof applyXhrSpanAttrs;
41
+ declare const _testOnlyResolveFailureRanges: typeof resolveFailureRanges;
42
+ declare const _testOnlyCreateFailureGate: typeof createFailureGate;
43
+ declare const _testOnlyClassifyNetworkError: typeof classifyNetworkError;
4
44
  interface InstrumentationsInput {
45
+ /** See {@link CaptureFailedRequestsOption}. On by default. */
46
+ captureFailedRequests?: CaptureFailedRequestsOption;
5
47
  /**
6
48
  * URLs the SDK exempts from `fetch` and `XHR` instrumentation —
7
49
  * typically our own ingest/OTLP endpoints, so the SDK doesn't trace
@@ -9,6 +51,7 @@ interface InstrumentationsInput {
9
51
  * `THIRD_PARTY_IGNORE` defaults and the customer's `ignoreUrls`.
10
52
  */
11
53
  ignoreUrls: (string | RegExp)[];
54
+ loggerProvider?: LoggerProvider;
12
55
  /**
13
56
  * Customer-supplied URL patterns the fetch + XHR instrumentations
14
57
  * inject `traceparent` + `baggage` headers on. Same-origin requests
@@ -39,4 +82,4 @@ interface InstrumentationsInput {
39
82
  */
40
83
  declare function registerBundledInstrumentations(input: InstrumentationsInput): () => void;
41
84
  //#endregion
42
- export { InstrumentationsInput, registerBundledInstrumentations };
85
+ export { CaptureFailedRequestsOption, CaptureFailedRequestsOptions, HttpStatusCodeRange, InstrumentationsInput, _testOnlyApplyFetchSpanAttrs, _testOnlyApplyXhrSpanAttrs, _testOnlyClassifyNetworkError, _testOnlyCreateFailureGate, _testOnlyResolveFailureRanges, registerBundledInstrumentations };
@@ -1 +1 @@
1
- import{describeActionable,isActionable}from"../dom/actionable.mjs";import{registerInstrumentations}from"@opentelemetry/instrumentation";import{BrowserNavigationInstrumentation,defaultSanitizeUrl}from"@opentelemetry/instrumentation-browser-navigation";import{DocumentLoadInstrumentation}from"@opentelemetry/instrumentation-document-load";import{FetchInstrumentation}from"@opentelemetry/instrumentation-fetch";import{LongTaskInstrumentation}from"@opentelemetry/instrumentation-long-task";import{UserInteractionInstrumentation}from"@opentelemetry/instrumentation-user-interaction";import{XMLHttpRequestInstrumentation}from"@opentelemetry/instrumentation-xml-http-request";const ATTR_HTTP_ROUTE=`http.route`,ATTR_URL_FULL=`url.full`,ATTR_URL_PATH=`url.path`,SEMCONV_STABILITY_OPT_IN=`http,database,messaging,gen_ai_latest_experimental`,THIRD_PARTY_IGNORE=[/(?:^|\/\/)clerk\./,/\.clerk\./,/\.sentry\./,/\.intercom\./,/\.posthog\./,/\.googletagmanager\.com/,/\.google-analytics\.com/,/\.googleapis\.com/,/\.doubleclick\.net/,/\.facebook\.com/,/\.fbcdn\.net/,/\.analytics\.google\.com/,/px\.ads\.linkedin\.com/,/[?&]_rsc=/],NEVER_MATCH=/^$/,ESCAPE_RE=/[.*+?^${}()|[\]\\]/g;function sameOriginPattern(){if(typeof window>`u`)return NEVER_MATCH;let{protocol,host}=window.location;return RegExp(`^${protocol}//${host.replace(ESCAPE_RE,`\\$&`)}`)}function stampInteractionAttrs(eventType,element,span){let desc=describeActionable(element);span.setAttribute(`ui.event_type`,eventType),span.setAttribute(`ui.target.tag`,desc.tag),desc.id&&span.setAttribute(`ui.target.id`,desc.id),desc.name&&span.setAttribute(`ui.target.name`,desc.name),desc.role&&span.setAttribute(`ui.target.role`,desc.role),desc.ariaLabel&&span.setAttribute(`ui.target.aria_label`,desc.ariaLabel),desc.text&&span.setAttribute(`ui.target.text`,desc.text)}function stampUrlAttrs(span,resolveRoute){if(typeof window>`u`)return;let pathname=window.location.pathname,route=resolveRoute?.(pathname);span.setAttribute(ATTR_URL_PATH,route??pathname),span.setAttribute(ATTR_URL_FULL,window.location.href),route&&span.setAttribute(ATTR_HTTP_ROUTE,route)}function registerBundledInstrumentations(input){let{tracerProvider,ignoreUrls,propagateContextUrls=[]}=input,resolveRoute=input.resolveRoute,origin=sameOriginPattern(),fetchIgnore=[...ignoreUrls,...THIRD_PARTY_IGNORE];return registerInstrumentations({tracerProvider,instrumentations:[new FetchInstrumentation({propagateTraceHeaderCorsUrls:[origin,...propagateContextUrls],ignoreUrls:fetchIgnore,semconvStabilityOptIn:SEMCONV_STABILITY_OPT_IN,measureRequestSize:!0,applyCustomAttributesOnSpan:(span,request)=>{let url=request instanceof Request?request.url:request?.toString();if(typeof url==`string`)try{let parsed=new URL(url);if(span.setAttribute(ATTR_URL_PATH,parsed.pathname),origin.test(url)||propagateContextUrls.some(re=>re instanceof RegExp?re.test(url):url.includes(re))){let method=(request instanceof Request?request.method:void 0)??`GET`,route=resolveRoute?.(parsed.pathname);route&&span.setAttribute(ATTR_HTTP_ROUTE,route),span.updateName(`${method} ${route??parsed.pathname}`)}}catch{return}}}),new XMLHttpRequestInstrumentation({propagateTraceHeaderCorsUrls:[origin,...propagateContextUrls],ignoreUrls:fetchIgnore,semconvStabilityOptIn:SEMCONV_STABILITY_OPT_IN}),new DocumentLoadInstrumentation({semconvStabilityOptIn:SEMCONV_STABILITY_OPT_IN,applyCustomAttributesOnSpan:{resourceFetch:(span,entry)=>{let name=entry?.name;if(typeof name==`string`)try{let url=new URL(name),isCrossOrigin=typeof window<`u`&&url.origin!==window.location.origin;span.updateName(isCrossOrigin?`resource: ${url.host}${url.pathname}`:`resource: ${url.pathname}`),span.setAttribute(ATTR_URL_FULL,name),span.setAttribute(ATTR_URL_PATH,url.pathname),span.setAttribute(`server.address`,url.host)}catch{}}}}),new BrowserNavigationInstrumentation({sanitizeUrl:defaultSanitizeUrl}),new UserInteractionInstrumentation({eventNames:[`click`,`submit`],shouldPreventSpanCreation:(eventType,element,span)=>eventType===`click`&&!isActionable(element)?!0:(stampInteractionAttrs(eventType,element,span),stampUrlAttrs(span,resolveRoute),!1)}),new LongTaskInstrumentation({observerCallback:(span,info)=>{stampUrlAttrs(span,resolveRoute);let containerType=info.longtaskEntry.attribution[0]?.containerType;containerType&&span.setAttribute(`longtask.container_type`,containerType)}})]})}export{registerBundledInstrumentations};
1
+ import{THIRD_PARTY_IGNORE}from"../third-party-hosts.mjs";import{describeActionable,isActionable}from"../dom/actionable.mjs";import{MECHANISM_TYPE}from"@interfere/types/sdk/errors";import{registerInstrumentations}from"@opentelemetry/instrumentation";import{BrowserNavigationInstrumentation,defaultSanitizeUrl}from"@opentelemetry/instrumentation-browser-navigation";import{DocumentLoadInstrumentation}from"@opentelemetry/instrumentation-document-load";import{FetchInstrumentation}from"@opentelemetry/instrumentation-fetch";import{LongTaskInstrumentation}from"@opentelemetry/instrumentation-long-task";import{UserInteractionInstrumentation}from"@opentelemetry/instrumentation-user-interaction";import{XMLHttpRequestInstrumentation}from"@opentelemetry/instrumentation-xml-http-request";const ATTR_HTTP_ROUTE=`http.route`,ATTR_URL_FULL=`url.full`,ATTR_URL_PATH=`url.path`,DEFAULT_FAILURE_RANGES=[[400,599]];function resolveFailureRanges(opt){if(opt===!1)return null;if(opt===void 0||opt===!0)return DEFAULT_FAILURE_RANGES;let codes=opt.statusCodes;return codes&&codes.length>0?codes:DEFAULT_FAILURE_RANGES}function createFailureGate(){let seen=new Set,windowStart=0,windowCount=0;return key=>{if(seen.has(key))return!1;let now=Date.now();return now-windowStart>6e4&&(windowStart=now,windowCount=0),windowCount>=20?!1:(seen.size>=100&&seen.clear(),seen.add(key),windowCount+=1,!0)}}function failureKey(method,discriminator,url){let path;try{path=new URL(url).pathname}catch{path=url}return`${method} ${discriminator} ${path}`}function statusMatches(status,ranges){for(let range of ranges)if(typeof range==`number`){if(status===range)return!0}else if(status>=range[0]&&status<=range[1])return!0;return!1}function recordHttpFailure(span,status,method,url,mechanism){span.addEvent(`exception`,{"exception.type":`HttpResponseError`,"exception.message":`HTTP ${status} ${method} ${url}`,"interfere.exception.mechanism":mechanism,"interfere.exception.handled":!1,"interfere.exception.kind":`error`})}function maybeCaptureFetchFailure(span,result,failureRanges,method,url,gate){if(!failureRanges)return;let status=result?.status;typeof status!=`number`||status<=0||!statusMatches(status,failureRanges)||gate&&!gate(failureKey(method,status,url))||recordHttpFailure(span,status,method,url,MECHANISM_TYPE.browser.fetchFailedResponse)}const NETWORK_TIMEOUT_RE=/timeout|timed out/i,NETWORK_ABORT_RE=/abort/i;function classifyNetworkError(message){return NETWORK_TIMEOUT_RE.test(message)?`timeout`:NETWORK_ABORT_RE.test(message)?`abort`:`network`}function recordNetworkFailure(span,kind,method,url,mechanism){span.addEvent(`exception`,{"exception.type":`NetworkError`,"exception.message":`Network ${kind} ${method} ${url}`,"interfere.exception.mechanism":mechanism,"interfere.exception.handled":!1,"interfere.exception.kind":`error`,"interfere.exception.subtype":kind})}function maybeCaptureNetworkFailure(span,result,failureRanges,method,url,gate){if(!failureRanges)return;let message=result.message;if(typeof message!=`string`||message.length===0)return;let kind=classifyNetworkError(message);gate&&!gate(failureKey(method,kind,url))||recordNetworkFailure(span,kind,method,url,MECHANISM_TYPE.browser.fetchNetworkError)}function applyFetchSpanAttrs(span,request,result,failureRanges,isTrackedUrl,resolveRoute,gate){let url=resolveFetchUrl(span,request,result);if(!url)return;let parsed;try{parsed=new URL(url)}catch{return}span.setAttribute(ATTR_URL_PATH,parsed.pathname);let tracked=isTrackedUrl(url),method=resolveFetchMethod(request);if(tracked){let route=resolveRoute?.(parsed.pathname);route&&span.setAttribute(ATTR_HTTP_ROUTE,route),span.updateName(`${method} ${route??parsed.pathname}`),result instanceof Response?maybeCaptureFetchFailure(span,result,failureRanges,method,url,gate):maybeCaptureNetworkFailure(span,result,failureRanges,method,url,gate)}}function applyXhrSpanAttrs(span,xhr,failureRanges,isTrackedUrl,gate){if(!failureRanges)return;let url=xhr.responseURL;if(!(url&&isTrackedUrl(url)))return;let status=xhr.status;if(typeof status==`number`){if(status===0){(!gate||gate(failureKey(`XHR`,`network`,url)))&&recordNetworkFailure(span,`network`,`XHR`,url,MECHANISM_TYPE.browser.xhrNetworkError);return}statusMatches(status,failureRanges)&&(gate&&!gate(failureKey(`XHR`,status,url))||recordHttpFailure(span,status,`XHR`,url,MECHANISM_TYPE.browser.xhrFailedResponse))}}function resolveFetchUrl(span,request,result){return responseUrl(result)??requestUrl(request)??spanUrlFull(span)}function isReadableSpan(span){return`attributes`in span}function spanUrlFull(span){if(!isReadableSpan(span))return null;let url=span.attributes[ATTR_URL_FULL]??span.attributes[`http.url`];return typeof url==`string`?url:null}function responseUrl(result){return result instanceof Response&&result.url?result.url:null}function requestUrl(request){return typeof request==`string`?request:readStringProp(request,`url`)??readStringProp(request,`href`)}function resolveFetchMethod(request){return readStringProp(request,`method`)??`GET`}function readStringProp(value,key){if(!(value&&typeof value==`object`&&key in value))return null;let prop=value[key];return typeof prop==`string`?prop:null}const _testOnlyApplyFetchSpanAttrs=applyFetchSpanAttrs,_testOnlyApplyXhrSpanAttrs=applyXhrSpanAttrs,_testOnlyResolveFailureRanges=resolveFailureRanges,_testOnlyCreateFailureGate=createFailureGate,_testOnlyClassifyNetworkError=classifyNetworkError,SEMCONV_STABILITY_OPT_IN=`http,database,messaging,gen_ai_latest_experimental`,NEVER_MATCH=/^$/,ESCAPE_RE=/[.*+?^${}()|[\]\\]/g;function sameOriginPattern(){if(typeof window>`u`)return NEVER_MATCH;let{protocol,host}=window.location;return RegExp(`^${protocol}//${host.replace(ESCAPE_RE,`\\$&`)}`)}function stampInteractionAttrs(eventType,element,span){let desc=describeActionable(element);span.setAttribute(`ui.event_type`,eventType),span.setAttribute(`ui.target.tag`,desc.tag),desc.id&&span.setAttribute(`ui.target.id`,desc.id),desc.name&&span.setAttribute(`ui.target.name`,desc.name),desc.role&&span.setAttribute(`ui.target.role`,desc.role),desc.ariaLabel&&span.setAttribute(`ui.target.aria_label`,desc.ariaLabel),desc.text&&span.setAttribute(`ui.target.text`,desc.text)}function stampUrlAttrs(span,resolveRoute){if(typeof window>`u`)return;let pathname=window.location.pathname,route=resolveRoute?.(pathname);span.setAttribute(ATTR_URL_PATH,route??pathname),span.setAttribute(ATTR_URL_FULL,window.location.href),route&&span.setAttribute(ATTR_HTTP_ROUTE,route)}function registerBundledInstrumentations(input){let{tracerProvider,loggerProvider,ignoreUrls,propagateContextUrls=[]}=input,resolveRoute=input.resolveRoute,origin=sameOriginPattern(),fetchIgnore=[...ignoreUrls,...THIRD_PARTY_IGNORE],failureRanges=resolveFailureRanges(input.captureFailedRequests),failureGate=createFailureGate();function isTrackedUrl(url){return origin.test(url)||propagateContextUrls.some(re=>re instanceof RegExp?re.test(url):url.includes(re))}return registerInstrumentations({tracerProvider,...loggerProvider?{loggerProvider}:{},instrumentations:[new FetchInstrumentation({propagateTraceHeaderCorsUrls:[origin,...propagateContextUrls],ignoreUrls:fetchIgnore,semconvStabilityOptIn:SEMCONV_STABILITY_OPT_IN,measureRequestSize:!0,applyCustomAttributesOnSpan:(span,request,result)=>{applyFetchSpanAttrs(span,request,result,failureRanges,isTrackedUrl,resolveRoute,failureGate)}}),new XMLHttpRequestInstrumentation({propagateTraceHeaderCorsUrls:[origin,...propagateContextUrls],ignoreUrls:fetchIgnore,semconvStabilityOptIn:SEMCONV_STABILITY_OPT_IN,applyCustomAttributesOnSpan:(span,xhr)=>{applyXhrSpanAttrs(span,xhr,failureRanges,isTrackedUrl,failureGate)}}),new DocumentLoadInstrumentation({semconvStabilityOptIn:SEMCONV_STABILITY_OPT_IN,applyCustomAttributesOnSpan:{resourceFetch:(span,entry)=>{let name=entry?.name;if(typeof name==`string`)try{let url=new URL(name),isCrossOrigin=typeof window<`u`&&url.origin!==window.location.origin;span.updateName(isCrossOrigin?`resource: ${url.host}${url.pathname}`:`resource: ${url.pathname}`),span.setAttribute(ATTR_URL_FULL,name),span.setAttribute(ATTR_URL_PATH,url.pathname),span.setAttribute(`server.address`,url.host)}catch{return}}}}),new BrowserNavigationInstrumentation({sanitizeUrl:defaultSanitizeUrl}),new UserInteractionInstrumentation({eventNames:[`click`,`submit`],shouldPreventSpanCreation:(eventType,element,span)=>eventType===`click`&&!isActionable(element)?!0:(stampInteractionAttrs(eventType,element,span),stampUrlAttrs(span,resolveRoute),!1)}),new LongTaskInstrumentation({observerCallback:(span,info)=>{stampUrlAttrs(span,resolveRoute);let containerType=info.longtaskEntry.attribution[0]?.containerType;containerType&&span.setAttribute(`longtask.container_type`,containerType)}})]})}export{_testOnlyApplyFetchSpanAttrs,_testOnlyApplyXhrSpanAttrs,_testOnlyClassifyNetworkError,_testOnlyCreateFailureGate,_testOnlyResolveFailureRanges,registerBundledInstrumentations};
@@ -1 +1 @@
1
- import{SDK_NAME,SDK_VERSION}from"../version.mjs";import{createBeaconLogExporter,createBeaconMetricExporter,createBeaconTraceExporter}from"./exporter.mjs";import{PageScopeContextManager}from"./page-scope-context-manager.mjs";import{propagation}from"@opentelemetry/api";import{CompositePropagator,W3CBaggagePropagator,W3CTraceContextPropagator}from"@opentelemetry/core";import{MeterProvider,PeriodicExportingMetricReader}from"@opentelemetry/sdk-metrics";import{normalizeEnv}from"@interfere/types/sdk/runtime";import{browserDetector}from"@opentelemetry/opentelemetry-browser-detector";import{detectResources,resourceFromAttributes}from"@opentelemetry/resources";import{BatchLogRecordProcessor,LoggerProvider}from"@opentelemetry/sdk-logs";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{WebTracerProvider}from"@opentelemetry/sdk-trace-web";import{createSessionLogRecordProcessor,createSessionSpanProcessor}from"@opentelemetry/web-common";const BROWSER_BATCH_OPTIONS={maxQueueSize:50,maxExportBatchSize:10,scheduledDelayMillis:2e3,exportTimeoutMillis:1e4};function buildOtelProvider(input){let environment=normalizeEnv(typeof process>`u`?void 0:process.env.NODE_ENV)??`unknown`,baseResource=resourceFromAttributes({"service.name":input.serviceName??`interfere-sdk`,"service.namespace":`interfere`,"deployment.environment.name":environment,"telemetry.sdk.language":`webjs`,"interfere.sdk.name":SDK_NAME,"interfere.sdk.version":SDK_VERSION,"interfere.sdk.stack":input.sdkStack.join(`, `),...input.releaseSlug?{"release.slug":input.releaseSlug}:{},...input.deviceId?{"device.id":input.deviceId}:{}}),detected=detectResources({detectors:[browserDetector]}),resource=baseResource.merge(detected),traceExporter=createBeaconTraceExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),getSessionId=input.getSessionId,tracerProvider=new WebTracerProvider({resource,spanProcessors:[createSessionSpanProcessor({getSessionId}),new BatchSpanProcessor(traceExporter,BROWSER_BATCH_OPTIONS),...input.additionalSpanProcessors??[]]}),contextManager=new PageScopeContextManager;tracerProvider.register({contextManager}),propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[new W3CTraceContextPropagator,new W3CBaggagePropagator]}));let metricReader=new PeriodicExportingMetricReader({exporter:createBeaconMetricExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),exportIntervalMillis:3e4}),meterProvider=new MeterProvider({resource,readers:[metricReader,...input.additionalMetricReaders??[]]}),logExporter=createBeaconLogExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),loggerProvider=new LoggerProvider({resource,processors:[createSessionLogRecordProcessor({getSessionId}),new BatchLogRecordProcessor(logExporter,BROWSER_BATCH_OPTIONS),...input.additionalLogRecordProcessors??[]]});return{contextManager,tracerProvider,meterProvider,metricReader,loggerProvider,flush:async()=>{await Promise.all([tracerProvider.forceFlush(),metricReader.forceFlush(),loggerProvider.forceFlush()])},shutdown:async()=>{await Promise.all([tracerProvider.shutdown(),meterProvider.shutdown(),loggerProvider.shutdown()])}}}export{buildOtelProvider};
1
+ import{getGlobal}from"../../util/global.mjs";import{SDK_NAME,SDK_VERSION}from"../version.mjs";import{createBeaconLogExporter,createBeaconMetricExporter,createBeaconTraceExporter}from"./exporter.mjs";import{PageScopeContextManager}from"./page-scope-context-manager.mjs";import{propagation}from"@opentelemetry/api";import{CompositePropagator,W3CBaggagePropagator,W3CTraceContextPropagator}from"@opentelemetry/core";import{MeterProvider,PeriodicExportingMetricReader}from"@opentelemetry/sdk-metrics";import{resolveEnvironment}from"@interfere/types/sdk/runtime";import{browserDetector}from"@opentelemetry/opentelemetry-browser-detector";import{detectResources,resourceFromAttributes}from"@opentelemetry/resources";import{BatchLogRecordProcessor,LoggerProvider}from"@opentelemetry/sdk-logs";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{WebTracerProvider}from"@opentelemetry/sdk-trace-web";import{createSessionLogRecordProcessor,createSessionSpanProcessor}from"@opentelemetry/web-common";const BROWSER_BATCH_OPTIONS={maxQueueSize:50,maxExportBatchSize:10,scheduledDelayMillis:2e3,exportTimeoutMillis:1e4};function buildOtelProvider(input){let environment=resolveEnvironment({explicit:getGlobal(`__INTERFERE_ENVIRONMENT__`),env:typeof process>`u`?{}:{INTERFERE_ENVIRONMENT:process.env.INTERFERE_ENVIRONMENT,VERCEL_ENV:process.env.VERCEL_ENV,NODE_ENV:process.env.NODE_ENV}}),baseResource=resourceFromAttributes({"service.name":input.serviceName??`interfere-sdk`,"service.namespace":`interfere`,"deployment.environment.name":environment,"telemetry.sdk.language":`webjs`,"interfere.sdk.name":SDK_NAME,"interfere.sdk.version":SDK_VERSION,"interfere.sdk.stack":input.sdkStack.join(`, `),...input.releaseSlug?{"release.slug":input.releaseSlug}:{},...input.deviceId?{"device.id":input.deviceId}:{}}),detected=detectResources({detectors:[browserDetector]}),resource=baseResource.merge(detected),traceExporter=createBeaconTraceExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),getSessionId=input.getSessionId,tracerProvider=new WebTracerProvider({resource,spanProcessors:[createSessionSpanProcessor({getSessionId}),new BatchSpanProcessor(traceExporter,BROWSER_BATCH_OPTIONS),...input.additionalSpanProcessors??[]]}),contextManager=new PageScopeContextManager;tracerProvider.register({contextManager}),propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[new W3CTraceContextPropagator,new W3CBaggagePropagator]}));let metricReader=new PeriodicExportingMetricReader({exporter:createBeaconMetricExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),exportIntervalMillis:3e4}),meterProvider=new MeterProvider({resource,readers:[metricReader,...input.additionalMetricReaders??[]]}),logExporter=createBeaconLogExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),loggerProvider=new LoggerProvider({resource,processors:[createSessionLogRecordProcessor({getSessionId}),new BatchLogRecordProcessor(logExporter,BROWSER_BATCH_OPTIONS),...input.additionalLogRecordProcessors??[]]});return{contextManager,tracerProvider,meterProvider,metricReader,loggerProvider,flush:async()=>{await Promise.all([tracerProvider.forceFlush(),metricReader.forceFlush(),loggerProvider.forceFlush()])},shutdown:async()=>{await Promise.all([tracerProvider.shutdown(),meterProvider.shutdown(),loggerProvider.shutdown()])}}}export{buildOtelProvider};
@@ -1,21 +1,8 @@
1
1
  //#region src/internal/page-lifecycle.d.ts
2
- /**
3
- * Single subscription point for `visibilitychange` (hidden) and
4
- * `beforeunload`. Modules call `onPageHidden(handler)` and the handler
5
- * fires once per transition into hidden / unload.
6
- *
7
- * Replaces the previous shape where the kernel, the queue, the pages
8
- * plugin, and the replay plugin each attached their own listeners — four
9
- * subscribers racing to flush / drain / emit on the same boundary, with
10
- * no shared lifecycle. One subscription, many handlers, no coordination
11
- * via the global event subsystem.
12
- *
13
- * Both events can fire (visibilitychange first when the user navigates
14
- * away, then sometimes beforeunload). Handlers must be idempotent.
15
- */
16
2
  type Handler = () => void;
17
3
  declare function onPageHidden(handler: Handler): () => void;
4
+ declare function onPageShown(handler: Handler): () => void;
18
5
  /** Test hook — drops every handler and unbinds the listeners. */
19
6
  declare function _resetPageLifecycleForTests(): void;
20
7
  //#endregion
21
- export { _resetPageLifecycleForTests, onPageHidden };
8
+ export { _resetPageLifecycleForTests, onPageHidden, onPageShown };
@@ -1 +1 @@
1
- const handlers=new Set;let installed=!1;function fire(){for(let handler of handlers)handler()}function onVisibilityChange(){typeof document<`u`&&document.visibilityState===`hidden`&&fire()}function install(){installed||typeof globalThis.addEventListener!=`function`||(installed=!0,globalThis.addEventListener(`visibilitychange`,onVisibilityChange),globalThis.addEventListener(`beforeunload`,fire))}function onPageHidden(handler){return install(),handlers.add(handler),()=>{handlers.delete(handler)}}function _resetPageLifecycleForTests(){handlers.clear(),installed&&typeof globalThis.removeEventListener==`function`&&(globalThis.removeEventListener(`visibilitychange`,onVisibilityChange),globalThis.removeEventListener(`beforeunload`,fire)),installed=!1}export{_resetPageLifecycleForTests,onPageHidden};
1
+ const hiddenHandlers=new Set,shownHandlers=new Set;let installed=!1;function fire(set){for(let handler of set)handler()}function fireHidden(){fire(hiddenHandlers)}function fireShown(){fire(shownHandlers)}function onVisibilityChange(){if(!(typeof document>`u`)){if(document.visibilityState===`hidden`){fireHidden();return}document.visibilityState===`visible`&&fireShown()}}function install(){installed||typeof globalThis.addEventListener!=`function`||(installed=!0,globalThis.addEventListener(`visibilitychange`,onVisibilityChange),globalThis.addEventListener(`beforeunload`,fireHidden))}function onPageHidden(handler){return install(),hiddenHandlers.add(handler),()=>{hiddenHandlers.delete(handler)}}function onPageShown(handler){return install(),shownHandlers.add(handler),()=>{shownHandlers.delete(handler)}}function _resetPageLifecycleForTests(){hiddenHandlers.clear(),shownHandlers.clear(),installed&&typeof globalThis.removeEventListener==`function`&&(globalThis.removeEventListener(`visibilitychange`,onVisibilityChange),globalThis.removeEventListener(`beforeunload`,fireHidden)),installed=!1}export{_resetPageLifecycleForTests,onPageHidden,onPageShown};
@@ -13,6 +13,7 @@ interface InterfereContextValue {
13
13
  getFpHash(): string | null;
14
14
  };
15
15
  identity: {
16
+ clear(): void;
16
17
  get(): IdentifyParams | null;
17
18
  set(params: IdentifyParams): Promise<void>;
18
19
  };
@@ -1 +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};
1
+ "use client";import{createContext}from"react";const NULL_CONTEXT_VALUE={consent:{get:()=>null,set:()=>void 0},device:{getDeviceId:()=>null,getFpHash:()=>null},identity:{clear:()=>void 0,get:()=>null,set:()=>Promise.resolve()},kernel:null,session:{getId:()=>null,getWindowId:()=>null}},InterfereContext=createContext(null);export{InterfereContext,NULL_CONTEXT_VALUE};
@@ -0,0 +1,9 @@
1
+ //#region src/internal/server-digest.d.ts
2
+ /**
3
+ * True when a thrown value carries a Next.js server `digest` — it originated on
4
+ * the server and was already captured there (`onRequestError`). Client capture
5
+ * sites skip these so one server error isn't recorded again on the client.
6
+ */
7
+ declare function hasServerDigest(value: unknown): boolean;
8
+ //#endregion
9
+ export { hasServerDigest };
@@ -0,0 +1 @@
1
+ function hasServerDigest(value){return typeof value==`object`&&!!value&&typeof value.digest==`string`}export{hasServerDigest};
@@ -0,0 +1,14 @@
1
+ //#region src/internal/third-party-hosts.d.ts
2
+ /**
3
+ * Hosts/paths whose client-side fetch / XHR / resource loads we never
4
+ * surface: common analytics / ads / auth / replay vendors that otherwise
5
+ * turn into orphan-span and resource-error noise. Conservative and
6
+ * customer-generic — app-specific noise belongs in the customer's own
7
+ * `ignoreUrls` config, not hardcoded into a public SDK.
8
+ *
9
+ * Dependency-free on purpose so the error-only bundle can reuse it without
10
+ * pulling in the OTel SDK that `internal/otel/*` carries.
11
+ */
12
+ declare const THIRD_PARTY_IGNORE: readonly RegExp[];
13
+ //#endregion
14
+ export { THIRD_PARTY_IGNORE };
@@ -0,0 +1 @@
1
+ const THIRD_PARTY_IGNORE=[/(?:^|\/\/)clerk\./,/\.clerk\./,/\.sentry\./,/\.intercom\./,/\.posthog\./,/\.googletagmanager\.com/,/\.google-analytics\.com/,/\.googleapis\.com/,/\.doubleclick\.net/,/\.facebook\.com/,/\.fbcdn\.net/,/\.analytics\.google\.com/,/px\.ads\.linkedin\.com/,/[?&]_rsc=/];export{THIRD_PARTY_IGNORE};
@@ -12,6 +12,7 @@ interface WrapperSingleton {
12
12
  getKernel(): Kernel;
13
13
  getKernelOrNull(): Kernel | null;
14
14
  identity: {
15
+ clear(): void;
15
16
  get(): IdentifyParams | null;
16
17
  set(params: IdentifyParams): Promise<void>;
17
18
  };
@@ -1 +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};
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:{clear:()=>kernel?.identity.clear(),get:()=>kernel?.identity.get()??null,set:params=>kernel?.identity.set(params)??Promise.resolve()}}}export{createWrapperSingleton};
package/dist/package.mjs CHANGED
@@ -1 +1 @@
1
- var name=`@interfere/react`,version=`10.0.1-canary.1`;export{name,version};
1
+ var name=`@interfere/react`,version=`11.0.0-canary.3`;export{name,version};
@@ -3,4 +3,4 @@ import { Plugin } from "./lib/types.mjs";
3
3
  //#region src/plugins/errors.d.ts
4
4
  declare const errorsPlugin: Plugin;
5
5
  //#endregion
6
- export { errorsPlugin as default, errorsPlugin };
6
+ export { errorsPlugin as default };
@@ -1 +1 @@
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
+ import{hasServerDigest}from"../internal/server-digest.mjs";import{onConsoleCall}from"../internal/console-patch.mjs";import{THIRD_PARTY_IGNORE}from"../internal/third-party-hosts.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){return args.find(arg=>arg instanceof Error)??null}function resourceError(target){return target instanceof HTMLImageElement||target instanceof HTMLScriptElement?target.src?{tag:target.localName,url:target.src}:null:target instanceof HTMLLinkElement&&target.href?{tag:target.localName,url:target.href}:null}function isIgnoredResourceUrl(url){return THIRD_PARTY_IGNORE.some(pattern=>pattern.test(url))}function createResourceGate(){let seen=new Set,windowStart=0,windowCount=0;return url=>{if(seen.has(url))return!1;let now=Date.now();return now-windowStart>6e4&&(windowStart=now,windowCount=0),windowCount>=20?!1:(seen.size>=100&&seen.clear(),seen.add(url),windowCount+=1,!0)}}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&&!hasServerDigest(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=>{hasServerDigest(event.reason)||capture(ctx,{error:toException(event.reason),mechanism:{type:MECHANISM_TYPE.browser.onunhandledrejection,handled:!1,...event.reason instanceof Error?{}:{synthetic:!0}}})},seenCspReports=new Set,onSecurityPolicyViolation=event=>{let key=`${event.violatedDirective}|${event.blockedURI}`;seenCspReports.has(key)||seenCspReports.size>=5||(seenCspReports.add(key),capture(ctx,{error:{kind:`non-error`,type:`SecurityPolicyViolation`,value:`CSP ${event.violatedDirective} blocked ${event.blockedURI||`inline`}`,serialized:{violatedDirective:event.violatedDirective,blockedURI:event.blockedURI,sourceFile:event.sourceFile,lineNumber:event.lineNumber,columnNumber:event.columnNumber,disposition:event.disposition}},mechanism:{type:MECHANISM_TYPE.browser.securityPolicyViolation,handled:!1}}))},resourceGate=createResourceGate(),onResourceError=event=>{let resource=resourceError(event.target);!resource||isIgnoredResourceUrl(resource.url)||!resourceGate(resource.url)||capture(ctx,{error:{kind:`non-error`,type:`ResourceLoadError`,value:`Failed to load ${resource.tag}: ${resource.url}`,serialized:{url:resource.url}},mechanism:{type:MECHANISM_TYPE.browser.resourceError,handled:!1}})},supportsEventTarget=typeof globalThis.addEventListener==`function`&&typeof globalThis.removeEventListener==`function`;supportsEventTarget&&(globalThis.addEventListener(`unhandledrejection`,onUnhandledRejection),globalThis.addEventListener(`securitypolicyviolation`,onSecurityPolicyViolation),globalThis.addEventListener(`error`,onResourceError,!0));let unsubscribeConsole=onConsoleCall((level,args)=>{if(level!==`error`)return;let error=findErrorArg(args);error&&!hasServerDigest(error)&&capture(ctx,{error,mechanism:{type:MECHANISM_TYPE.browser.consoleError,handled:!0}})});return()=>{Error.stackTraceLimit=originalStackTraceLimit,globalThis.onerror=originalOnError,supportsEventTarget&&(globalThis.removeEventListener(`unhandledrejection`,onUnhandledRejection),globalThis.removeEventListener(`securitypolicyviolation`,onSecurityPolicyViolation),globalThis.removeEventListener(`error`,onResourceError,!0)),unsubscribeConsole()}}};export{errorsPlugin as default};
@@ -1 +1 @@
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
+ 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/index.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};
@@ -10,4 +10,4 @@ import { Plugin } from "./lib/types.mjs";
10
10
  */
11
11
  declare const logsPlugin: Plugin;
12
12
  //#endregion
13
- export { logsPlugin as default, logsPlugin };
13
+ export { logsPlugin as default };
@@ -1 +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};
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};
@@ -3,4 +3,4 @@ import { Plugin } from "./lib/types.mjs";
3
3
  //#region src/plugins/rage-clicks.d.ts
4
4
  declare const rageClicksPlugin: Plugin;
5
5
  //#endregion
6
- export { rageClicksPlugin as default, rageClicksPlugin };
6
+ export { rageClicksPlugin as default };
@@ -1 +1 @@
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
+ 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};
@@ -0,0 +1,13 @@
1
+ import { BufferedReplayEvent } from "./chunking.mjs";
2
+
3
+ //#region src/plugins/replay/buffer.d.ts
4
+ interface ReplayBuffer {
5
+ drain(): BufferedReplayEvent[];
6
+ push(event: unknown): void;
7
+ }
8
+ declare function createReplayBuffer(opts: {
9
+ onSoftLimit: () => void;
10
+ softLimitBytes: number;
11
+ }): ReplayBuffer;
12
+ //#endregion
13
+ export { ReplayBuffer, createReplayBuffer };
@@ -0,0 +1 @@
1
+ import{utf8ByteLength}from"./chunking.mjs";function createReplayBuffer(opts){let entries=[],totalBytes=0;return{push(event){let json=JSON.stringify(event),bytes=utf8ByteLength(json);entries.push({json,ts:Date.now(),bytes}),totalBytes+=bytes,totalBytes>=opts.softLimitBytes&&opts.onSoftLimit()},drain(){let drained=entries;return entries=[],totalBytes=0,drained}}}export{createReplayBuffer};
@@ -0,0 +1,21 @@
1
+ //#region src/plugins/replay/chunking.d.ts
2
+ interface BufferedReplayEvent {
3
+ bytes: number;
4
+ json: string;
5
+ ts: number;
6
+ }
7
+ interface ReplayChunkPayload {
8
+ bodyJson: string;
9
+ eventCount: number;
10
+ firstTs: number | null;
11
+ lastTs: number | null;
12
+ }
13
+ interface SplitReplayEventsResult {
14
+ chunks: BufferedReplayEvent[][];
15
+ droppedEventBytes: number[];
16
+ }
17
+ declare function toReplayChunkPayload(group: BufferedReplayEvent[]): ReplayChunkPayload;
18
+ declare function splitReplayEvents(buffered: BufferedReplayEvent[]): SplitReplayEventsResult;
19
+ declare function utf8ByteLength(value: string): number;
20
+ //#endregion
21
+ export { BufferedReplayEvent, ReplayChunkPayload, SplitReplayEventsResult, splitReplayEvents, toReplayChunkPayload, utf8ByteLength };
@@ -0,0 +1 @@
1
+ const textEncoder=new TextEncoder;function toReplayChunkPayload(group){let parts=[];for(let entry of group)parts.push(entry.json);return{bodyJson:`[${parts.join(`,`)}]`,eventCount:group.length,firstTs:group[0]?.ts??null,lastTs:group.at(-1)?.ts??null}}function splitReplayEvents(buffered){let chunks=[],droppedEventBytes=[],current=[],currentBytes=2;for(let entry of buffered){let singleEventBodyBytes=2+entry.bytes;if(singleEventBodyBytes>8388608){droppedEventBytes.push(singleEventBodyBytes);continue}let commaBytes=current.length===0?0:1;current.length>0&&currentBytes+commaBytes+entry.bytes>6291456&&(chunks.push(current),current=[],currentBytes=2),currentBytes+=(current.length===0?0:1)+entry.bytes,current.push(entry)}return current.length>0&&chunks.push(current),{chunks,droppedEventBytes}}function utf8ByteLength(value){return textEncoder.encode(value).length}export{splitReplayEvents,toReplayChunkPayload,utf8ByteLength};
@@ -0,0 +1,9 @@
1
+ //#region src/plugins/replay/compressor-host.d.ts
2
+ /** `gzip` is `null` when compression is unavailable — callers must POST the chunk uncompressed. */
3
+ interface Compressor {
4
+ readonly dispose: () => void;
5
+ readonly gzip: ((bytes: Uint8Array) => Promise<Uint8Array<ArrayBuffer>>) | null;
6
+ }
7
+ declare function createCompressor(): Compressor;
8
+ //#endregion
9
+ export { Compressor, createCompressor };
@@ -0,0 +1 @@
1
+ import{createLogger}from"../../util/log.mjs";const log=createLogger(`replay/compressor`),FAIL_OPEN={gzip:null,dispose:()=>void 0};function createCompressor(){return typeof CompressionStream>`u`?(log.warn(`CompressionStream unavailable; replay chunks will POST uncompressed`),FAIL_OPEN):{gzip:async bytes=>{let cs=new CompressionStream(`gzip`),compressed=new Response(cs.readable).arrayBuffer(),writer=cs.writable.getWriter();return await writer.write(new Uint8Array(bytes)),await writer.close(),new Uint8Array(await compressed)},dispose:()=>void 0}}export{createCompressor};
@@ -0,0 +1,23 @@
1
+ import { PluginContext } from "../lib/types.mjs";
2
+ import { ReplayChunkPayload } from "./chunking.mjs";
3
+ import { Compressor } from "./compressor-host.mjs";
4
+
5
+ //#region src/plugins/replay/delivery.d.ts
6
+ interface DeliverInput {
7
+ authHeaders: Record<string, string>;
8
+ compressor: Compressor;
9
+ ctx: PluginContext;
10
+ payload: ReplayChunkPayload;
11
+ skipCompression: boolean;
12
+ uploadBaseUrl: string;
13
+ }
14
+ declare function deliverChunk({
15
+ ctx,
16
+ uploadBaseUrl,
17
+ authHeaders,
18
+ compressor,
19
+ payload,
20
+ skipCompression
21
+ }: DeliverInput): Promise<void>;
22
+ //#endregion
23
+ export { DeliverInput, deliverChunk };
@@ -0,0 +1 @@
1
+ import{createLogger}from"../../util/log.mjs";import{appendPathBeforeQuery}from"../../internal/config.mjs";import{trace}from"@opentelemetry/api";const log=createLogger(`replay`),textEncoder=new TextEncoder;async function prepareBody(compressor,rawBytes){if(!compressor.gzip)return{body:rawBytes,encoding:null};try{return{body:await compressor.gzip(rawBytes),encoding:`gzip`}}catch(error){return log.warn(`gzip failed for chunk; sending uncompressed: %o`,error),{body:rawBytes,encoding:null}}}async function deliverChunk({ctx,uploadBaseUrl,authHeaders,compressor,payload,skipCompression}){let sessionId=ctx.getSessionId();if(!sessionId)return;let rawBytes=textEncoder.encode(payload.bodyJson),uncompressedBytes=rawBytes.byteLength,prepared=skipCompression?{body:rawBytes,encoding:null}:await prepareBody(compressor,rawBytes),headers={...authHeaders,"content-type":`application/json`,...prepared.encoding===`gzip`?{"content-encoding":`gzip`}:{}},res=await fetch(appendPathBeforeQuery(uploadBaseUrl,`/v2/replay/upload/${encodeURIComponent(sessionId)}`),{method:`POST`,headers,body:prepared.body});if(!res.ok){log.warn(`replay chunk upload rejected (%d)`,res.status);return}let{objectKey}=await res.json();trace.getTracer(`@interfere/react/replay`).startSpan(`replay.chunk`,{attributes:{"replay.chunk.uri":objectKey,"replay.chunk.size_bytes":uncompressedBytes,...prepared.encoding===`gzip`?{"replay.chunk.compressed_bytes":prepared.body.byteLength}:{},"replay.chunk.event_count":payload.eventCount,...payload.firstTs===null?{}:{"replay.chunk.first_event_ts":payload.firstTs},...payload.lastTs===null?{}:{"replay.chunk.last_event_ts":payload.lastTs}}}).end()}export{deliverChunk};
@@ -0,0 +1,6 @@
1
+ import { Plugin } from "../lib/types.mjs";
2
+
3
+ //#region src/plugins/replay/index.d.ts
4
+ declare const replayPlugin: Plugin;
5
+ //#endregion
6
+ export { replayPlugin as default };
@@ -0,0 +1 @@
1
+ import{createLogger}from"../../util/log.mjs";import{resolveTargets}from"../../internal/config.mjs";import{onPageHidden,onPageShown}from"../../internal/page-lifecycle.mjs";import{splitReplayEvents,toReplayChunkPayload}from"./chunking.mjs";import{createReplayBuffer}from"./buffer.mjs";import{createCompressor}from"./compressor-host.mjs";import{deliverChunk}from"./delivery.mjs";import{startReplayRecorder}from"./recorder.mjs";const log=createLogger(`replay`),replayPlugin={name:`replay`,setup(ctx){let targets=resolveTargets(),uploadBaseUrl=targets.collectorBaseUrl,authHeaders=Object.fromEntries(targets.ingest.headers.entries()),recorder=null,flushTimer=null,unsubscribeHidden=null,unsubscribeShown=null,isHidden=!1,compressor=createCompressor();function flush(opts){let drained=buffer.drain();if(drained.length===0)return;let{chunks,droppedEventBytes}=splitReplayEvents(drained);for(let bytes of droppedEventBytes)log.warn(`dropping replay event of ~%d bytes: too large to upload`,bytes);for(let group of chunks)deliverChunk({ctx,uploadBaseUrl,authHeaders,compressor,payload:toReplayChunkPayload(group),skipCompression:opts?.skipCompression??!1}).catch(error=>log.warn(`replay chunk dropped: %o`,error))}let buffer=createReplayBuffer({softLimitBytes:4194304,onSoftLimit:flush}),onHide=()=>{isHidden=!0,flush({skipCompression:!0})},onShow=()=>{isHidden&&(isHidden=!1,recorder?.takeFullSnapshot())};return(async()=>{recorder=await startReplayRecorder(event=>{isHidden||buffer.push(event)}),recorder&&(flushTimer=setInterval(flush,1e4),unsubscribeHidden=onPageHidden(onHide),unsubscribeShown=onPageShown(onShow),log.debug(`recording started`))})(),()=>{flush(),recorder?.stop(),flushTimer&&clearInterval(flushTimer),unsubscribeHidden?.(),unsubscribeShown?.(),compressor.dispose()}}};export{replayPlugin as default};
@@ -0,0 +1,8 @@
1
+ //#region src/plugins/replay/recorder.d.ts
2
+ interface ReplayRecorder {
3
+ stop(): void;
4
+ takeFullSnapshot(): void;
5
+ }
6
+ declare function startReplayRecorder(onEvent: (event: unknown) => void): Promise<ReplayRecorder | null>;
7
+ //#endregion
8
+ export { ReplayRecorder, startReplayRecorder };
@@ -0,0 +1 @@
1
+ import{createLogger}from"../../util/log.mjs";const log=createLogger(`replay`);async function startReplayRecorder(onEvent){try{let rrweb=await import(`rrweb`),stop=rrweb.record({emit:onEvent,recordAfter:`load`,inlineStylesheet:!0,slimDOMOptions:`all`,sampling:{mousemove:50,scroll:150,media:800}})??null;return{stop:()=>{stop?.()},takeFullSnapshot:()=>{try{rrweb.record.takeFullSnapshot?.(!0)}catch(error){log.warn(`takeFullSnapshot failed: %o`,error)}}}}catch{return log.error(`rrweb failed to load, replay disabled`),null}}export{startReplayRecorder};