@interfere/react 11.0.0-canary.6 → 11.0.0-canary.7

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.
@@ -8,7 +8,7 @@ interface AuthHeaders {
8
8
  }
9
9
  declare function withQueryParam(url: string, key: string, value: string): string;
10
10
  declare function appendPathBeforeQuery(url: string, path: string): string;
11
- declare function resolveTargets(): {
11
+ declare function resolveTargets(apiHost?: string): {
12
12
  /**
13
13
  * Base URL for OTLP exports — the trace/metric exporters append the
14
14
  * sink path themselves. Avoids the kernel having to regex-strip
@@ -1 +1 @@
1
- import{getGlobal}from"../util/global.mjs";import{API_PATHS,API_URL}from"@interfere/constants/api";import{resolveRoutePrefix}from"@interfere/constants/route-prefix";const TRAILING_SLASH_RE=/\/$/,RELATIVE_URL_BASE=`https://interfere.local`;function resolvePublicKey(){let injected=getGlobal(`__INTERFERE_PUBLIC_KEY__`);if(injected)return injected;if(typeof process<`u`)return process.env.INTERFERE_PUBLIC_KEY??void 0}function resolveApiUrlOverride(){let injected=getGlobal(`__INTERFERE_API_URL__`);if(injected)return injected}function isForceEnableActive(){return!!getGlobal(`__INTERFERE_FORCE_ENABLE__`)}function isProxyModeActive(){return!!getGlobal(`__INTERFERE_PROXY_MODE__`)}function serializeUrl(input,url){return input.startsWith(`/`)?`${url.pathname}${url.search}`:url.toString()}function withQueryParam(url,key,value){let next=new URL(url,RELATIVE_URL_BASE);return next.searchParams.set(key,value),serializeUrl(url,next)}function appendPublicKey(url,publicKey){return publicKey?withQueryParam(url,`pk`,publicKey):url}function appendPathBeforeQuery(url,path){let next=new URL(url,RELATIVE_URL_BASE),suffix=path.startsWith(`/`)?path:`/${path}`;return next.pathname=`${next.pathname.replace(TRAILING_SLASH_RE,``)}${suffix}`,serializeUrl(url,next)}function resolveTargets(){let publicKey=resolvePublicKey(),headers=new Headers({"content-type":`application/json`});isForceEnableActive()&&headers.set(`x-interfere-force-enable`,`1`);let baseUrl=publicKey&&!isProxyModeActive()?resolveApiUrlOverride()??API_URL:resolveRoutePrefix();return{collectorBaseUrl:appendPublicKey(baseUrl,publicKey),config:{url:appendPublicKey(`${baseUrl}${API_PATHS.CONFIG}`,publicKey),headers},ingest:{headers},session:{url:appendPublicKey(`${baseUrl}${API_PATHS.SESSION}`,publicKey),headers}}}export{appendPathBeforeQuery,resolveTargets,withQueryParam};
1
+ import{getGlobal}from"../util/global.mjs";import{API_PATHS,API_URL}from"@interfere/constants/api";const TRAILING_SLASH_RE=/\/$/,RELATIVE_URL_BASE=`https://interfere.local`;function resolvePublicKey(){let injected=getGlobal(`__INTERFERE_PUBLIC_KEY__`);if(injected)return injected;if(typeof process<`u`)return process.env.INTERFERE_PUBLIC_KEY??void 0}function isForceEnableActive(){return!!getGlobal(`__INTERFERE_FORCE_ENABLE__`)}function resolveApiHost(explicit){return explicit??getGlobal(`__INTERFERE_API_URL__`)}function normalizeOrigin(host){try{return new URL(host).origin}catch{return API_URL}}function serializeUrl(input,url){return input.startsWith(`/`)?`${url.pathname}${url.search}`:url.toString()}function withQueryParam(url,key,value){let next=new URL(url,RELATIVE_URL_BASE);return next.searchParams.set(key,value),serializeUrl(url,next)}function appendPublicKey(url,publicKey){return publicKey?withQueryParam(url,`pk`,publicKey):url}function appendPathBeforeQuery(url,path){let next=new URL(url,RELATIVE_URL_BASE),suffix=path.startsWith(`/`)?path:`/${path}`;return next.pathname=`${next.pathname.replace(TRAILING_SLASH_RE,``)}${suffix}`,serializeUrl(url,next)}function resolveTargets(apiHost){let publicKey=resolvePublicKey(),headers=new Headers({"content-type":`application/json`});isForceEnableActive()&&headers.set(`x-interfere-force-enable`,`1`);let host=resolveApiHost(apiHost),baseUrl=host?normalizeOrigin(host):API_URL;return{collectorBaseUrl:appendPublicKey(baseUrl,publicKey),config:{url:appendPublicKey(`${baseUrl}${API_PATHS.CONFIG}`,publicKey),headers},ingest:{headers},session:{url:appendPublicKey(`${baseUrl}${API_PATHS.SESSION}`,publicKey),headers}}}export{appendPathBeforeQuery,resolveTargets,withQueryParam};
@@ -19,6 +19,13 @@ import { ErrorMechanism } from "@interfere/types/sdk/plugins/payload/errors";
19
19
  //#region src/internal/kernel.d.ts
20
20
  declare function buildSdkStack(wrapperVersions?: string[]): string[];
21
21
  interface KernelOptions {
22
+ /**
23
+ * The endpoint the SDK posts telemetry to. Defaults to Interfere's
24
+ * endpoint. Set it to a subdomain of your own site that resolves to
25
+ * Interfere via a DNS record (e.g. `"https://in.example.com"`) to
26
+ * survive ad-blockers, or to a regional endpoint. Must be a full URL.
27
+ */
28
+ apiHost?: string;
22
29
  consent?: ConsentState;
23
30
  /**
24
31
  * Override the automatic dev-mode guard. When `undefined`, the SDK
@@ -39,16 +46,6 @@ interface KernelOptions {
39
46
  * cleanly by surface.
40
47
  */
41
48
  serviceName?: string;
42
- /**
43
- * `false` skips registering the SDK's standalone ingest service worker.
44
- * Set this when the host already runs a service worker at scope `/`
45
- * (e.g. a Workbox precache worker) and wires {@link installIngestQueue}
46
- * into it instead — two workers can't both control `/`, and a worker
47
- * scoped to `/api/interfere/` never sees page requests.
48
- *
49
- * @default true
50
- */
51
- serviceWorker?: boolean;
52
49
  /**
53
50
  * `false` skips the OTel module entirely — the bundler code-splits
54
51
  * `internal/otel/*` out of this path so the error-only bundle never
@@ -65,6 +62,14 @@ interface KernelOptions {
65
62
  * need to touch any of these — sensible defaults ship out of the box.
66
63
  */
67
64
  interface TracingOptions {
65
+ /**
66
+ * Hostnames where your backend runs, e.g. `["api.example.com"]`. Every
67
+ * request to a listed host carries the W3C `traceparent` header so the
68
+ * browser trace continues on the server. Same-origin requests always
69
+ * propagate — you only need this when your API is on a different host
70
+ * than your app. Matched by hostname (any path, port, or scheme); no regex.
71
+ */
72
+ apiHosts?: string[];
68
73
  /**
69
74
  * Promote failed `fetch` / `XHR` responses to exception events on the
70
75
  * active span. **On by default** — captures same-origin (and
@@ -82,10 +87,11 @@ interface TracingOptions {
82
87
  */
83
88
  ignoreUrls?: (string | RegExp)[];
84
89
  /**
85
- * URL patterns that should receive W3C `traceparent` + `baggage`
86
- * headers on outgoing fetch / XHR. Same-origin requests always
87
- * propagate; this allowlist enables cross-origin propagation
88
- * (separate API origin, internal service mesh, etc.).
90
+ * @deprecated Prefer {@link apiHosts} — it takes plain hostnames and
91
+ * needs no regex. Still fully supported: raw URL patterns injected with
92
+ * `traceparent` + `baggage` on outgoing fetch / XHR. Note string entries
93
+ * match the full request URL by exact equality (an OpenTelemetry quirk),
94
+ * so cross-origin values usually have to be a `RegExp`.
89
95
  */
90
96
  propagateContextUrls?: (string | RegExp)[];
91
97
  /**
@@ -1 +1 @@
1
- import{createLogger}from"../util/log.mjs";import{onPageHidden}from"./page-lifecycle.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{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()??``}onSessionPause(handler){return this.tracker.onSessionPause(handler)}onSessionResume(handler){return this.tracker.onSessionResume(handler)}getPluginConfigState(key){return this.requireRuntime().getPluginConfigState(key)}subscribePluginConfigState(key,listener){return this.requireRuntime().subscribePluginConfigState(key,listener)}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),opts.serviceWorker!==!1&&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){runtime.applyRemoteConfigDefaults();return}let config=await res.json();if(config?.plugins){runtime.applyRemoteConfig(config.plugins),log.debug(`applied remote config`);return}runtime.applyRemoteConfigDefaults()}catch{runtime.applyRemoteConfigDefaults(),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{onPageHidden}from"./page-lifecycle.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{PluginRuntime}from"./plugin-runtime.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=/\/$/,REPLAY_UPLOAD_RE=/\/v2\/replay\/upload\//;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};if(opts.errorDigest&&(attrs[`interfere.error.digest`]=opts.errorDigest),opts.subtype&&(attrs[`interfere.exception.subtype`]=opts.subtype),isNonErrorException(value))return 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));let debugIds=globalThis._debugIds;return debugIds&&typeof debugIds==`object`&&Object.keys(debugIds).length>0&&(attrs[`interfere.exception.debug_ids`]=safeStringify(debugIds)),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()??``}onSessionPause(handler){return this.tracker.onSessionPause(handler)}onSessionResume(handler){return this.tracker.onSessionResume(handler)}getPluginConfigState(key){return this.requireRuntime().getPluginConfigState(key)}subscribePluginConfigState(key,listener){return this.requireRuntime().subscribePluginConfigState(key,listener)}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(opts.apiHost);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),kernel}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,REPLAY_UPLOAD_RE],...tracing.apiHosts?{apiHosts:tracing.apiHosts}:{},...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,credentials:`omit`,signal:AbortSignal.timeout(1e4)});if(!res.ok){runtime.applyRemoteConfigDefaults();return}let config=await res.json();if(config?.plugins){runtime.applyRemoteConfig(config.plugins),log.debug(`applied remote config`);return}runtime.applyRemoteConfigDefaults()}catch{runtime.applyRemoteConfigDefaults(),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,9 +1,10 @@
1
1
  import { ExportResult } from "@opentelemetry/core";
2
2
  import { AggregationTemporality, PushMetricExporter, ResourceMetrics } from "@opentelemetry/sdk-metrics";
3
3
  import { ReadableLogRecord } from "@opentelemetry/sdk-logs";
4
- import { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
4
+ import { SpanExporter } from "@opentelemetry/sdk-trace-base";
5
5
 
6
6
  //#region src/internal/otel/exporter.d.ts
7
+ type SignalSender = (payload: Uint8Array | undefined, resultCallback: (result: ExportResult) => void) => void;
7
8
  interface ExporterCommon {
8
9
  /**
9
10
  * Kept for wrapper compatibility. Public-key identity is baked into
@@ -16,70 +17,29 @@ interface ExporterCommon {
16
17
  /** Exported for direct unit testing. */
17
18
  declare function buildSinkUrl(collectorUrl: string): string;
18
19
  /**
19
- * Beacon-flavoured sink URL — encodes the producer-version (always)
20
- * while preserving the `pk` query already baked into `collectorUrl`.
20
+ * Ingest URL carrying the producer-version query while preserving the
21
+ * `pk` query already baked into `collectorUrl`.
21
22
  *
22
23
  * Exported for direct unit testing.
23
24
  */
24
- declare function buildBeaconUrl(input: ExporterCommon): string;
25
- /**
26
- * Browser-side OTLP trace exporter.
27
- *
28
- * Dispatches every export via `navigator.sendBeacon` — which is the
29
- * only browser transport that reliably commits a request the page is
30
- * also tearing down (`visibilitychange→hidden`, hard navigation). The
31
- * `keepalive: true` fetch path the OTLP HTTP exporter ships with
32
- * works for small payloads but falls back to ordinary fetch (which
33
- * the renderer aborts on unload) once the cumulative 64KiB / 9-
34
- * concurrent budget is exhausted. Production data on 2026-05-11
35
- * attributed ~15% browser-fetch span loss to that fallback; the
36
- * beacon path closes that hole by design.
37
- *
38
- * Identity rides the URL because beacons can't carry custom headers.
39
- *
40
- * No retry on failure. The service worker backstop captures 5xx /
41
- * network failures separately by intercepting the same beacon POST
42
- * and queueing into IndexedDB for replay (`internal/sw.ts`).
43
- */
44
- declare class BeaconTraceExporter implements SpanExporter {
45
- private readonly url;
46
- constructor(url: string);
47
- export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void;
48
- shutdown(): Promise<void>;
49
- forceFlush(): Promise<void>;
50
- }
51
- /**
52
- * Browser-side OTLP log exporter — same beacon transport, log
53
- * payload. See `BeaconTraceExporter` for the design rationale.
54
- */
55
- declare class BeaconLogExporter {
56
- private readonly url;
57
- constructor(url: string);
25
+ declare function buildIngestUrl(collectorUrl: string): string;
26
+ declare class IngestLogExporter {
27
+ private readonly send;
28
+ constructor(send: SignalSender);
58
29
  export(logs: ReadableLogRecord[], resultCallback: (result: ExportResult) => void): void;
59
30
  shutdown(): Promise<void>;
60
31
  forceFlush(): Promise<void>;
61
32
  }
62
- /**
63
- * Browser-side OTLP metric exporter — beacon transport, metric
64
- * payload, `DELTA` temporality.
65
- *
66
- * `DELTA` matches what the OTLP HTTP exporter the kit's previous
67
- * `OTLPMetricExporter` was configured with (see the
68
- * `temporalityPreference: AggregationTemporalityPreference.DELTA`
69
- * arg `provider.ts` used to pass). Returning the same temporality
70
- * for every instrument type keeps the wire shape downstream
71
- * consumers receive unchanged across the migration.
72
- */
73
- declare class BeaconMetricExporter implements PushMetricExporter {
74
- private readonly url;
75
- constructor(url: string);
33
+ declare class IngestMetricExporter implements PushMetricExporter {
34
+ private readonly send;
35
+ constructor(send: SignalSender);
76
36
  export(metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void): void;
77
37
  selectAggregationTemporality(): AggregationTemporality;
78
38
  forceFlush(): Promise<void>;
79
39
  shutdown(): Promise<void>;
80
40
  }
81
- declare function createBeaconTraceExporter(input: ExporterCommon): BeaconTraceExporter;
82
- declare function createBeaconMetricExporter(input: ExporterCommon): BeaconMetricExporter;
83
- declare function createBeaconLogExporter(input: ExporterCommon): BeaconLogExporter;
41
+ declare function createTraceExporter(input: ExporterCommon): SpanExporter;
42
+ declare function createMetricExporter(input: ExporterCommon): IngestMetricExporter;
43
+ declare function createLogExporter(input: ExporterCommon): IngestLogExporter;
84
44
  //#endregion
85
- export { buildBeaconUrl, buildSinkUrl, createBeaconLogExporter, createBeaconMetricExporter, createBeaconTraceExporter };
45
+ export { buildIngestUrl, buildSinkUrl, createLogExporter, createMetricExporter, createTraceExporter };
@@ -1 +1 @@
1
- import{appendPathBeforeQuery,withQueryParam}from"../config.mjs";import{PRODUCER_VERSION}from"../version.mjs";import{diag}from"@opentelemetry/api";import{ExportResultCode}from"@opentelemetry/core";import{JsonLogsSerializer}from"@opentelemetry/otlp-transformer/build/esm/logs/json/logs.js";import{JsonMetricsSerializer}from"@opentelemetry/otlp-transformer/build/esm/metrics/json/metrics.js";import{JsonTraceSerializer}from"@opentelemetry/otlp-transformer/build/esm/trace/json/trace.js";import{AggregationTemporality}from"@opentelemetry/sdk-metrics";function buildSinkUrl(collectorUrl){return appendPathBeforeQuery(collectorUrl,`/v2/sink`)}function buildBeaconUrl(input){return withQueryParam(buildSinkUrl(input.collectorUrl),`_pv`,PRODUCER_VERSION)}function canSendBeacon(){return typeof navigator<`u`&&typeof navigator.sendBeacon==`function`}function reportBeacon(url,payload,signal,resultCallback){if(!payload||payload.byteLength===0){diag.debug(`[interfere/beacon] ${signal} serializer returned no bytes`),resultCallback({code:ExportResultCode.SUCCESS});return}if(!canSendBeacon()){diag.debug(`[interfere/beacon] navigator.sendBeacon unavailable; dropping ${signal} batch (${payload.byteLength}B)`),resultCallback({code:ExportResultCode.SUCCESS});return}let blob=new Blob([payload],{type:`application/json`});navigator.sendBeacon(url,blob)||diag.debug(`[interfere/beacon] navigator.sendBeacon refused ${signal} payload (oversized or queue full); dropping ${payload.byteLength}B`),resultCallback({code:ExportResultCode.SUCCESS})}var BeaconTraceExporter=class{url;constructor(url){this.url=url}export(spans,resultCallback){if(spans.length===0){resultCallback({code:ExportResultCode.SUCCESS});return}reportBeacon(this.url,JsonTraceSerializer.serializeRequest(spans),`trace`,resultCallback)}shutdown(){return Promise.resolve()}forceFlush(){return Promise.resolve()}},BeaconLogExporter=class{url;constructor(url){this.url=url}export(logs,resultCallback){if(logs.length===0){resultCallback({code:ExportResultCode.SUCCESS});return}reportBeacon(this.url,JsonLogsSerializer.serializeRequest(logs),`log`,resultCallback)}shutdown(){return Promise.resolve()}forceFlush(){return Promise.resolve()}},BeaconMetricExporter=class{url;constructor(url){this.url=url}export(metrics,resultCallback){reportBeacon(this.url,JsonMetricsSerializer.serializeRequest(metrics),`metric`,resultCallback)}selectAggregationTemporality(){return AggregationTemporality.DELTA}forceFlush(){return Promise.resolve()}shutdown(){return Promise.resolve()}};function createBeaconTraceExporter(input){return new BeaconTraceExporter(buildBeaconUrl(input))}function createBeaconMetricExporter(input){return new BeaconMetricExporter(buildBeaconUrl(input))}function createBeaconLogExporter(input){return new BeaconLogExporter(buildBeaconUrl(input))}export{buildBeaconUrl,buildSinkUrl,createBeaconLogExporter,createBeaconMetricExporter,createBeaconTraceExporter};
1
+ import{appendPathBeforeQuery,withQueryParam}from"../config.mjs";import{PRODUCER_VERSION}from"../version.mjs";import{ExportResultCode}from"@opentelemetry/core";import{JsonLogsSerializer}from"@opentelemetry/otlp-transformer/build/esm/logs/json/logs.js";import{JsonMetricsSerializer}from"@opentelemetry/otlp-transformer/build/esm/metrics/json/metrics.js";import{JsonTraceSerializer}from"@opentelemetry/otlp-transformer/build/esm/trace/json/trace.js";import{AggregationTemporality}from"@opentelemetry/sdk-metrics";function buildSinkUrl(collectorUrl){return appendPathBeforeQuery(collectorUrl,`/v2/sink`)}function buildIngestUrl(collectorUrl){return withQueryParam(buildSinkUrl(collectorUrl),`_pv`,PRODUCER_VERSION)}function makeSender(input){let url=buildIngestUrl(input.collectorUrl);return(payload,resultCallback)=>{payload&&payload.byteLength>0&&fetch(url,{method:`POST`,keepalive:!0,credentials:`omit`,headers:{"content-type":`application/json`},body:payload}).catch(()=>void 0),resultCallback({code:ExportResultCode.SUCCESS})}}var IngestTraceExporter=class{send;constructor(send){this.send=send}export(spans,resultCallback){if(spans.length===0){resultCallback({code:ExportResultCode.SUCCESS});return}this.send(JsonTraceSerializer.serializeRequest(spans),resultCallback)}shutdown(){return Promise.resolve()}forceFlush(){return Promise.resolve()}},IngestLogExporter=class{send;constructor(send){this.send=send}export(logs,resultCallback){if(logs.length===0){resultCallback({code:ExportResultCode.SUCCESS});return}this.send(JsonLogsSerializer.serializeRequest(logs),resultCallback)}shutdown(){return Promise.resolve()}forceFlush(){return Promise.resolve()}},IngestMetricExporter=class{send;constructor(send){this.send=send}export(metrics,resultCallback){this.send(JsonMetricsSerializer.serializeRequest(metrics),resultCallback)}selectAggregationTemporality(){return AggregationTemporality.DELTA}forceFlush(){return Promise.resolve()}shutdown(){return Promise.resolve()}};function createTraceExporter(input){return new IngestTraceExporter(makeSender(input))}function createMetricExporter(input){return new IngestMetricExporter(makeSender(input))}function createLogExporter(input){return new IngestLogExporter(makeSender(input))}export{buildIngestUrl,buildSinkUrl,createLogExporter,createMetricExporter,createTraceExporter};
@@ -1,6 +1,5 @@
1
- import { createBeaconMetricExporter, createBeaconTraceExporter } from "./exporter.mjs";
2
1
  import { CaptureFailedRequestsOption, CaptureFailedRequestsOptions, HttpStatusCodeRange, InstrumentationsInput, registerBundledInstrumentations } from "./instrumentations.mjs";
3
2
  import { readPropagationFromDocument } from "./propagation.mjs";
4
3
  import { OtelProviderHandle, OtelProviderInput, buildOtelProvider } from "./provider.mjs";
5
4
  import { captureWebVitals } from "./web-vitals.mjs";
6
- export { type CaptureFailedRequestsOption, type CaptureFailedRequestsOptions, type HttpStatusCodeRange, type InstrumentationsInput, type OtelProviderHandle, type OtelProviderInput, buildOtelProvider, captureWebVitals, createBeaconMetricExporter, createBeaconTraceExporter, readPropagationFromDocument, registerBundledInstrumentations };
5
+ export { type CaptureFailedRequestsOption, type CaptureFailedRequestsOptions, type HttpStatusCodeRange, type InstrumentationsInput, type OtelProviderHandle, type OtelProviderInput, buildOtelProvider, captureWebVitals, readPropagationFromDocument, registerBundledInstrumentations };
@@ -1 +1 @@
1
- import{createBeaconMetricExporter,createBeaconTraceExporter}from"./exporter.mjs";import{registerBundledInstrumentations}from"./instrumentations.mjs";import{readPropagationFromDocument}from"./propagation.mjs";import{buildOtelProvider}from"./provider.mjs";import{captureWebVitals}from"./web-vitals.mjs";export{buildOtelProvider,captureWebVitals,createBeaconMetricExporter,createBeaconTraceExporter,readPropagationFromDocument,registerBundledInstrumentations};
1
+ import{registerBundledInstrumentations}from"./instrumentations.mjs";import{readPropagationFromDocument}from"./propagation.mjs";import{buildOtelProvider}from"./provider.mjs";import{captureWebVitals}from"./web-vitals.mjs";export{buildOtelProvider,captureWebVitals,readPropagationFromDocument,registerBundledInstrumentations};
@@ -42,6 +42,11 @@ declare const _testOnlyResolveFailureRanges: typeof resolveFailureRanges;
42
42
  declare const _testOnlyCreateFailureGate: typeof createFailureGate;
43
43
  declare const _testOnlyClassifyNetworkError: typeof classifyNetworkError;
44
44
  interface InstrumentationsInput {
45
+ /**
46
+ * Hostnames the fetch + XHR instrumentations inject `traceparent` onto,
47
+ * matched by host (any path/port/scheme). Same-origin always propagates.
48
+ */
49
+ apiHosts?: string[];
45
50
  /** See {@link CaptureFailedRequestsOption}. On by default. */
46
51
  captureFailedRequests?: CaptureFailedRequestsOption;
47
52
  /**
@@ -1 +1 @@
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
+ 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 escapeRegExp(value){return value.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function apiHostToMatcher(host){return RegExp(`^https?://${escapeRegExp(host)}(:\\d+)?([/?#]|$)`)}function registerBundledInstrumentations(input){let{tracerProvider,loggerProvider,ignoreUrls,apiHosts=[],propagateContextUrls=[]}=input,resolveRoute=input.resolveRoute,origin=sameOriginPattern(),propagateMatchers=[...apiHosts.map(apiHostToMatcher),...propagateContextUrls],fetchIgnore=[...ignoreUrls,...THIRD_PARTY_IGNORE],failureRanges=resolveFailureRanges(input.captureFailedRequests),failureGate=createFailureGate();function isTrackedUrl(url){return origin.test(url)||propagateMatchers.some(re=>re instanceof RegExp?re.test(url):url.includes(re))}return registerInstrumentations({tracerProvider,...loggerProvider?{loggerProvider}:{},instrumentations:[new FetchInstrumentation({propagateTraceHeaderCorsUrls:[origin,...propagateMatchers],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,...propagateMatchers],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{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
+ import{getGlobal}from"../../util/global.mjs";import{SDK_NAME,SDK_VERSION}from"../version.mjs";import{createLogExporter,createMetricExporter,createTraceExporter}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=createTraceExporter({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:createMetricExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),exportIntervalMillis:3e4}),meterProvider=new MeterProvider({resource,readers:[metricReader,...input.additionalMetricReaders??[]]}),logExporter=createLogExporter({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};
package/dist/package.mjs CHANGED
@@ -1 +1 @@
1
- var name=`@interfere/react`,version=`11.0.0-canary.6`;export{name,version};
1
+ var name=`@interfere/react`,version=`11.0.0-canary.7`;export{name,version};
@@ -1 +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};
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,credentials:`omit`,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};
@@ -1 +1 @@
1
- import{createLogger}from"../util/log.mjs";import{ActivityMonitor}from"../internal/activity.mjs";import{appendPathBeforeQuery}from"../internal/config.mjs";import{DeviceManager}from"./device.mjs";import{GeoDetector}from"./geo.mjs";import{SessionManager}from"./session.mjs";const log=createLogger(`tracking`);var SessionTracker=class{target;fetcher;device;geo;mgr=null;activity=new ActivityMonitor;currentIdentity=null;identifiedSessionId=null;syncedSessionId=null;syncAttemptMs=0;generation=0;constructor(opts){this.target=opts.target,this.fetcher=opts.fetcher??globalThis.fetch.bind(globalThis),this.device=opts.device??new DeviceManager,this.geo=opts.geo??new GeoDetector}start(){this.device.init(),this.geo.detect(),this.mgr=new SessionManager(id=>{this.onRotate(id).catch(()=>void 0)}),this.activity.start({markActivity:()=>this.markActivity(),reset:()=>this.rotateSession(),startedAt:()=>this.getSessionStartedAt()})}onSessionPause(handler){return this.activity.onPause(handler)}onSessionResume(handler){return this.activity.onResume(handler)}sessionId(){let id=this.mgr?.getSessionId()??null;return id&&this.ensureSynced(id),id}windowId(){return this.mgr?.getWindowId()??null}markActivity(){this.mgr?.markActivity()}getSessionStartedAt(){return this.mgr?.getSessionStartedAt()??0}getDeviceId(){return this.device.getDeviceId()}getFpHash(){return this.device.getFpHash()}headers(){let h={},sid=this.mgr?.getSessionId()??null;sid&&(h[`x-interfere-session`]=sid);let wid=this.mgr?.getWindowId()??null;wid&&(h[`x-interfere-window`]=wid);let did=this.device.getDeviceId();return did&&(h[`x-interfere-device`]=did),h}getIdentity(){return this.currentIdentity}async identify(params){if(!this.mgr)return;let sessionId=this.mgr.getSessionId();if(this.identifiedSessionId===sessionId){log.debug(`skipped, already identified for session %s`,sessionId);return}this.currentIdentity=params,this.identifiedSessionId=sessionId;let gen=this.generation,[fpHash,country]=await Promise.all([this.device.whenFingerprintReady(),this.geo.detect()]),deviceId=this.device.getDeviceId();if(gen!==this.generation){this.identifiedSessionId=null;return}log.info(`POST session %s → identify`,sessionId),this.fetcher(appendPathBeforeQuery(this.target.url,`/identify`),{method:`POST`,headers:this.requestHeaders(),body:JSON.stringify({sessionId,deviceId,fpHash,...params,...country&&{country}}),keepalive:!0,signal:AbortSignal.timeout(1e4)}).catch(()=>{this.identifiedSessionId=null,log.warn(`identify failed for session %s`,sessionId)})}clearIdentity(){this.currentIdentity=null,this.identifiedSessionId=null}reset(){this.clearIdentity(),this.mgr?.reset()}rotateSession(){this.mgr?.reset()}dispose(){this.activity.stop(),this.generation+=1,this.clearIdentity(),this.syncedSessionId=null,this.syncAttemptMs=0,this.mgr=null}requestHeaders(){return{...Object.fromEntries(this.target.headers.entries()),...this.headers()}}ensureSynced(sessionId){this.syncedSessionId!==sessionId&&(Date.now()-this.syncAttemptMs<5e3||this.syncSession(sessionId,this.device.getDeviceId(),this.device.getFpHash()))}syncSession(sessionId,deviceId,fpHash){this.syncAttemptMs=Date.now(),this.syncedSessionId=sessionId,this.fetcher(this.target.url,{method:`POST`,headers:this.requestHeaders(),body:JSON.stringify({sessionId,deviceId,fpHash}),keepalive:!0,signal:AbortSignal.timeout(1e4)}).then(res=>{res.ok||(this.syncedSessionId=null)}).catch(()=>{this.syncedSessionId=null,log.warn(`session sync failed, will retry`)})}async onRotate(sessionId){this.syncedSessionId=null,this.syncAttemptMs=Date.now();let gen=this.generation,fpHash=await this.device.whenFingerprintReady(),deviceId=this.device.getDeviceId();gen===this.generation&&(log.debug(`POST session %s (device=%s fp=%s)`,sessionId,deviceId??`pending`,fpHash??`none`),this.syncSession(sessionId,deviceId,fpHash))}};export{SessionTracker};
1
+ import{createLogger}from"../util/log.mjs";import{ActivityMonitor}from"../internal/activity.mjs";import{appendPathBeforeQuery}from"../internal/config.mjs";import{DeviceManager}from"./device.mjs";import{GeoDetector}from"./geo.mjs";import{SessionManager}from"./session.mjs";const log=createLogger(`tracking`);var SessionTracker=class{target;fetcher;device;geo;mgr=null;activity=new ActivityMonitor;currentIdentity=null;identifiedSessionId=null;syncedSessionId=null;syncAttemptMs=0;generation=0;constructor(opts){this.target=opts.target,this.fetcher=opts.fetcher??globalThis.fetch.bind(globalThis),this.device=opts.device??new DeviceManager,this.geo=opts.geo??new GeoDetector}start(){this.device.init(),this.geo.detect(),this.mgr=new SessionManager(id=>{this.onRotate(id).catch(()=>void 0)}),this.activity.start({markActivity:()=>this.markActivity(),reset:()=>this.rotateSession(),startedAt:()=>this.getSessionStartedAt()})}onSessionPause(handler){return this.activity.onPause(handler)}onSessionResume(handler){return this.activity.onResume(handler)}sessionId(){let id=this.mgr?.getSessionId()??null;return id&&this.ensureSynced(id),id}windowId(){return this.mgr?.getWindowId()??null}markActivity(){this.mgr?.markActivity()}getSessionStartedAt(){return this.mgr?.getSessionStartedAt()??0}getDeviceId(){return this.device.getDeviceId()}getFpHash(){return this.device.getFpHash()}headers(){let h={},sid=this.mgr?.getSessionId()??null;sid&&(h[`x-interfere-session`]=sid);let wid=this.mgr?.getWindowId()??null;wid&&(h[`x-interfere-window`]=wid);let did=this.device.getDeviceId();return did&&(h[`x-interfere-device`]=did),h}getIdentity(){return this.currentIdentity}async identify(params){if(!this.mgr)return;let sessionId=this.mgr.getSessionId();if(this.identifiedSessionId===sessionId){log.debug(`skipped, already identified for session %s`,sessionId);return}this.currentIdentity=params,this.identifiedSessionId=sessionId;let gen=this.generation,[fpHash,country]=await Promise.all([this.device.whenFingerprintReady(),this.geo.detect()]),deviceId=this.device.getDeviceId();if(gen!==this.generation){this.identifiedSessionId=null;return}log.info(`POST session %s → identify`,sessionId),this.fetcher(appendPathBeforeQuery(this.target.url,`/identify`),{method:`POST`,headers:this.requestHeaders(),body:JSON.stringify({sessionId,deviceId,fpHash,...params,...country&&{country}}),keepalive:!0,credentials:`omit`,signal:AbortSignal.timeout(1e4)}).catch(()=>{this.identifiedSessionId=null,log.warn(`identify failed for session %s`,sessionId)})}clearIdentity(){this.currentIdentity=null,this.identifiedSessionId=null}reset(){this.clearIdentity(),this.mgr?.reset()}rotateSession(){this.mgr?.reset()}dispose(){this.activity.stop(),this.generation+=1,this.clearIdentity(),this.syncedSessionId=null,this.syncAttemptMs=0,this.mgr=null}requestHeaders(){return{...Object.fromEntries(this.target.headers.entries()),...this.headers()}}ensureSynced(sessionId){this.syncedSessionId!==sessionId&&(Date.now()-this.syncAttemptMs<5e3||this.syncSession(sessionId,this.device.getDeviceId(),this.device.getFpHash()))}syncSession(sessionId,deviceId,fpHash){this.syncAttemptMs=Date.now(),this.syncedSessionId=sessionId,this.fetcher(this.target.url,{method:`POST`,headers:this.requestHeaders(),body:JSON.stringify({sessionId,deviceId,fpHash}),keepalive:!0,credentials:`omit`,signal:AbortSignal.timeout(1e4)}).then(res=>{res.ok||(this.syncedSessionId=null)}).catch(()=>{this.syncedSessionId=null,log.warn(`session sync failed, will retry`)})}async onRotate(sessionId){this.syncedSessionId=null,this.syncAttemptMs=Date.now();let gen=this.generation,fpHash=await this.device.whenFingerprintReady(),deviceId=this.device.getDeviceId();gen===this.generation&&(log.debug(`POST session %s (device=%s fp=%s)`,sessionId,deviceId??`pending`,fpHash??`none`),this.syncSession(sessionId,deviceId,fpHash))}};export{SessionTracker};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interfere/react",
3
- "version": "11.0.0-canary.6",
3
+ "version": "11.0.0-canary.7",
4
4
  "license": "MIT",
5
5
  "description": "Client-side React SDK for Interfere. Error tracking, session replay, and analytics.",
6
6
  "keywords": [
@@ -65,10 +65,6 @@
65
65
  "types": "./dist/internal/wrapper-singleton.d.mts",
66
66
  "default": "./dist/internal/wrapper-singleton.mjs"
67
67
  },
68
- "./sw": {
69
- "types": "./dist/sw.d.mts",
70
- "default": "./dist/sw.mjs"
71
- },
72
68
  "./sw-runtime": {
73
69
  "monorepo": "./src/internal/sw-queue.ts",
74
70
  "types": "./dist/internal/sw-queue.d.mts",
@@ -87,13 +83,12 @@
87
83
  "scripts": {
88
84
  "build": "tsdown",
89
85
  "test": "vitest run",
90
- "typecheck": "tsgo --noEmit",
91
- "typegen": "bun run scripts/build-sw.ts"
86
+ "typecheck": "tsgo --noEmit"
92
87
  },
93
88
  "dependencies": {
94
89
  "@fingerprintjs/fingerprintjs": "^5.2.0",
95
- "@interfere/constants": "^9.0.2-canary.2",
96
- "@interfere/types": "^10.0.0-canary.5",
90
+ "@interfere/constants": "^9.0.2-canary.3",
91
+ "@interfere/types": "^10.0.0-canary.6",
97
92
  "@opentelemetry/api": "^1.9.1",
98
93
  "@opentelemetry/api-logs": "^0.219.0",
99
94
  "@opentelemetry/context-zone": "^2.7.1",
@@ -114,7 +109,7 @@
114
109
  "@opentelemetry/sdk-trace-web": "^2.7.1",
115
110
  "@opentelemetry/web-common": "^0.219.0",
116
111
  "@ua-parser-js/pro-enterprise": "^2.0.6",
117
- "rrweb": "2.0.1",
112
+ "rrweb": "2.1.0",
118
113
  "uuid": "^14.0.1",
119
114
  "web-vitals": "^5.3.0",
120
115
  "workbox-background-sync": "^7.4.0"
@@ -132,12 +127,12 @@
132
127
  }
133
128
  },
134
129
  "devDependencies": {
135
- "@interfere/test-utils": "^9.0.1-canary.0",
136
- "@interfere/typescript-config": "^9.0.1-canary.0",
137
- "@types/node": "^26.0.0",
130
+ "@interfere/test-utils": "^9.0.1-canary.1",
131
+ "@interfere/typescript-config": "^9.0.1-canary.1",
132
+ "@types/node": "^26.1.0",
138
133
  "@types/react": "19.2.17",
139
134
  "@types/react-dom": "19.2.3",
140
- "@typescript/native-preview": "7.0.0-dev.20260623.1",
135
+ "@typescript/native-preview": "7.0.0-dev.20260701.1",
141
136
  "@vitest/browser": "4.1.9",
142
137
  "@vitest/browser-playwright": "4.1.9",
143
138
  "@vitest/coverage-v8": "4.1.9",
@@ -145,7 +140,6 @@
145
140
  "playwright": "^1.61.1",
146
141
  "react": "^19.2.6",
147
142
  "react-dom": "^19.2.6",
148
- "rolldown": "^1.1.1",
149
143
  "tsdown": "^0.22.2",
150
144
  "vitest": "4.1.9",
151
145
  "vitest-browser-react": "2.2.0"
@@ -1,24 +0,0 @@
1
- //#region src/internal/sw.d.ts
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>;
23
- //#endregion
24
- export { SwMessage, SwRegistrationOptions, registerServiceWorker };
@@ -1 +0,0 @@
1
- import{createLogger}from"../util/log.mjs";import{resolveRoutePrefix}from"@interfere/constants/route-prefix";const log=createLogger(`sw`);async function registerServiceWorker(opts={}){if(typeof navigator>`u`||!(`serviceWorker`in navigator))return;let prefix=resolveRoutePrefix(),swPath=`${prefix}/sw`,swScope=`${prefix}/`;try{await navigator.serviceWorker.register(swPath,{scope:swScope}),log.debug(`registered at %s (scope %s)`,swPath,swScope)}catch(err){log.warn(`registration failed, falling back to direct fetch`,err);return}opts.onMessage&&navigator.serviceWorker.addEventListener(`message`,event=>{let data=event.data;isSwMessage(data)&&opts.onMessage?.(data)})}function isSwMessage(value){if(!value||typeof value!=`object`)return!1;let v=value;return(v.type===`interfere.sw.queued`||v.type===`interfere.sw.replayed`||v.type===`interfere.sw.dropped`)&&typeof v.url==`string`}export{registerServiceWorker};
package/dist/sw.d.mts DELETED
@@ -1,2 +0,0 @@
1
- // AUTOGENERATED by scripts/build-sw.ts — do not edit.
2
- export declare const SW_SCRIPT: string;
package/dist/sw.mjs DELETED
@@ -1,2 +0,0 @@
1
- // AUTOGENERATED by scripts/build-sw.ts — do not edit.
2
- export const SW_SCRIPT = "(function(){try{self[`workbox:core:7.4.0`]&&_()}catch{}let e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n};var t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}};let n=(e,t)=>t.some(t=>e instanceof t),r,i;function a(){return r||=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction]}function o(){return i||=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey]}let s=new WeakMap,c=new WeakMap,l=new WeakMap,u=new WeakMap,d=new WeakMap;function f(e){let t=new Promise((t,n)=>{let r=()=>{e.removeEventListener(`success`,i),e.removeEventListener(`error`,a)},i=()=>{t(y(e.result)),r()},a=()=>{n(e.error),r()};e.addEventListener(`success`,i),e.addEventListener(`error`,a)});return t.then(t=>{t instanceof IDBCursor&&s.set(t,e)}).catch(()=>{}),d.set(t,e),t}function p(e){if(c.has(e))return;let t=new Promise((t,n)=>{let r=()=>{e.removeEventListener(`complete`,i),e.removeEventListener(`error`,a),e.removeEventListener(`abort`,a)},i=()=>{t(),r()},a=()=>{n(e.error||new DOMException(`AbortError`,`AbortError`)),r()};e.addEventListener(`complete`,i),e.addEventListener(`error`,a),e.addEventListener(`abort`,a)});c.set(e,t)}let m={get(e,t,n){if(e instanceof IDBTransaction){if(t===`done`)return c.get(e);if(t===`objectStoreNames`)return e.objectStoreNames||l.get(e);if(t===`store`)return n.objectStoreNames[1]?void 0:n.objectStore(n.objectStoreNames[0])}return y(e[t])},set(e,t,n){return e[t]=n,!0},has(e,t){return e instanceof IDBTransaction&&(t===`done`||t===`store`)?!0:t in e}};function h(e){m=e(m)}function g(e){return e===IDBDatabase.prototype.transaction&&!(`objectStoreNames`in IDBTransaction.prototype)?function(t,...n){let r=e.call(b(this),t,...n);return l.set(r,t.sort?t.sort():[t]),y(r)}:o().includes(e)?function(...t){return e.apply(b(this),t),y(s.get(this))}:function(...t){return y(e.apply(b(this),t))}}function v(e){return typeof e==`function`?g(e):(e instanceof IDBTransaction&&p(e),n(e,a())?new Proxy(e,m):e)}function y(e){if(e instanceof IDBRequest)return f(e);if(u.has(e))return u.get(e);let t=v(e);return t!==e&&(u.set(e,t),d.set(t,e)),t}let b=e=>d.get(e);function x(e,t,{blocked:n,upgrade:r,blocking:i,terminated:a}={}){let o=indexedDB.open(e,t),s=y(o);return r&&o.addEventListener(`upgradeneeded`,e=>{r(y(o.result),e.oldVersion,e.newVersion,y(o.transaction),e)}),n&&o.addEventListener(`blocked`,e=>n(e.oldVersion,e.newVersion,e)),s.then(e=>{a&&e.addEventListener(`close`,()=>a()),i&&e.addEventListener(`versionchange`,e=>i(e.oldVersion,e.newVersion,e))}).catch(()=>{}),s}let S=[`get`,`getKey`,`getAll`,`getAllKeys`,`count`],C=[`put`,`add`,`delete`,`clear`],w=new Map;function T(e,t){if(!(e instanceof IDBDatabase&&!(t in e)&&typeof t==`string`))return;if(w.get(t))return w.get(t);let n=t.replace(/FromIndex$/,``),r=t!==n,i=C.includes(n);if(!(n in(r?IDBIndex:IDBObjectStore).prototype)||!(i||S.includes(n)))return;let a=async function(e,...t){let a=this.transaction(e,i?`readwrite`:`readonly`),o=a.store;return r&&(o=o.index(t.shift())),(await Promise.all([o[n](...t),i&&a.done]))[0]};return w.set(t,a),a}h(e=>({...e,get:(t,n,r)=>T(t,n)||e.get(t,n,r),has:(t,n)=>!!T(t,n)||e.has(t,n)}));try{self[`workbox:background-sync:7.4.0`]&&_()}catch{}let E=`requests`,D=`queueName`;var O=class{constructor(){this._db=null}async addEntry(e){let t=(await this.getDb()).transaction(E,`readwrite`,{durability:`relaxed`});await t.store.add(e),await t.done}async getFirstEntryId(){return(await(await this.getDb()).transaction(E).store.openCursor())?.value.id}async getAllEntriesByQueueName(e){return await(await this.getDb()).getAllFromIndex(E,D,IDBKeyRange.only(e))||[]}async getEntryCountByQueueName(e){return(await this.getDb()).countFromIndex(E,D,IDBKeyRange.only(e))}async deleteEntry(e){await(await this.getDb()).delete(E,e)}async getFirstEntryByQueueName(e){return await this.getEndEntryFromIndex(IDBKeyRange.only(e),`next`)}async getLastEntryByQueueName(e){return await this.getEndEntryFromIndex(IDBKeyRange.only(e),`prev`)}async getEndEntryFromIndex(e,t){return(await(await this.getDb()).transaction(E).store.index(D).openCursor(e,t))?.value}async getDb(){return this._db||=await x(`workbox-background-sync`,3,{upgrade:this._upgradeDb}),this._db}_upgradeDb(e,t){t>0&&t<3&&e.objectStoreNames.contains(E)&&e.deleteObjectStore(E),e.createObjectStore(E,{autoIncrement:!0,keyPath:`id`}).createIndex(D,D,{unique:!1})}},k=class{constructor(e){this._queueName=e,this._queueDb=new O}async pushEntry(e){delete e.id,e.queueName=this._queueName,await this._queueDb.addEntry(e)}async unshiftEntry(e){let t=await this._queueDb.getFirstEntryId();t?e.id=t-1:delete e.id,e.queueName=this._queueName,await this._queueDb.addEntry(e)}async popEntry(){return this._removeEntry(await this._queueDb.getLastEntryByQueueName(this._queueName))}async shiftEntry(){return this._removeEntry(await this._queueDb.getFirstEntryByQueueName(this._queueName))}async getAll(){return await this._queueDb.getAllEntriesByQueueName(this._queueName)}async size(){return await this._queueDb.getEntryCountByQueueName(this._queueName)}async deleteEntry(e){await this._queueDb.deleteEntry(e)}async _removeEntry(e){return e&&await this.deleteEntry(e.id),e}};let A=[`method`,`referrer`,`referrerPolicy`,`mode`,`credentials`,`cache`,`redirect`,`integrity`,`keepalive`];var j=class e{static async fromRequest(t){let n={url:t.url,headers:{}};t.method!==`GET`&&(n.body=await t.clone().arrayBuffer());for(let[e,r]of t.headers.entries())n.headers[e]=r;for(let e of A)t[e]!==void 0&&(n[e]=t[e]);return new e(n)}constructor(e){e.mode===`navigate`&&(e.mode=`same-origin`),this._requestData=e}toObject(){let e=Object.assign({},this._requestData);return e.headers=Object.assign({},this._requestData.headers),e.body&&=e.body.slice(0),e}toRequest(){return new Request(this._requestData.url,this._requestData)}clone(){return new e(this.toObject())}};let M=`workbox-background-sync`,N=new Set,P=e=>{let t={request:new j(e.requestData).toRequest(),timestamp:e.timestamp};return e.metadata&&(t.metadata=e.metadata),t};var F=class{constructor(e,{forceSyncFallback:n,onSync:r,maxRetentionTime:i}={}){if(this._syncInProgress=!1,this._requestsAddedDuringSync=!1,N.has(e))throw new t(`duplicate-queue-name`,{name:e});N.add(e),this._name=e,this._onSync=r||this.replayRequests,this._maxRetentionTime=i||10080,this._forceSyncFallback=!!n,this._queueStore=new k(this._name),this._addSyncListener()}get name(){return this._name}async pushRequest(e){await this._addRequest(e,`push`)}async unshiftRequest(e){await this._addRequest(e,`unshift`)}async popRequest(){return this._removeRequest(`pop`)}async shiftRequest(){return this._removeRequest(`shift`)}async getAll(){let e=await this._queueStore.getAll(),t=Date.now(),n=[];for(let r of e){let e=this._maxRetentionTime*60*1e3;t-r.timestamp>e?await this._queueStore.deleteEntry(r.id):n.push(P(r))}return n}async size(){return await this._queueStore.size()}async _addRequest({request:e,metadata:t,timestamp:n=Date.now()},r){let i={requestData:(await j.fromRequest(e.clone())).toObject(),timestamp:n};switch(t&&(i.metadata=t),r){case`push`:await this._queueStore.pushEntry(i);break;case`unshift`:await this._queueStore.unshiftEntry(i);break}this._syncInProgress?this._requestsAddedDuringSync=!0:await this.registerSync()}async _removeRequest(e){let t=Date.now(),n;switch(e){case`pop`:n=await this._queueStore.popEntry();break;case`shift`:n=await this._queueStore.shiftEntry();break}if(n){let r=this._maxRetentionTime*60*1e3;return t-n.timestamp>r?this._removeRequest(e):P(n)}else return}async replayRequests(){let e;for(;e=await this.shiftRequest();)try{await fetch(e.request.clone())}catch{throw await this.unshiftRequest(e),new t(`queue-replay-failed`,{name:this._name})}}async registerSync(){if(`sync`in self.registration&&!this._forceSyncFallback)try{await self.registration.sync.register(`${M}:${this._name}`)}catch{}}_addSyncListener(){`sync`in self.registration&&!this._forceSyncFallback?self.addEventListener(`sync`,e=>{e.tag===`${M}:${this._name}`&&e.waitUntil((async()=>{this._syncInProgress=!0;let t;try{await this._onSync({queue:this})}catch(e){if(e instanceof Error)throw t=e,t}finally{this._requestsAddedDuringSync&&!(t&&!e.lastChance)&&await this.registerSync(),this._syncInProgress=!1,this._requestsAddedDuringSync=!1}})())}):this._onSync({queue:this})}static get _queueNames(){return N}};let I={status:202,statusText:`Queued`};async function L(e){let t=await self.clients.matchAll({includeUncontrolled:!1});for(let n of t)n.postMessage(e)}async function R(e){let t=await e.getAll(),n=0;for(let e of t){let t=e.metadata;typeof t?.bytes==`number`&&(n+=t.bytes)}return n}async function z(e,t){let n=await R(e);for(;n+t>10485760;){let t=await e.shiftRequest();if(!t)break;let r=t.metadata;n-=r?.bytes??0,await L({type:`interfere.sw.dropped`,url:t.request.url,reason:`queue_size_cap`})}}async function B(e,t){let n=t.clone(),r=(await n.clone().arrayBuffer()).byteLength;await z(e,r),await e.pushRequest({request:n,metadata:{bytes:r}}),await L({type:`interfere.sw.queued`,url:t.url})}function V(e={}){let t=e.match??(e=>e.pathname.startsWith(`/api/interfere/`)),n=new F(e.queueName??`interfere-ingest`,{maxRetentionTime:1440,onSync:async({queue:e})=>{let t=await e.shiftRequest();for(;t;){try{let e=await self.fetch(t.request.clone());if(!e.ok&&(e.status>=500||e.status===429))throw Error(`retry status ${e.status}`);await L({type:`interfere.sw.replayed`,url:t.request.url})}catch(n){throw await e.unshiftRequest(t),n}t=await e.shiftRequest()}}});self.addEventListener(`activate`,e=>{e.waitUntil(n.replayRequests().catch(()=>void 0))}),self.addEventListener(`fetch`,e=>{let{request:r}=e;if(r.method!==`POST`)return;let i;try{i=new URL(r.url)}catch{return}t(i,r)&&e.respondWith((async()=>{try{let e=await self.fetch(r.clone());return e.ok?e:e.status>=500||e.status===429?(await B(n,r),new Response(null,I)):e}catch{return await B(n,r),new Response(null,I)}})())})}self.addEventListener(`install`,()=>{self.skipWaiting()}),self.addEventListener(`activate`,e=>{e.waitUntil(self.clients.claim())}),V({match:e=>e.pathname.startsWith(`/api/interfere/`)})})();";