@interfere/react 11.0.5 → 11.1.1
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.
- package/dist/api.mjs +1 -1
- package/dist/internal/activity.d.mts +3 -5
- package/dist/internal/activity.mjs +1 -1
- package/dist/internal/browser-context.mjs +1 -1
- package/dist/internal/config.mjs +1 -1
- package/dist/internal/console-patch.mjs +1 -1
- package/dist/internal/dom/actionable.mjs +1 -1
- package/dist/internal/hydration.mjs +1 -1
- package/dist/internal/kernel.d.mts +19 -12
- package/dist/internal/kernel.mjs +1 -1
- package/dist/internal/otel/exporter-logs.d.mts +13 -0
- package/dist/internal/otel/exporter-logs.mjs +1 -0
- package/dist/internal/otel/exporter-metrics.d.mts +14 -0
- package/dist/internal/otel/exporter-metrics.mjs +1 -0
- package/dist/internal/otel/exporter.d.mts +3 -18
- package/dist/internal/otel/exporter.mjs +1 -1
- package/dist/internal/otel/initial-navigation.d.mts +3 -3
- package/dist/internal/otel/instrumentations.mjs +1 -1
- package/dist/internal/otel/provider.d.mts +2 -2
- package/dist/internal/otel/provider.mjs +1 -1
- package/dist/internal/otel/web-vitals.mjs +1 -1
- package/dist/internal/plugin-runtime.mjs +1 -1
- package/dist/internal/react-context.d.mts +9 -9
- package/dist/internal/react-context.mjs +1 -1
- package/dist/internal/sw-queue.mjs +1 -1
- package/dist/internal/third-party-hosts.mjs +1 -1
- package/dist/internal/wrapper-singleton.d.mts +10 -10
- package/dist/internal/wrapper-singleton.mjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/plugins/lib/types.d.mts +9 -9
- package/dist/plugins/rage-clicks.mjs +1 -1
- package/dist/plugins/replay/buffer.d.mts +2 -2
- package/dist/plugins/replay/chunking.mjs +1 -1
- package/dist/plugins/replay/compressor-host.mjs +1 -1
- package/dist/plugins/replay/index.mjs +1 -1
- package/dist/plugins/replay/recorder.d.mts +2 -2
- package/dist/plugins/replay/sequence.d.mts +1 -1
- package/dist/provider.mjs +1 -1
- package/dist/tracking/api.d.mts +1 -0
- package/dist/tracking/api.mjs +1 -1
- package/dist/tracking/device.mjs +1 -1
- package/dist/tracking/geo.d.mts +1 -0
- package/dist/tracking/geo.mjs +1 -1
- package/dist/util/log.d.mts +4 -4
- package/package.json +34 -34
package/dist/api.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{activeKernel}from"./internal/kernel-registry.mjs";import{MECHANISM_TYPE,toError,toException}from"@interfere/types/sdk/errors";import{SpanStatusCode,trace}from"@opentelemetry/api";function tracer(){return trace.getTracer(`@interfere/react`)}function isPromise(value){return!!value&&(typeof value==`object`||typeof value==`function`)&&typeof value.then==`function`}function span(name,fn,options){return tracer().startActiveSpan(name,options??{},s=>{try{let result=fn(s);return isPromise(result)?result.then(value=>(s.end(),value),
|
|
1
|
+
import{activeKernel}from"./internal/kernel-registry.mjs";import{MECHANISM_TYPE,toError,toException}from"@interfere/types/sdk/errors";import{SpanStatusCode,trace}from"@opentelemetry/api";function tracer(){return trace.getTracer(`@interfere/react`)}function isPromise(value){return!!value&&(typeof value==`object`||typeof value==`function`)&&typeof value.then==`function`}function span(name,fn,options){return tracer().startActiveSpan(name,options??{},s=>{try{let result=fn(s);return isPromise(result)?result.then(value=>(s.end(),value),error=>{let e=toError(error);throw s.recordException(e),s.setStatus({code:SpanStatusCode.ERROR,message:e.message}),s.end(),error}):(s.end(),result)}catch(error){let e=toError(error);throw s.recordException(e),s.setStatus({code:SpanStatusCode.ERROR,message:e.message}),s.end(),error}})}function capture(error){activeKernel()?.recordException(toException(error),{mechanism:{type:MECHANISM_TYPE.manual.capture,handled:!0}})}export{capture,span};
|
|
@@ -5,10 +5,10 @@ declare const MAX_AGE_MS: number;
|
|
|
5
5
|
declare const CHECK_INTERVAL_MS: number;
|
|
6
6
|
declare const KEEPALIVE_THROTTLE_MS = 1000;
|
|
7
7
|
interface ActivitySession {
|
|
8
|
-
markActivity()
|
|
9
|
-
reset()
|
|
8
|
+
markActivity: () => void;
|
|
9
|
+
reset: () => void;
|
|
10
10
|
/** Wall-clock epoch ms the session began; `0` when unknown. */
|
|
11
|
-
startedAt()
|
|
11
|
+
startedAt: () => number;
|
|
12
12
|
}
|
|
13
13
|
type SessionResumeReason = "idle" | "rotated";
|
|
14
14
|
type Handler<A> = (arg: A) => void;
|
|
@@ -31,8 +31,6 @@ declare class ActivityMonitor {
|
|
|
31
31
|
private readonly onInput;
|
|
32
32
|
private resume;
|
|
33
33
|
private touch;
|
|
34
|
-
private subscribe;
|
|
35
|
-
private fire;
|
|
36
34
|
}
|
|
37
35
|
//#endregion
|
|
38
36
|
export { ActivityMonitor, ActivitySession, CHECK_INTERVAL_MS, EXPIRE_AFTER_MS, IDLE_AFTER_MS, KEEPALIVE_THROTTLE_MS, MAX_AGE_MS, SessionResumeReason };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"../util/log.mjs";import{onPageShown}from"./page-lifecycle.mjs";const log=createLogger(`activity`),IDLE_AFTER_MS=
|
|
1
|
+
import{createLogger}from"../util/log.mjs";import{onPageShown}from"./page-lifecycle.mjs";const log=createLogger(`activity`),IDLE_AFTER_MS=3e5,EXPIRE_AFTER_MS=9e5,MAX_AGE_MS=864e5,CHECK_INTERVAL_MS=3e4,KEEPALIVE_THROTTLE_MS=1e3,ACTIVITY_EVENTS=[`pointerdown`,`keydown`,`scroll`,`touchstart`,`mousemove`];function monotonicNow(){return typeof performance>`u`?Date.now():performance.now()}function subscribe(set,handler){return set.add(handler),()=>{set.delete(handler)}}function fire(handlers,arg){for(let handler of handlers)try{handler(arg)}catch(error){log.warn(`activity handler threw`,error)}}var ActivityMonitor=class{session=null;paused=!1;lastActivityMs=0;keepaliveUntilMs=0;checkTimer=null;unsubscribeShown=null;pauseHandlers=new Set;resumeHandlers=new Set;onPause(handler){return subscribe(this.pauseHandlers,handler)}onResume(handler){return subscribe(this.resumeHandlers,handler)}start(session){if(this.checkTimer===null&&typeof globalThis.addEventListener==`function`){this.session=session,this.paused=!1,this.lastActivityMs=monotonicNow(),this.keepaliveUntilMs=0;for(let type of ACTIVITY_EVENTS)globalThis.addEventListener(type,this.onInput,{capture:!0,passive:!0});this.unsubscribeShown=onPageShown(this.onInput),this.checkTimer=setInterval(this.poll,CHECK_INTERVAL_MS)}}stop(){if(this.checkTimer!==null){clearInterval(this.checkTimer),this.checkTimer=null;for(let type of ACTIVITY_EVENTS)globalThis.removeEventListener(type,this.onInput,!0);this.unsubscribeShown?.(),this.unsubscribeShown=null,this.session=null,this.paused=!1,this.lastActivityMs=0,this.keepaliveUntilMs=0}}idleMs(){return monotonicNow()-this.lastActivityMs}isOverMaxAge(){let startedAt=this.session?.startedAt()??0;return startedAt>0&&Date.now()-startedAt>=864e5}poll=()=>{this.paused||(this.isOverMaxAge()||this.idleMs()>=3e5)&&(this.paused=!0,log.debug(`session paused`),fire(this.pauseHandlers))};onInput=()=>{let now=monotonicNow();if(this.isOverMaxAge()||now-this.lastActivityMs>=9e5){this.resume(now,`rotated`);return}if(this.paused){this.resume(now,`idle`);return}now<this.keepaliveUntilMs||(this.touch(now),this.session?.markActivity())};resume(now,reason){reason===`rotated`&&(log.debug(`resume after expiry → new session`),this.session?.reset()),this.paused=!1,this.touch(now),this.session?.markActivity(),fire(this.resumeHandlers,reason)}touch(now){this.lastActivityMs=now,this.keepaliveUntilMs=now+KEEPALIVE_THROTTLE_MS}};export{ActivityMonitor,CHECK_INTERVAL_MS,EXPIRE_AFTER_MS,IDLE_AFTER_MS,KEEPALIVE_THROTTLE_MS,MAX_AGE_MS};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{UAParser}from"@ua-parser-js/pro-enterprise";import{InApps}from"@ua-parser-js/pro-enterprise/extensions";import{isFrozenUA}from"@ua-parser-js/pro-enterprise/helpers";const NOT_A_BRAND_REGEX=/not.a.brand/
|
|
1
|
+
import{UAParser}from"@ua-parser-js/pro-enterprise";import{InApps}from"@ua-parser-js/pro-enterprise/extensions";import{isFrozenUA}from"@ua-parser-js/pro-enterprise/helpers";const NOT_A_BRAND_REGEX=/not.a.brand/iu,HIGH_ENTROPY_HINTS=[`bitness`,`platformVersion`,`fullVersionList`];async function getDeviceMetadata(){if(typeof navigator>`u`)return null;let uaData=navigator.userAgentData,[parsed,hints]=await Promise.all([UAParser(navigator.userAgent,InApps).withClientHints(),uaData?.getHighEntropyValues?uaData.getHighEntropyValues(HIGH_ENTROPY_HINTS).catch(()=>({})):Promise.resolve({})]);if(!parsed)return null;let fullVersion=hints.fullVersionList?.find(b=>!NOT_A_BRAND_REGEX.test(b.brand))?.version;return{...parsed,browser:{...parsed.browser,...fullVersion?{fullVersion}:{}},cpu:{...parsed.cpu,...hints.bitness?{bitness:hints.bitness}:{}},os:{...parsed.os,...hints.platformVersion?{versionFull:hints.platformVersion}:{}},frozenUa:isFrozenUA(navigator.userAgent)}}function getBrowserMetadata(){if([typeof navigator,typeof screen,typeof globalThis].some(x=>x===`undefined`))return null;let{language}=navigator;return{language,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,display:{screen:{height:screen.availHeight,width:screen.availWidth,orientation:screen.orientation?.type}}}}async function collectContext(){return{runtime:`browser`,browser:getBrowserMetadata(),device:await getDeviceMetadata()}}export{collectContext};
|
package/dist/internal/config.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getGlobal,setGlobal}from"../util/global.mjs";import{API_PATHS,API_URL}from"@interfere/constants/api";import{API_URL_GLOBAL,FORCE_ENABLE_GLOBAL,INGEST_URL_GLOBAL,PUBLIC_KEY_GLOBAL}from"@interfere/types/sdk/injected-globals";const TRAILING_SLASH_RE
|
|
1
|
+
import{getGlobal,setGlobal}from"../util/global.mjs";import{API_PATHS,API_URL}from"@interfere/constants/api";import{API_URL_GLOBAL,FORCE_ENABLE_GLOBAL,INGEST_URL_GLOBAL,PUBLIC_KEY_GLOBAL}from"@interfere/types/sdk/injected-globals";const TRAILING_SLASH_RE=/\/$/u,RELATIVE_URL_BASE=`https://interfere.local`;function resolvePublicKey(){let injected=getGlobal(PUBLIC_KEY_GLOBAL);if(injected)return injected;if(typeof process<`u`)return process.env.INTERFERE_PUBLIC_KEY??void 0}function isForceEnableActive(){return!!getGlobal(FORCE_ENABLE_GLOBAL)}function resolveApiHost(explicit){return explicit??getGlobal(API_URL_GLOBAL)}function resolveIngestHost(explicit){return explicit??getGlobal(INGEST_URL_GLOBAL)}function setIngestHost(host){setGlobal(INGEST_URL_GLOBAL,host)}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,ingestHost){let publicKey=resolvePublicKey(),headers=new Headers({"content-type":`application/json`});isForceEnableActive()&&headers.set(`x-interfere-force-enable`,`1`);let configHost=resolveApiHost(apiHost),configBaseUrl=configHost?normalizeOrigin(configHost):API_URL,resolvedIngestHost=resolveIngestHost(ingestHost),ingestBaseUrl=resolvedIngestHost?normalizeOrigin(resolvedIngestHost):configBaseUrl;return{collectorBaseUrl:appendPublicKey(ingestBaseUrl,publicKey),config:{url:appendPublicKey(`${configBaseUrl}${API_PATHS.CONFIG}`,publicKey),headers},ingest:{headers},session:{url:appendPublicKey(`${ingestBaseUrl}${API_PATHS.SESSION}`,publicKey),headers}}}export{appendPathBeforeQuery,resolveTargets,setIngestHost,withQueryParam};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const CONSOLE_LEVELS=[`debug`,`log`,`info`,`warn`,`error`],handlers=new Set,originals=
|
|
1
|
+
const CONSOLE_LEVELS=[`debug`,`log`,`info`,`warn`,`error`],handlers=new Set,originals=new Map;let installed=!1;function install(){if(installed)return;installed=!0;let target=globalThis.console;for(let level of CONSOLE_LEVELS){let orig=target[level];typeof orig==`function`&&(originals.set(level,orig),target[level]=(...args)=>{orig.apply(globalThis.console,args);for(let handler of handlers)handler(level,args)})}}function uninstall(){if(!installed)return;installed=!1;let target=globalThis.console;for(let level of CONSOLE_LEVELS){let orig=originals.get(level);orig&&(target[level]=orig,originals.delete(level))}}function onConsoleCall(handler){return install(),handlers.add(handler),()=>{handlers.delete(handler),handlers.size===0&&uninstall()}}function _resetConsolePatchForTests(){handlers.clear(),uninstall()}export{CONSOLE_LEVELS,_resetConsolePatchForTests,onConsoleCall};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const ACTIONABLE_TAGS=new Set([`a`,`button`,`input`,`select`,`textarea`,`summary`,`label`]),ACTIONABLE_ROLES=new Set([`button`,`link`,`menuitem`,`tab`,`checkbox`,`radio`,`switch`]);function isActionable(el){if(ACTIONABLE_TAGS.has(el.tagName.toLowerCase()))return!0;let role=el.getAttribute(`role`);return role&&ACTIONABLE_ROLES.has(role)?!0:el.
|
|
1
|
+
const ACTIONABLE_TAGS=new Set([`a`,`button`,`input`,`select`,`textarea`,`summary`,`label`]),ACTIONABLE_ROLES=new Set([`button`,`link`,`menuitem`,`tab`,`checkbox`,`radio`,`switch`]);function isActionable(el){if(ACTIONABLE_TAGS.has(el.tagName.toLowerCase()))return!0;let role=el.getAttribute(`role`);return role&&ACTIONABLE_ROLES.has(role)?!0:(el instanceof HTMLElement||el instanceof SVGElement)&&Object.hasOwn(el.dataset,`trackClick`)}function closestActionable(target){if(!(target instanceof Element))return null;let el=target;for(;el;){if(isActionable(el))return el;el=el.parentElement}return null}function describeActionable(el){let desc={tag:el.tagName.toLowerCase()};el.id&&(desc.id=el.id);let role=el.getAttribute(`role`);role&&(desc.role=role);let name=el.getAttribute(`name`);name&&(desc.name=name);let ariaLabel=el.getAttribute(`aria-label`);ariaLabel&&(desc.ariaLabel=ariaLabel),el instanceof HTMLAnchorElement&&(desc.href=el.href);let text=el.textContent?.trim().slice(0,120);return text&&(desc.text=text),desc}export{closestActionable,describeActionable,isActionable};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const HYDRATION_CODE=/(?:Minified React error #|react\.dev\/errors\/)(?:418|423|425)
|
|
1
|
+
const HYDRATION_CODE=/(?:Minified React error #|react\.dev\/errors\/)(?:418|423|425)/u,HYDRATION_TEXT=/hydration failed|error while hydrating|hydrated but|text content does not match|server[- ]rendered html|did(?:n't| not) match the client/iu;function isHydrationError(error){if(!(error instanceof Error))return!1;let{message}=error;return HYDRATION_CODE.test(message)||HYDRATION_TEXT.test(message)}export{isHydrationError};
|
|
@@ -109,11 +109,12 @@ interface TracingOptions {
|
|
|
109
109
|
webVitals?: boolean;
|
|
110
110
|
}
|
|
111
111
|
/**
|
|
112
|
-
* @internal
|
|
113
112
|
* Framework wrappers (`@interfere/next`, `@interfere/vite`) pass extra
|
|
114
113
|
* fields through `createKernel` that customers never set themselves.
|
|
115
114
|
* Kept off the customer-facing `KernelOptions` to make the public surface
|
|
116
115
|
* clean.
|
|
116
|
+
*
|
|
117
|
+
* @internal
|
|
117
118
|
*/
|
|
118
119
|
interface KernelInternalOptions extends KernelOptions {
|
|
119
120
|
/** @internal */
|
|
@@ -126,21 +127,21 @@ interface KernelInternalOptions extends KernelOptions {
|
|
|
126
127
|
_wrapperVersions?: string[];
|
|
127
128
|
}
|
|
128
129
|
interface KernelConsent {
|
|
129
|
-
get()
|
|
130
|
-
set(value?: ConsentState)
|
|
130
|
+
get: () => ConsentState | null;
|
|
131
|
+
set: (value?: ConsentState) => void;
|
|
131
132
|
}
|
|
132
133
|
interface KernelIdentity {
|
|
133
|
-
clear()
|
|
134
|
-
get()
|
|
135
|
-
set(params: IdentifyParams)
|
|
134
|
+
clear: () => void;
|
|
135
|
+
get: () => IdentifyParams | null;
|
|
136
|
+
set: (params: IdentifyParams) => Promise<void>;
|
|
136
137
|
}
|
|
137
138
|
interface KernelDevice {
|
|
138
|
-
getDeviceId()
|
|
139
|
-
getFpHash()
|
|
139
|
+
getDeviceId: () => string | null;
|
|
140
|
+
getFpHash: () => string | null;
|
|
140
141
|
}
|
|
141
142
|
interface KernelSession {
|
|
142
|
-
getId()
|
|
143
|
-
getWindowId()
|
|
143
|
+
getId: () => SessionId | null;
|
|
144
|
+
getWindowId: () => string | null;
|
|
144
145
|
}
|
|
145
146
|
interface RecordExceptionOpts {
|
|
146
147
|
/**
|
|
@@ -197,7 +198,11 @@ declare class Kernel implements PluginContext {
|
|
|
197
198
|
private otelDispose;
|
|
198
199
|
private unsubscribePageHidden;
|
|
199
200
|
constructor(tracker: SessionTracker);
|
|
200
|
-
/**
|
|
201
|
+
/**
|
|
202
|
+
* Bound by `createKernel` immediately after construction.
|
|
203
|
+
*
|
|
204
|
+
* @internal
|
|
205
|
+
*/
|
|
201
206
|
attachRuntime(runtime: PluginRuntime): void;
|
|
202
207
|
private requireRuntime;
|
|
203
208
|
/** PluginContext shim: plugins expect a non-null string. */
|
|
@@ -252,11 +257,13 @@ declare class Kernel implements PluginContext {
|
|
|
252
257
|
*/
|
|
253
258
|
flush(): Promise<void>;
|
|
254
259
|
/**
|
|
255
|
-
*
|
|
260
|
+
* Wired by `createKernel` after the OTel module has been
|
|
256
261
|
* lazy-loaded. Held on the kernel so `flush()` and `dispose()` can fan
|
|
257
262
|
* out to the providers. The tracer/meter providers stay private —
|
|
258
263
|
* customers don't get raw OTel access; the public surface is the
|
|
259
264
|
* `span()` / `capture()` helpers in `api.ts`.
|
|
265
|
+
*
|
|
266
|
+
* @internal
|
|
260
267
|
*/
|
|
261
268
|
attachOtel(handle: OtelProviderHandle, dispose: () => void): void;
|
|
262
269
|
syncProviderConfig(config: {
|
package/dist/internal/kernel.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"../util/log.mjs";import{onPageHidden}from"./page-lifecycle.mjs";import{getGlobal}from"../util/global.mjs";import{resolveTargets,setIngestHost}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{FORCE_ENABLE_GLOBAL,RELEASE_SLUG_GLOBAL,SDK_STACK_GLOBAL}from"@interfere/types/sdk/injected-globals";import{releaseSlugSchema}from"@interfere/types/releases/slug";import{readBakedPlugins}from"@interfere/types/sdk/remote-config";const log=createLogger(`kernel`),TRAILING_SLASH_RE
|
|
1
|
+
import{createLogger}from"../util/log.mjs";import{onPageHidden}from"./page-lifecycle.mjs";import{getGlobal}from"../util/global.mjs";import{resolveTargets,setIngestHost}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{FORCE_ENABLE_GLOBAL,RELEASE_SLUG_GLOBAL,SDK_STACK_GLOBAL}from"@interfere/types/sdk/injected-globals";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{releaseSlugSchema}from"@interfere/types/releases/slug";import{readBakedPlugins}from"@interfere/types/sdk/remote-config";const log=createLogger(`kernel`),TRAILING_SLASH_RE=/\/$/u,REPLAY_UPLOAD_RE=/\/v2\/replay\/upload\//u;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(()=>{})})}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()??``}getWindowId(){return this.tracker.windowId()??``}onSessionPause(handler){return this.tracker.onSessionPause(handler)}onSessionResume(handler){return this.tracker.onSessionResume(handler)}getPluginConfigState(key){return this.requireRuntime().getPluginConfigState(key)}getReplayConfig(){return this.requireRuntime().getReplayConfig()}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}syncProviderConfig(config){this.requireRuntime().applyProviderConfig(config.plugins,config.consent)}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),remotePlugins=await resolveRemotePlugins(opts,fetcher),targets=resolveTargets(opts.apiHost);log.info(`collector: %s`,targets.collectorBaseUrl),getGlobal(FORCE_ENABLE_GLOBAL)&&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[SDK_STACK_GLOBAL]=opts._wrapperVersions);let kernel=new Kernel(tracker),runtime=new PluginRuntime(kernel);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 remotePlugins?runtime.applyRemoteConfig(remotePlugins):runtime.applyRemoteConfigDefaults(),kernel}async function resolveRemotePlugins(opts,fetcher){let baked=readBakedPlugins();if(baked)return baked;let remoteConfig=await fetchRemoteConfig(resolveTargets(opts.apiHost).config,fetcher);return remoteConfig?.ingestUrl&&setIngestHost(remoteConfig.ingestUrl),remoteConfig?.plugins??null}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(),getDeviceId:()=>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(omitUndefined({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}:{},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){try{let headers=Object.fromEntries(configTarget.headers.entries()),res=await fetcher(configTarget.url,{method:`GET`,headers,credentials:`omit`,signal:AbortSignal.timeout(1e4)});return res.ok?await res.json():null}catch{return log.warn(`remote config fetch failed, using local defaults`),null}}function isEnabledByEnvironment(){if(typeof process>`u`||!process.env)return!0;let nodeEnv=process.env.NODE_ENV;return nodeEnv===`production`||nodeEnv===void 0||!!(getGlobal(FORCE_ENABLE_GLOBAL)||process.env.INTERFERE_FORCE_ENABLE)}function readReleaseSlugFromGlobals(){let value=getGlobal(RELEASE_SLUG_GLOBAL);return typeof value==`string`?releaseSlugSchema.parse(value):null}export{Kernel,buildSdkStack,createKernel,isEnabledByEnvironment,readReleaseSlugFromGlobals};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SignalSender } from "./exporter.mjs";
|
|
2
|
+
import { ExportResult } from "@opentelemetry/core";
|
|
3
|
+
import { LogRecordExporter, ReadableLogRecord } from "@opentelemetry/sdk-logs";
|
|
4
|
+
//#region src/internal/otel/exporter-logs.d.ts
|
|
5
|
+
declare class IngestLogExporter implements LogRecordExporter {
|
|
6
|
+
private readonly send;
|
|
7
|
+
constructor(send: SignalSender);
|
|
8
|
+
export(logs: ReadableLogRecord[], resultCallback: (result: ExportResult) => void): void;
|
|
9
|
+
shutdown(): Promise<void>;
|
|
10
|
+
forceFlush(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { IngestLogExporter };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ExportResultCode}from"@opentelemetry/core";import{JsonLogsSerializer}from"@opentelemetry/otlp-transformer";var 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()}};export{IngestLogExporter};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SignalSender } from "./exporter.mjs";
|
|
2
|
+
import { ExportResult } from "@opentelemetry/core";
|
|
3
|
+
import { AggregationTemporality, PushMetricExporter, ResourceMetrics } from "@opentelemetry/sdk-metrics";
|
|
4
|
+
//#region src/internal/otel/exporter-metrics.d.ts
|
|
5
|
+
declare class IngestMetricExporter implements PushMetricExporter {
|
|
6
|
+
private readonly send;
|
|
7
|
+
constructor(send: SignalSender);
|
|
8
|
+
export(metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void): void;
|
|
9
|
+
selectAggregationTemporality(): AggregationTemporality;
|
|
10
|
+
forceFlush(): Promise<void>;
|
|
11
|
+
shutdown(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { IngestMetricExporter };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{JsonMetricsSerializer}from"@opentelemetry/otlp-transformer";import{AggregationTemporality}from"@opentelemetry/sdk-metrics";var 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()}};export{IngestMetricExporter};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { IngestMetricExporter } from "./exporter-metrics.mjs";
|
|
2
|
+
import { IngestLogExporter } from "./exporter-logs.mjs";
|
|
1
3
|
import { ExportResult } from "@opentelemetry/core";
|
|
2
|
-
import { AggregationTemporality, PushMetricExporter, ResourceMetrics } from "@opentelemetry/sdk-metrics";
|
|
3
|
-
import { ReadableLogRecord } from "@opentelemetry/sdk-logs";
|
|
4
4
|
import { SpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
5
5
|
//#region src/internal/otel/exporter.d.ts
|
|
6
6
|
type SignalSender = (payload: Uint8Array | undefined, resultCallback: (result: ExportResult) => void) => void;
|
|
@@ -22,23 +22,8 @@ declare function buildSinkUrl(collectorUrl: string): string;
|
|
|
22
22
|
* Exported for direct unit testing.
|
|
23
23
|
*/
|
|
24
24
|
declare function buildIngestUrl(collectorUrl: string): string;
|
|
25
|
-
declare class IngestLogExporter {
|
|
26
|
-
private readonly send;
|
|
27
|
-
constructor(send: SignalSender);
|
|
28
|
-
export(logs: ReadableLogRecord[], resultCallback: (result: ExportResult) => void): void;
|
|
29
|
-
shutdown(): Promise<void>;
|
|
30
|
-
forceFlush(): Promise<void>;
|
|
31
|
-
}
|
|
32
|
-
declare class IngestMetricExporter implements PushMetricExporter {
|
|
33
|
-
private readonly send;
|
|
34
|
-
constructor(send: SignalSender);
|
|
35
|
-
export(metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void): void;
|
|
36
|
-
selectAggregationTemporality(): AggregationTemporality;
|
|
37
|
-
forceFlush(): Promise<void>;
|
|
38
|
-
shutdown(): Promise<void>;
|
|
39
|
-
}
|
|
40
25
|
declare function createTraceExporter(input: ExporterCommon): SpanExporter;
|
|
41
26
|
declare function createMetricExporter(input: ExporterCommon): IngestMetricExporter;
|
|
42
27
|
declare function createLogExporter(input: ExporterCommon): IngestLogExporter;
|
|
43
28
|
//#endregion
|
|
44
|
-
export { buildIngestUrl, buildSinkUrl, createLogExporter, createMetricExporter, createTraceExporter };
|
|
29
|
+
export { SignalSender, buildIngestUrl, buildSinkUrl, createLogExporter, createMetricExporter, createTraceExporter };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{appendPathBeforeQuery,withQueryParam}from"../config.mjs";import{PRODUCER_VERSION}from"../version.mjs";import{
|
|
1
|
+
import{appendPathBeforeQuery,withQueryParam}from"../config.mjs";import{PRODUCER_VERSION}from"../version.mjs";import{IngestLogExporter}from"./exporter-logs.mjs";import{IngestMetricExporter}from"./exporter-metrics.mjs";import{ExportResultCode}from"@opentelemetry/core";import{JsonTraceSerializer}from"@opentelemetry/otlp-transformer";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(()=>{}),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()}};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,9 +1,9 @@
|
|
|
1
1
|
import { LoggerProvider } from "@opentelemetry/sdk-logs";
|
|
2
2
|
//#region src/internal/otel/initial-navigation.d.ts
|
|
3
3
|
interface InitialNavigationInput {
|
|
4
|
-
getLocationHref
|
|
5
|
-
getSessionId()
|
|
6
|
-
getWindowId()
|
|
4
|
+
getLocationHref?: () => string | undefined;
|
|
5
|
+
getSessionId: () => string | null;
|
|
6
|
+
getWindowId: () => string | null;
|
|
7
7
|
loggerProvider: LoggerProvider;
|
|
8
8
|
seen: Set<string>;
|
|
9
9
|
}
|
|
@@ -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/
|
|
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/iu,NETWORK_ABORT_RE=/abort/iu;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;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;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,NEVER_MATCH=/^$/u,ESCAPE_RE=/[.*+?^${}()|[\]\\]/gu;function sameOriginPattern(){if(typeof window>`u`)return NEVER_MATCH;let{protocol,host}=window.location;return RegExp(`^${protocol}//${host.replace(ESCAPE_RE,`\\$&`)}`,`u`)}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,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.replaceAll(/[.*+?^${}()|[\]\\]/gu,`\\$&`)}function apiHostToMatcher(host){return RegExp(`^https?://${escapeRegExp(host)}(:\\d+)?([/?#]|$)`,`u`)}function registerBundledInstrumentations(input){let{tracerProvider,loggerProvider,ignoreUrls,apiHosts=[],propagateContextUrls=[]}=input,{resolveRoute}=input,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,measureRequestSize:!0,applyCustomAttributesOnSpan:(span,request,result)=>{applyFetchSpanAttrs(span,request,result,failureRanges,isTrackedUrl,resolveRoute,failureGate)}}),new XMLHttpRequestInstrumentation({propagateTraceHeaderCorsUrls:[origin,...propagateMatchers],ignoreUrls:fetchIgnore,applyCustomAttributesOnSpan:(span,xhr)=>{applyXhrSpanAttrs(span,xhr,failureRanges,isTrackedUrl,failureGate)}}),new DocumentLoadInstrumentation({applyCustomAttributesOnSpan:{resourceFetch:(span,entry)=>{let name=entry?.name;if(typeof name!=`string`)return;let url;try{url=new URL(name)}catch{return}let 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)}}}),new BrowserNavigationInstrumentation({sanitizeUrl:defaultSanitizeUrl}),new UserInteractionInstrumentation({eventNames:[`click`,`submit`],shouldPreventSpanCreation:(eventType,element,span)=>eventType===`click`&&!isActionable(element)||(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};
|
|
@@ -63,12 +63,12 @@ interface OtelProviderHandle {
|
|
|
63
63
|
*/
|
|
64
64
|
contextManager: PageScopeContextManager;
|
|
65
65
|
/** Force-flush all three providers — call from kernel.flush() / on visibility hidden. */
|
|
66
|
-
flush()
|
|
66
|
+
flush: () => Promise<void>;
|
|
67
67
|
loggerProvider: LoggerProvider;
|
|
68
68
|
meterProvider: MeterProvider;
|
|
69
69
|
metricReader: MetricReader;
|
|
70
70
|
/** Tear down all three providers — called from kernel.dispose(). */
|
|
71
|
-
shutdown()
|
|
71
|
+
shutdown: () => Promise<void>;
|
|
72
72
|
tracerProvider: WebTracerProvider;
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
@@ -1 +1 @@
|
|
|
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{context,propagation}from"@opentelemetry/api";import{ENVIRONMENT_GLOBAL}from"@interfere/types/sdk/injected-globals";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 ATTR_DEVICE_ID=`device.id`,BROWSER_BATCH_OPTIONS={maxQueueSize:50,maxExportBatchSize:10,scheduledDelayMillis:2e3,exportTimeoutMillis:1e4};function createDeviceSpanProcessor(opts){return{onStart(span){let id=opts.getDeviceId();id&&span.setAttribute(ATTR_DEVICE_ID,id)},onEnd(){},forceFlush:()=>Promise.resolve(),shutdown:()=>Promise.resolve()}}function createDeviceLogRecordProcessor(opts){return{onEmit(logRecord){let id=opts.getDeviceId();id&&logRecord.setAttribute(ATTR_DEVICE_ID,id)},forceFlush:()=>Promise.resolve(),shutdown:()=>Promise.resolve()}}function buildOtelProvider(input){let environment=resolveEnvironment({explicit:getGlobal(ENVIRONMENT_GLOBAL),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}:{}}),detected=detectResources({detectors:[browserDetector]}),resource=baseResource.merge(detected),traceExporter=createTraceExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),getSessionId=input
|
|
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{context,propagation}from"@opentelemetry/api";import{ENVIRONMENT_GLOBAL}from"@interfere/types/sdk/injected-globals";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 ATTR_DEVICE_ID=`device.id`,BROWSER_BATCH_OPTIONS={maxQueueSize:50,maxExportBatchSize:10,scheduledDelayMillis:2e3,exportTimeoutMillis:1e4};function createDeviceSpanProcessor(opts){return{onStart(span){let id=opts.getDeviceId();id&&span.setAttribute(ATTR_DEVICE_ID,id)},onEnd(){},forceFlush:()=>Promise.resolve(),shutdown:()=>Promise.resolve()}}function createDeviceLogRecordProcessor(opts){return{onEmit(logRecord){let id=opts.getDeviceId();id&&logRecord.setAttribute(ATTR_DEVICE_ID,id)},forceFlush:()=>Promise.resolve(),shutdown:()=>Promise.resolve()}}function buildOtelProvider(input){let environment=resolveEnvironment({explicit:getGlobal(ENVIRONMENT_GLOBAL),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}:{}}),detected=detectResources({detectors:[browserDetector]}),resource=baseResource.merge(detected),traceExporter=createTraceExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),{getSessionId}=input,{getDeviceId}=input,spanProcessors=[createSessionSpanProcessor({getSessionId}),createDeviceSpanProcessor({getDeviceId}),new BatchSpanProcessor(traceExporter,BROWSER_BATCH_OPTIONS),...input.additionalSpanProcessors??[]],tracerProvider=new WebTracerProvider({resource,spanProcessors}),contextManager=new PageScopeContextManager;contextManager.enable(),context.setGlobalContextManager(contextManager)||contextManager.disable(),propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[new W3CTraceContextPropagator,new W3CBaggagePropagator]}));let metricExporter=createMetricExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),metricReader=new PeriodicExportingMetricReader({exporter:metricExporter,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}),createDeviceLogRecordProcessor({getDeviceId}),new BatchLogRecordProcessor({exporter: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 +1 @@
|
|
|
1
|
-
import{onCLS,onFCP,onINP,onLCP,onTTFB}from"web-vitals";const LOAD_VITALS=new Set([`TTFB`,`FCP`,`LCP`]);function captureWebVitals(input){if(typeof window>`u`)return;let{meter,resolveRoute,flush}=input,lcp=meter.createHistogram(`web_vitals.lcp`,{description:`Largest Contentful Paint`,unit:`s`,advice:{explicitBucketBoundaries:[.5,1,1.5,2,2.5,3,4,5,7.5,10]}}),fcp=meter.createHistogram(`web_vitals.fcp`,{description:`First Contentful Paint`,unit:`s`,advice:{explicitBucketBoundaries:[.5,1,1.5,1.8,2,2.5,3,4,5,7.5]}}),ttfb=meter.createHistogram(`web_vitals.ttfb`,{description:`Time to First Byte`,unit:`s`,advice:{explicitBucketBoundaries:[.1,.2,.3,.5,.8,1,1.5,2,3,5]}}),inp=meter.createHistogram(`web_vitals.inp`,{description:`Interaction to Next Paint`,unit:`s`,advice:{explicitBucketBoundaries:[.05,.1,.15,.2,.3,.4,.5,.75,1,2]}}),cls=meter.createHistogram(`web_vitals.cls`,{description:`Cumulative Layout Shift`,unit:`1`,advice:{explicitBucketBoundaries:[.01,.025,.05,.075,.1,.15,.2,.25,.5,1]}}),vitals={LCP:{histogram:lcp,toSeconds:!0},FCP:{histogram:fcp,toSeconds:!0},TTFB:{histogram:ttfb,toSeconds:!0},INP:{histogram:inp,toSeconds:!0},CLS:{histogram:cls,toSeconds:!1}},received=new Set,flushed=!1;function report(metric){let entry=vitals[metric.name];if(!entry)return;let value=entry.toSeconds?metric.value*.001:metric.value,pathname=window.location
|
|
1
|
+
import{onCLS,onFCP,onINP,onLCP,onTTFB}from"web-vitals";const LOAD_VITALS=new Set([`TTFB`,`FCP`,`LCP`]);function captureWebVitals(input){if(typeof window>`u`)return;let{meter,resolveRoute,flush}=input,lcp=meter.createHistogram(`web_vitals.lcp`,{description:`Largest Contentful Paint`,unit:`s`,advice:{explicitBucketBoundaries:[.5,1,1.5,2,2.5,3,4,5,7.5,10]}}),fcp=meter.createHistogram(`web_vitals.fcp`,{description:`First Contentful Paint`,unit:`s`,advice:{explicitBucketBoundaries:[.5,1,1.5,1.8,2,2.5,3,4,5,7.5]}}),ttfb=meter.createHistogram(`web_vitals.ttfb`,{description:`Time to First Byte`,unit:`s`,advice:{explicitBucketBoundaries:[.1,.2,.3,.5,.8,1,1.5,2,3,5]}}),inp=meter.createHistogram(`web_vitals.inp`,{description:`Interaction to Next Paint`,unit:`s`,advice:{explicitBucketBoundaries:[.05,.1,.15,.2,.3,.4,.5,.75,1,2]}}),cls=meter.createHistogram(`web_vitals.cls`,{description:`Cumulative Layout Shift`,unit:`1`,advice:{explicitBucketBoundaries:[.01,.025,.05,.075,.1,.15,.2,.25,.5,1]}}),vitals={LCP:{histogram:lcp,toSeconds:!0},FCP:{histogram:fcp,toSeconds:!0},TTFB:{histogram:ttfb,toSeconds:!0},INP:{histogram:inp,toSeconds:!0},CLS:{histogram:cls,toSeconds:!1}},received=new Set,flushed=!1;function report(metric){let entry=vitals[metric.name];if(!entry)return;let value=entry.toSeconds?metric.value*.001:metric.value,{pathname}=window.location,route=resolveRoute?.(pathname)??pathname;entry.histogram.record(value,{...metric.rating?{"web_vital.rating":metric.rating}:{},...metric.navigationType?{"web_vital.navigation_type":metric.navigationType}:{},"url.path":route}),!flushed&&LOAD_VITALS.has(metric.name)&&(received.add(metric.name),received.size===LOAD_VITALS.size&&(flushed=!0,flush().catch(()=>{})))}onLCP(report),onCLS(report),onINP(report),onTTFB(report),onFCP(report)}export{captureWebVitals};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"../util/log.mjs";import{getPluginConsentCategory,hasConsentChanged,isConsentAllowed,resolveGrantedConsent}from"./consent.mjs";import errorsPlugin from"../plugins/errors.mjs";import{loadPlugin,resolveFeatures}from"../plugins/lib/loader.mjs";import{PLUGIN_MANIFEST}from"@interfere/types/sdk/plugins/manifest";const log=createLogger(`plugin-runtime`);var PluginRuntime=class{activeCleanups=new Map;pending=new Set;context;features=resolveFeatures(
|
|
1
|
+
import{createLogger}from"../util/log.mjs";import{getPluginConsentCategory,hasConsentChanged,isConsentAllowed,resolveGrantedConsent}from"./consent.mjs";import errorsPlugin from"../plugins/errors.mjs";import{loadPlugin,resolveFeatures}from"../plugins/lib/loader.mjs";import{PLUGIN_MANIFEST}from"@interfere/types/sdk/plugins/manifest";const log=createLogger(`plugin-runtime`);var PluginRuntime=class{activeCleanups=new Map;pending=new Set;context;features=resolveFeatures();pluginConfigListeners=new Map;remoteConfig={};remoteConfigSettled=!1;consentState=null;configSettled=!1;replayConfig={};syncVersion=0;disposed=!1;constructor(context){this.context=context}getConsent(){return this.consentState}getReplayConfig(){return this.replayConfig}applyProviderConfig(overrides,consent){let replay=overrides?.replay;this.features=resolveFeatures(overrides),this.replayConfig=typeof replay==`object`?replay:{},this.consentState=consent===void 0?null:resolveGrantedConsent(consent),this.configSettled=!0,this.sync()}setConsent(nextConsent){let nextState=resolveGrantedConsent(nextConsent);this.configSettled&&!hasConsentChanged(this.consentState,nextState)||(this.consentState=nextState,this.configSettled=!0,this.sync())}resetConsent(){this.configSettled&&!hasConsentChanged(this.consentState,null)||(this.consentState=null,this.configSettled=!0,this.sync())}applyRemoteConfig(config){this.remoteConfig=config,this.remoteConfigSettled=!0,this.emitPluginConfigState(),this.sync()}applyRemoteConfigDefaults(){this.remoteConfigSettled=!0,this.emitPluginConfigState(),this.sync()}start(){if(this.shouldEnablePlugin(`errors`)){let cleanup=errorsPlugin.setup(this.context);cleanup&&this.activeCleanups.set(`errors`,cleanup)}this.sync()}async dispose(){this.disposed=!0,this.syncVersion+=1;for(let key of this.activeCleanups.keys())this.deactivate(key);await Promise.allSettled(this.pending)}shouldEnablePlugin(key){let category=getPluginConsentCategory(key);return category!==`necessary`&&!this.configSettled?!1:this.features[key]&&this.remoteConfig[key]!==!1&&isConsentAllowed(category,this.consentState)}getPluginConfigState(key){return this.remoteConfigSettled?this.remoteConfig[key]===!1?`disabled`:`enabled`:`pending`}subscribePluginConfigState(key,listener){let listeners=this.pluginConfigListeners.get(key)??new Set;return listeners.add(listener),this.pluginConfigListeners.set(key,listeners),()=>{listeners.delete(listener),listeners.size===0&&this.pluginConfigListeners.delete(key)}}emitPluginConfigState(){for(let[key,listeners]of this.pluginConfigListeners){let state=this.getPluginConfigState(key);for(let listener of listeners)listener(state)}}deactivate(key){let cleanup=this.activeCleanups.get(key);if(cleanup){try{cleanup()}catch{log.warn(`cleanup failed for %s`,key)}this.activeCleanups.delete(key)}}async activate(key){if(this.activeCleanups.has(key)||!this.shouldEnablePlugin(key))return;let version=this.syncVersion,cleanup=await loadPlugin(key,this.context);if(cleanup){if(version!==this.syncVersion||!this.shouldEnablePlugin(key)){cleanup();return}this.activeCleanups.set(key,cleanup)}}sync(){if(!this.disposed){this.syncVersion+=1;for(let plugin of PLUGIN_MANIFEST){if(this.shouldEnablePlugin(plugin.name)){if(plugin.name===`errors`){if(!this.activeCleanups.has(`errors`)){let cleanup=errorsPlugin.setup(this.context);cleanup&&this.activeCleanups.set(`errors`,cleanup)}continue}let p=(async()=>{try{await this.activate(plugin.name)}catch{log.warn(`non-critical plugin loading failed`)}})();this.pending.add(p),(async()=>{await p,this.pending.delete(p)})();continue}this.deactivate(plugin.name)}}}};export{PluginRuntime};
|
|
@@ -4,17 +4,17 @@ import { IdentifyParams } from "@interfere/types/sdk/identify";
|
|
|
4
4
|
//#region src/internal/react-context.d.ts
|
|
5
5
|
interface InterfereContextValue {
|
|
6
6
|
consent: {
|
|
7
|
-
get()
|
|
8
|
-
set(state?: ConsentState)
|
|
7
|
+
get: () => ConsentState | null;
|
|
8
|
+
set: (state?: ConsentState) => void;
|
|
9
9
|
};
|
|
10
10
|
device: {
|
|
11
|
-
getDeviceId()
|
|
12
|
-
getFpHash()
|
|
11
|
+
getDeviceId: () => string | null;
|
|
12
|
+
getFpHash: () => string | null;
|
|
13
13
|
};
|
|
14
14
|
identity: {
|
|
15
|
-
clear()
|
|
16
|
-
get()
|
|
17
|
-
set(params: IdentifyParams)
|
|
15
|
+
clear: () => void;
|
|
16
|
+
get: () => IdentifyParams | null;
|
|
17
|
+
set: (params: IdentifyParams) => Promise<void>;
|
|
18
18
|
};
|
|
19
19
|
/**
|
|
20
20
|
* The active kernel, or `null` when the SDK isn't initialized yet — for
|
|
@@ -24,8 +24,8 @@ interface InterfereContextValue {
|
|
|
24
24
|
*/
|
|
25
25
|
kernel: Kernel | null;
|
|
26
26
|
session: {
|
|
27
|
-
getId()
|
|
28
|
-
getWindowId()
|
|
27
|
+
getId: () => string | null;
|
|
28
|
+
getWindowId: () => string | null;
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{createContext}from"react";const NULL_CONTEXT_VALUE={consent:{get:()=>null,set:()=>
|
|
1
|
+
"use client";import{createContext}from"react";const NULL_CONTEXT_VALUE={consent:{get:()=>null,set:()=>{}},device:{getDeviceId:()=>null,getFpHash:()=>null},identity:{clear:()=>{},get:()=>null,set:()=>Promise.resolve()},kernel:null,session:{getId:()=>null,getWindowId:()=>null}},InterfereContext=createContext(null);export{InterfereContext,NULL_CONTEXT_VALUE};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Queue}from"workbox-background-sync";const SYNTHETIC_QUEUED_RESPONSE={status:202,statusText:`Queued`};async function postToClients(message){let clients=await self.clients.matchAll({includeUncontrolled:!1});for(let client of clients)client.postMessage(message)}async function totalQueueBytes(queue){let entries=await queue.getAll(),total=0;for(let entry of entries){let meta=entry.metadata;typeof meta?.bytes==`number`&&(total+=meta.bytes)}return total}async function pruneOldest(queue,neededBytes){let total=await totalQueueBytes(queue);for(;total+neededBytes>10485760;){let dropped=await queue.shiftRequest();if(!dropped)break;let meta=dropped.metadata;total-=meta?.bytes??0,await postToClients({type:`interfere.sw.dropped`,url:dropped.request.url,reason:`queue_size_cap`})}}async function enqueue(queue,request){let cloned=request.clone(),bytes=(await cloned.clone().arrayBuffer()).byteLength;await pruneOldest(queue,bytes),await queue.pushRequest({request:cloned,metadata:{bytes}}),await postToClients({type:`interfere.sw.queued`,url:request.url})}function installIngestQueue(options={}){let matches=options.match??(url=>url.pathname.startsWith(`/api/interfere/`)),queue=new Queue(options.queueName??`interfere-ingest`,{maxRetentionTime:1440,onSync:async({queue:q})=>{let entry=await q.shiftRequest();for(;entry;){try{let res=await self.fetch(entry.request.clone());if(!res.ok&&(res.status>=500||res.status===429))throw Error(`retry status ${res.status}`);await postToClients({type:`interfere.sw.replayed`,url:entry.request.url})}catch(
|
|
1
|
+
import{Queue}from"workbox-background-sync";const SYNTHETIC_QUEUED_RESPONSE={status:202,statusText:`Queued`};async function postToClients(message){let clients=await self.clients.matchAll({includeUncontrolled:!1});for(let client of clients)client.postMessage(message)}async function totalQueueBytes(queue){let entries=await queue.getAll(),total=0;for(let entry of entries){let meta=entry.metadata;typeof meta?.bytes==`number`&&(total+=meta.bytes)}return total}async function pruneOldest(queue,neededBytes){let total=await totalQueueBytes(queue);for(;total+neededBytes>10485760;){let dropped=await queue.shiftRequest();if(!dropped)break;let meta=dropped.metadata;total-=meta?.bytes??0,await postToClients({type:`interfere.sw.dropped`,url:dropped.request.url,reason:`queue_size_cap`})}}async function enqueue(queue,request){let cloned=request.clone(),bytes=(await cloned.clone().arrayBuffer()).byteLength;await pruneOldest(queue,bytes),await queue.pushRequest({request:cloned,metadata:{bytes}}),await postToClients({type:`interfere.sw.queued`,url:request.url})}function installIngestQueue(options={}){let matches=options.match??(url=>url.pathname.startsWith(`/api/interfere/`)),queue=new Queue(options.queueName??`interfere-ingest`,{maxRetentionTime:1440,onSync:async({queue:q})=>{let entry=await q.shiftRequest();for(;entry;){try{let res=await self.fetch(entry.request.clone());if(!res.ok&&(res.status>=500||res.status===429))throw Error(`retry status ${res.status}`);await postToClients({type:`interfere.sw.replayed`,url:entry.request.url})}catch(error){throw await q.unshiftRequest(entry),error}entry=await q.shiftRequest()}}});self.addEventListener(`activate`,event=>{event.waitUntil(queue.replayRequests().catch(()=>{}))}),self.addEventListener(`fetch`,event=>{let{request}=event;if(request.method!==`POST`)return;let url;try{url=new URL(request.url)}catch{return}matches(url,request)&&event.respondWith((async()=>{try{let res=await self.fetch(request.clone());return res.ok?res:res.status>=500||res.status===429?(await enqueue(queue,request),new Response(null,SYNTHETIC_QUEUED_RESPONSE)):res}catch{return await enqueue(queue,request),new Response(null,SYNTHETIC_QUEUED_RESPONSE)}})())})}export{installIngestQueue};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const THIRD_PARTY_IGNORE=[/(?:^|\/\/)clerk
|
|
1
|
+
const THIRD_PARTY_IGNORE=[/(?:^|\/\/)clerk\./u,/\.clerk\./u,/\.sentry\./u,/\.intercom\./u,/\.posthog\./u,/\.googletagmanager\.com/u,/\.google-analytics\.com/u,/\.googleapis\.com/u,/\.doubleclick\.net/u,/\.facebook\.com/u,/\.fbcdn\.net/u,/\.analytics\.google\.com/u,/px\.ads\.linkedin\.com/u,/[?&]_rsc=/u];export{THIRD_PARTY_IGNORE};
|
|
@@ -3,20 +3,20 @@ import { ConsentState } from "@interfere/types/sdk/plugins/manifest";
|
|
|
3
3
|
import { IdentifyParams } from "@interfere/types/sdk/identify";
|
|
4
4
|
//#region src/internal/wrapper-singleton.d.ts
|
|
5
5
|
interface WrapperSingleton {
|
|
6
|
-
close()
|
|
6
|
+
close: () => Promise<void>;
|
|
7
7
|
consent: {
|
|
8
|
-
get()
|
|
9
|
-
set(value?: ConsentState)
|
|
8
|
+
get: () => ConsentState | null;
|
|
9
|
+
set: (value?: ConsentState) => void;
|
|
10
10
|
};
|
|
11
|
-
getKernel()
|
|
12
|
-
getKernelOrNull()
|
|
11
|
+
getKernel: () => Kernel;
|
|
12
|
+
getKernelOrNull: () => Kernel | null;
|
|
13
13
|
identity: {
|
|
14
|
-
clear()
|
|
15
|
-
get()
|
|
16
|
-
set(params: IdentifyParams)
|
|
14
|
+
clear: () => void;
|
|
15
|
+
get: () => IdentifyParams | null;
|
|
16
|
+
set: (params: IdentifyParams) => Promise<void>;
|
|
17
17
|
};
|
|
18
|
-
init(opts?: KernelOptions)
|
|
19
|
-
subscribeToKernel(listener: () => void)
|
|
18
|
+
init: (opts?: KernelOptions) => Promise<Kernel | null>;
|
|
19
|
+
subscribeToKernel: (listener: () => void) => () => void;
|
|
20
20
|
}
|
|
21
21
|
interface WrapperSingletonInput {
|
|
22
22
|
/**
|
|
@@ -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=
|
|
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()}async function initKernel(internalOpts){try{let k=await createKernel({opts:internalOpts});return kernel=k,k&®isterKernel(k),emit(),k}finally{pending=null}}return{init(opts={}){return kernel?Promise.resolve(kernel):pending||(!(opts.enabled??isEnabledByEnvironment())||isBotUserAgent()?Promise.resolve(null):(pending=initKernel({...opts,_wrapperVersions:[input.producerVersion]}),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=`11.
|
|
1
|
+
var name=`@interfere/react`,version=`11.1.1`;export{name,version};
|
|
@@ -7,18 +7,18 @@ import { PluginKey } from "@interfere/types/sdk/plugins/manifest";
|
|
|
7
7
|
type PluginCleanup = () => void;
|
|
8
8
|
type PluginConfigState = "disabled" | "enabled" | "pending";
|
|
9
9
|
interface PluginContext {
|
|
10
|
-
getPluginConfigState(key: PluginKey)
|
|
11
|
-
getReplayConfig()
|
|
12
|
-
getSessionId()
|
|
13
|
-
getWindowId()
|
|
14
|
-
onSessionPause(handler: () => void)
|
|
15
|
-
onSessionResume(handler: (reason: SessionResumeReason) => void)
|
|
16
|
-
recordException(value: Error | NonErrorException, opts: RecordExceptionOpts)
|
|
17
|
-
subscribePluginConfigState(key: PluginKey, listener: (state: PluginConfigState) => void)
|
|
10
|
+
getPluginConfigState: (key: PluginKey) => PluginConfigState;
|
|
11
|
+
getReplayConfig: () => ReplayConfig;
|
|
12
|
+
getSessionId: () => string;
|
|
13
|
+
getWindowId: () => string;
|
|
14
|
+
onSessionPause: (handler: () => void) => () => void;
|
|
15
|
+
onSessionResume: (handler: (reason: SessionResumeReason) => void) => () => void;
|
|
16
|
+
recordException: (value: Error | NonErrorException, opts: RecordExceptionOpts) => void;
|
|
17
|
+
subscribePluginConfigState: (key: PluginKey, listener: (state: PluginConfigState) => void) => () => void;
|
|
18
18
|
}
|
|
19
19
|
interface Plugin {
|
|
20
20
|
readonly name: string;
|
|
21
|
-
setup(ctx: PluginContext)
|
|
21
|
+
setup: (ctx: PluginContext) => PluginCleanup | undefined;
|
|
22
22
|
}
|
|
23
23
|
//#endregion
|
|
24
24
|
export { Plugin, PluginCleanup, PluginConfigState, PluginContext };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{trace}from"@opentelemetry/api";function distance(a,b){return Math.
|
|
1
|
+
import{trace}from"@opentelemetry/api";function distance(a,b){return Math.hypot(a.x-b.x,a.y-b.y)}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;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};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { BufferedReplayEvent } from "./chunking.mjs";
|
|
2
2
|
//#region src/plugins/replay/buffer.d.ts
|
|
3
3
|
interface ReplayBuffer {
|
|
4
|
-
drain()
|
|
5
|
-
push(event: unknown)
|
|
4
|
+
drain: () => BufferedReplayEvent[];
|
|
5
|
+
push: (event: unknown) => void;
|
|
6
6
|
}
|
|
7
7
|
declare function createReplayBuffer(opts: {
|
|
8
8
|
onSoftLimit: () => void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{EventType,IncrementalSource}from"@rrweb/types";const textEncoder=new TextEncoder,ACTIVITY_SOURCES=new Set([IncrementalSource.MouseMove,IncrementalSource.MouseInteraction,IncrementalSource.Scroll,IncrementalSource.Input,IncrementalSource.TouchMove,IncrementalSource.Drag,IncrementalSource.Selection]);function classifyReplayEvent(event){let typed=event;if(!typed||typeof typed.type!=`number`)return{isActivity:!1,isFullSnapshot:!1};if(typed.type===EventType.FullSnapshot)return{isActivity:!1,isFullSnapshot:!0};if(typed.type===EventType.IncrementalSnapshot&&typed.data){let
|
|
1
|
+
import{EventType,IncrementalSource}from"@rrweb/types";const textEncoder=new TextEncoder,ACTIVITY_SOURCES=new Set([IncrementalSource.MouseMove,IncrementalSource.MouseInteraction,IncrementalSource.Scroll,IncrementalSource.Input,IncrementalSource.TouchMove,IncrementalSource.Drag,IncrementalSource.Selection]);function classifyReplayEvent(event){let typed=event;if(!typed||typeof typed.type!=`number`)return{isActivity:!1,isFullSnapshot:!1};if(typed.type===EventType.FullSnapshot)return{isActivity:!1,isFullSnapshot:!0};if(typed.type===EventType.IncrementalSnapshot&&typed.data){let{source}=typed.data;if(source!==void 0&&ACTIVITY_SOURCES.has(source))return{isActivity:!0,isFullSnapshot:!1}}return{isActivity:!1,isFullSnapshot:!1}}function countActivityEvents(group){let count=0;for(let entry of group)entry.isActivity&&(count+=1);return count}function toReplayChunkPayload(group){let parts=[],hasFullSnapshot=!1;for(let entry of group)parts.push(entry.json),entry.isFullSnapshot&&(hasFullSnapshot=!0);let firstTs=group[0]?.ts??null;return{activityCount:countActivityEvents(group),bodyJson:parts.length>0?`${parts.join(`
|
|
2
2
|
`)}\n`:``,eventCount:group.length,firstTs,hasFullSnapshot,lastTs:group.at(-1)?.ts??null}}function splitReplayEvents(buffered){let chunks=[],droppedEventBytes=[],current=[],currentBytes=0;for(let entry of buffered){let singleEventBodyBytes=entry.bytes+1;if(singleEventBodyBytes>8388608){droppedEventBytes.push(singleEventBodyBytes);continue}current.length>0&¤tBytes+singleEventBodyBytes>6291456&&(chunks.push(current),current=[],currentBytes=0),currentBytes+=singleEventBodyBytes,current.push(entry)}return current.length>0&&chunks.push(current),{chunks,droppedEventBytes}}function utf8ByteLength(value){return textEncoder.encode(value).length}export{classifyReplayEvent,splitReplayEvents,toReplayChunkPayload,utf8ByteLength};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"../../util/log.mjs";const log=createLogger(`replay/compressor`),FAIL_OPEN={gzip:null,dispose:()=>void 0}
|
|
1
|
+
import{createLogger}from"../../util/log.mjs";const log=createLogger(`replay/compressor`),FAIL_OPEN={gzip:null,dispose:()=>void 0},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)};function createCompressor(){return typeof CompressionStream>`u`?(log.warn(`CompressionStream unavailable; replay chunks will POST uncompressed`),FAIL_OPEN):{gzip,dispose:()=>void 0}}export{createCompressor};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"../../util/log.mjs";import{onPageHidden,onPageShown}from"../../internal/page-lifecycle.mjs";import{resolveTargets}from"../../internal/config.mjs";import{splitReplayEvents,toReplayChunkPayload}from"./chunking.mjs";import{createReplayBuffer}from"./buffer.mjs";import{createCompressor}from"./compressor-host.mjs";import{toRecordOptions}from"./config.mjs";import{deliverChunk}from"./delivery.mjs";import{startReplayRecorder}from"./recorder.mjs";import{createSegmentSequence}from"./sequence.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,unsubscribeConfig=null,unsubscribeHidden=null,unsubscribeShown=null,unsubscribePause=null,unsubscribeResume=null,isPaused=!1,compressor=createCompressor(),windowId=ctx.getWindowId(),sequence=createSegmentSequence(windowId),consecutiveFailures=0,halted=!1;function halt(){halted||(halted=!0,flushTimer&&=(clearInterval(flushTimer),null),recorder?.stop(),recorder=null)}function handleResult(result){if(result===`ok`){consecutiveFailures=0;return}if(result===`fatal`){log.warn(`replay halted: upload rejected`),halt();return}consecutiveFailures+=1,consecutiveFailures>=3&&(log.warn(`replay halted after %d consecutive upload failures`,consecutiveFailures),halt())}function flush(opts){if(halted||ctx.getPluginConfigState(`replay`)!==`enabled`)return;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,chunk:{windowId,segmentId:sequence.take()},uploadBaseUrl,authHeaders,compressor,payload
|
|
1
|
+
import{createLogger}from"../../util/log.mjs";import{onPageHidden,onPageShown}from"../../internal/page-lifecycle.mjs";import{resolveTargets}from"../../internal/config.mjs";import{splitReplayEvents,toReplayChunkPayload}from"./chunking.mjs";import{createReplayBuffer}from"./buffer.mjs";import{createCompressor}from"./compressor-host.mjs";import{toRecordOptions}from"./config.mjs";import{deliverChunk}from"./delivery.mjs";import{startReplayRecorder}from"./recorder.mjs";import{createSegmentSequence}from"./sequence.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,unsubscribeConfig=null,unsubscribeHidden=null,unsubscribeShown=null,unsubscribePause=null,unsubscribeResume=null,isPaused=!1,compressor=createCompressor(),windowId=ctx.getWindowId(),sequence=createSegmentSequence(windowId),consecutiveFailures=0,halted=!1;function halt(){halted||(halted=!0,flushTimer&&=(clearInterval(flushTimer),null),recorder?.stop(),recorder=null)}function handleResult(result){if(result===`ok`){consecutiveFailures=0;return}if(result===`fatal`){log.warn(`replay halted: upload rejected`),halt();return}consecutiveFailures+=1,consecutiveFailures>=3&&(log.warn(`replay halted after %d consecutive upload failures`,consecutiveFailures),halt())}let buffer=createReplayBuffer({softLimitBytes:4194304,onSoftLimit:flush});function flush(opts){if(halted||ctx.getPluginConfigState(`replay`)!==`enabled`)return;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)sendChunk(toReplayChunkPayload(group),opts?.skipCompression??!1)}async function sendChunk(payload,skipCompression){try{handleResult(await deliverChunk({ctx,chunk:{windowId,segmentId:sequence.take()},uploadBaseUrl,authHeaders,compressor,payload,skipCompression}))}catch(error){log.warn(`replay chunk dropped: %o`,error),handleResult(`retryable`)}}let pauseSending=unload=>{isPaused||(isPaused=!0,flush(unload?{skipCompression:!0}:void 0))},resumeSending=()=>{isPaused&&(isPaused=!1,recorder?.takeFullSnapshot())},recordOptions=toRecordOptions(ctx.getReplayConfig());return(async()=>{recorder=await startReplayRecorder(event=>{isPaused||buffer.push(event)},recordOptions),recorder&&(unsubscribeConfig=ctx.subscribePluginConfigState(`replay`,state=>{state===`enabled`&&flush()}),flushTimer=setInterval(flush,1e4),unsubscribeHidden=onPageHidden(()=>pauseSending(!0)),unsubscribeShown=onPageShown(resumeSending),unsubscribePause=ctx.onSessionPause(()=>pauseSending(!1)),unsubscribeResume=ctx.onSessionResume(resumeSending),log.debug(`recording started`))})(),()=>{flush(),recorder?.stop(),flushTimer&&clearInterval(flushTimer),unsubscribeConfig?.(),unsubscribeHidden?.(),unsubscribeShown?.(),unsubscribePause?.(),unsubscribeResume?.(),compressor.dispose()}}};export{replayPlugin as default};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ReplayRecordOptions } from "./config.mjs";
|
|
2
2
|
//#region src/plugins/replay/recorder.d.ts
|
|
3
3
|
interface ReplayRecorder {
|
|
4
|
-
stop()
|
|
5
|
-
takeFullSnapshot()
|
|
4
|
+
stop: () => void;
|
|
5
|
+
takeFullSnapshot: () => void;
|
|
6
6
|
}
|
|
7
7
|
declare function startReplayRecorder(onEvent: (event: unknown) => void, maskingOptions: ReplayRecordOptions): Promise<ReplayRecorder | null>;
|
|
8
8
|
//#endregion
|
package/dist/provider.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{InterfereContext,NULL_CONTEXT_VALUE}from"./internal/react-context.mjs";import{CaptureBoundary}from"./internal/capture-boundary.mjs";import{useContext,useEffect}from"react";import{jsx}from"react/jsx-runtime";function InterfereProvider({kernel,children,consent,plugins,errorBoundary=!0}){
|
|
1
|
+
"use client";import{InterfereContext,NULL_CONTEXT_VALUE}from"./internal/react-context.mjs";import{CaptureBoundary}from"./internal/capture-boundary.mjs";import{useContext,useEffect}from"react";import{jsx}from"react/jsx-runtime";function InterfereProvider({kernel,children,consent,plugins,errorBoundary=!0}){useEffect(()=>{kernel?.syncProviderConfig({consent,plugins})},[kernel,consent,plugins]);let value=kernel?{consent:kernel.consent,device:kernel.device,identity:kernel.identity,kernel,session:kernel.session}:NULL_CONTEXT_VALUE;return jsx(InterfereContext,{value,children:errorBoundary?jsx(CaptureBoundary,{children}):children})}function useInterfere(){let ctx=useContext(InterfereContext);if(!ctx)throw Error(`useInterfere must be used within <InterfereProvider>`);return ctx}function useSession(){return useInterfere().session.getId()}export{InterfereContext,InterfereProvider,useInterfere,useSession};
|
package/dist/tracking/api.d.mts
CHANGED
package/dist/tracking/api.mjs
CHANGED
|
@@ -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)
|
|
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)}),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.postIdentify(sessionId,{sessionId,deviceId,fpHash,...params,...country&&{country}})}async postIdentify(sessionId,body){try{await this.fetcher(appendPathBeforeQuery(this.target.url,`/identify`),{method:`POST`,headers:this.requestHeaders(),body:JSON.stringify(body),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.device.reset(),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()))}async syncSession(sessionId,deviceId,fpHash){this.syncAttemptMs=Date.now(),this.syncedSessionId=sessionId;try{(await this.fetcher(this.target.url,{method:`POST`,headers:this.requestHeaders(),body:JSON.stringify({sessionId,deviceId,fpHash}),keepalive:!0,credentials:`omit`,signal:AbortSignal.timeout(1e4)})).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;try{let fpHash=await this.device.whenFingerprintReady(),deviceId=this.device.getDeviceId();if(gen!==this.generation)return;log.debug(`POST session %s (device=%s fp=%s)`,sessionId,deviceId??`pending`,fpHash??`none`),this.syncSession(sessionId,deviceId,fpHash)}catch{}}};export{SessionTracker};
|
package/dist/tracking/device.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"../util/log.mjs";const log=createLogger(`device`),LS_KEY=`interfere:device_id`,COOKIE_NAME=`interfere_did`;function whenIdle(){return
|
|
1
|
+
import{createLogger}from"../util/log.mjs";const log=createLogger(`device`),LS_KEY=`interfere:device_id`,COOKIE_NAME=`interfere_did`;function whenIdle(){let{promise,resolve}=Promise.withResolvers();return typeof globalThis.requestIdleCallback==`function`?globalThis.requestIdleCallback(()=>resolve(void 0),{timeout:5e3}):setTimeout(()=>resolve(void 0),0),promise}const defaultFingerprintProvider=async()=>{await whenIdle();try{let result=await(await(await import(`@fingerprintjs/fingerprintjs`)).load()).get();return result.visitorId?result.visitorId:(log.error(`Fingerprinting returned an empty visitor id; visitor identity will be cookie-only`),null)}catch(error){return log.error(`fingerprint init failed; visitor identity will be cookie-only`,error),null}};function tryLocalStorage(){try{let s=globalThis.localStorage,key=`__interfere_device_probe__`;return s.setItem(key,`1`),s.removeItem(key),s}catch{return null}}function getCookie(name){if(typeof document>`u`)return null;let match=document.cookie.split(`; `).find(c=>c.startsWith(`${name}=`));return match?decodeURIComponent(match.split(`=`)[1]??``):null}function setCookie(name,value){typeof document>`u`||(document.cookie=`${name}=${encodeURIComponent(value)};path=/;max-age=34560000;SameSite=Lax`)}function generateId(){return crypto.randomUUID()}var DeviceManager=class{deviceId=null;fpHash=null;fpPending=null;fingerprintProvider;constructor(fingerprintProvider){this.fingerprintProvider=fingerprintProvider??defaultFingerprintProvider}init(){if(this.deviceId)return;let ls=tryLocalStorage(),fromLs=ls?.getItem(LS_KEY)??null,fromCookie=getCookie(COOKIE_NAME);this.deviceId=fromLs??fromCookie??generateId(),!fromLs&&ls&&ls.setItem(LS_KEY,this.deviceId),fromCookie||setCookie(COOKIE_NAME,this.deviceId),fromLs&&!fromCookie&&setCookie(COOKIE_NAME,this.deviceId),fromCookie&&!fromLs&&ls&&ls.setItem(LS_KEY,this.deviceId),log.debug(`device %s (ls=%s cookie=%s)`,this.deviceId,!!fromLs,!!fromCookie),this.startFingerprint()}startFingerprint(){this.fpHash||this.fpPending||(this.fpPending=(async()=>{let hash=await this.fingerprintProvider();return this.fpHash=hash,hash&&log.debug(`fpHash %s`,hash),hash})())}getDeviceId(){return this.deviceId}getFpHash(){return this.fpHash}whenFingerprintReady(){return this.fpHash?Promise.resolve(this.fpHash):this.fpPending??Promise.resolve(null)}reset(){return this.deviceId=generateId(),tryLocalStorage()?.setItem(LS_KEY,this.deviceId),setCookie(COOKIE_NAME,this.deviceId),log.debug(`device %s (reset)`,this.deviceId),this.deviceId}};export{DeviceManager,whenIdle};
|
package/dist/tracking/geo.d.mts
CHANGED
package/dist/tracking/geo.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import{cloudflareTraceSchema}from"@interfere/types/sdk/geo";function parseTrace(text){let result={};for(let line of text.split(`
|
|
2
|
-
`)){let idx=line.indexOf(`=`);idx>0&&(result[line.slice(0,idx)]=line.slice(idx+1))}return result}var GeoDetector=class{cached=null;pending=null;failed=!1;fetcher;constructor(fetcher){this.fetcher=fetcher??globalThis.fetch.bind(globalThis)}detect(){return this.cached?Promise.resolve(this.cached):this.failed?Promise.resolve(null):(this.pending||=this.
|
|
2
|
+
`)){let idx=line.indexOf(`=`);idx>0&&(result[line.slice(0,idx)]=line.slice(idx+1))}return result}var GeoDetector=class{cached=null;pending=null;failed=!1;fetcher;constructor(fetcher){this.fetcher=fetcher??globalThis.fetch.bind(globalThis)}detect(){return this.cached?Promise.resolve(this.cached):this.failed?Promise.resolve(null):(this.pending||=this.resolveCountryCode(),this.pending)}async resolveCountryCode(){let code=await this.fetchCountryCode();return this.cached=code,code||(this.failed=!0),this.pending=null,code}getCountry(){return this.cached}async fetchCountryCode(){try{let res=await this.fetcher(`https://cloudflare.com/cdn-cgi/trace`,{signal:AbortSignal.timeout(3e3)});if(!res.ok)return null;let raw=parseTrace(await res.text()),trace=cloudflareTraceSchema.safeParse(raw);return trace.success?trace.data.loc.toUpperCase():null}catch{return null}}};export{GeoDetector};
|
package/dist/util/log.d.mts
CHANGED
|
@@ -3,10 +3,10 @@ type LogLevel = "debug" | "info" | "warn" | "error" | "none";
|
|
|
3
3
|
declare function setLogLevel(level: LogLevel): void;
|
|
4
4
|
declare function getLogLevel(): LogLevel;
|
|
5
5
|
interface Logger {
|
|
6
|
-
debug(...args: unknown[])
|
|
7
|
-
error(...args: unknown[])
|
|
8
|
-
info(...args: unknown[])
|
|
9
|
-
warn(...args: unknown[])
|
|
6
|
+
debug: (...args: unknown[]) => void;
|
|
7
|
+
error: (...args: unknown[]) => void;
|
|
8
|
+
info: (...args: unknown[]) => void;
|
|
9
|
+
warn: (...args: unknown[]) => void;
|
|
10
10
|
}
|
|
11
11
|
declare function createLogger(scope: string): Logger;
|
|
12
12
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@interfere/react",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.1.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Client-side React SDK for Interfere. Error tracking, session replay, and analytics.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"observability",
|
|
8
|
-
"
|
|
8
|
+
"typescript",
|
|
9
9
|
"react",
|
|
10
10
|
"error-tracking",
|
|
11
11
|
"session-replay"
|
|
@@ -83,42 +83,42 @@
|
|
|
83
83
|
"scripts": {
|
|
84
84
|
"build": "tsdown",
|
|
85
85
|
"test": "vitest run",
|
|
86
|
-
"typecheck": "
|
|
86
|
+
"typecheck": "tsc --noEmit"
|
|
87
87
|
},
|
|
88
88
|
"dependencies": {
|
|
89
89
|
"@fingerprintjs/fingerprintjs": "^5.2.0",
|
|
90
|
-
"@interfere/constants": "^9.0.
|
|
91
|
-
"@interfere/helpers": "^9.
|
|
92
|
-
"@interfere/types": "^10.
|
|
90
|
+
"@interfere/constants": "^9.0.5",
|
|
91
|
+
"@interfere/helpers": "^9.1.1",
|
|
92
|
+
"@interfere/types": "^10.1.1",
|
|
93
93
|
"@opentelemetry/api": "^1.9.1",
|
|
94
|
-
"@opentelemetry/api-logs": "^0.
|
|
94
|
+
"@opentelemetry/api-logs": "^0.221.0",
|
|
95
95
|
"@opentelemetry/context-zone": "^2.7.1",
|
|
96
96
|
"@opentelemetry/core": "^2.7.0",
|
|
97
|
-
"@opentelemetry/instrumentation": "^0.
|
|
98
|
-
"@opentelemetry/instrumentation-browser-navigation": "^0.
|
|
99
|
-
"@opentelemetry/instrumentation-document-load": "^0.
|
|
100
|
-
"@opentelemetry/instrumentation-fetch": "^0.
|
|
101
|
-
"@opentelemetry/instrumentation-long-task": "^0.
|
|
102
|
-
"@opentelemetry/instrumentation-user-interaction": "^0.
|
|
103
|
-
"@opentelemetry/instrumentation-xml-http-request": "^0.
|
|
104
|
-
"@opentelemetry/opentelemetry-browser-detector": "^0.
|
|
105
|
-
"@opentelemetry/otlp-transformer": "^0.
|
|
97
|
+
"@opentelemetry/instrumentation": "^0.221.0",
|
|
98
|
+
"@opentelemetry/instrumentation-browser-navigation": "^0.14.0",
|
|
99
|
+
"@opentelemetry/instrumentation-document-load": "^0.66.0",
|
|
100
|
+
"@opentelemetry/instrumentation-fetch": "^0.221.0",
|
|
101
|
+
"@opentelemetry/instrumentation-long-task": "^0.65.0",
|
|
102
|
+
"@opentelemetry/instrumentation-user-interaction": "^0.65.0",
|
|
103
|
+
"@opentelemetry/instrumentation-xml-http-request": "^0.221.0",
|
|
104
|
+
"@opentelemetry/opentelemetry-browser-detector": "^0.221.0",
|
|
105
|
+
"@opentelemetry/otlp-transformer": "^0.221.0",
|
|
106
106
|
"@opentelemetry/resources": "^2.7.0",
|
|
107
|
-
"@opentelemetry/sdk-logs": "^0.
|
|
107
|
+
"@opentelemetry/sdk-logs": "^0.221.0",
|
|
108
108
|
"@opentelemetry/sdk-metrics": "^2.7.0",
|
|
109
109
|
"@opentelemetry/sdk-trace-base": "^2.7.0",
|
|
110
110
|
"@opentelemetry/sdk-trace-web": "^2.7.1",
|
|
111
|
-
"@opentelemetry/web-common": "^0.
|
|
112
|
-
"@rrweb/types": "2.1.
|
|
111
|
+
"@opentelemetry/web-common": "^0.221.0",
|
|
112
|
+
"@rrweb/types": "2.1.1",
|
|
113
113
|
"@ua-parser-js/pro-enterprise": "^2.0.6",
|
|
114
|
-
"rrweb": "2.1.
|
|
114
|
+
"rrweb": "2.1.1",
|
|
115
115
|
"uuid": "^14.0.1",
|
|
116
|
-
"web-vitals": "^
|
|
116
|
+
"web-vitals": "^6.1.0",
|
|
117
117
|
"workbox-background-sync": "^7.4.0"
|
|
118
118
|
},
|
|
119
119
|
"peerDependencies": {
|
|
120
120
|
"react": "^19.2.6",
|
|
121
|
-
"react-dom": "^19.
|
|
121
|
+
"react-dom": "^19.0.0"
|
|
122
122
|
},
|
|
123
123
|
"peerDependenciesMeta": {
|
|
124
124
|
"react": {
|
|
@@ -129,21 +129,21 @@
|
|
|
129
129
|
}
|
|
130
130
|
},
|
|
131
131
|
"devDependencies": {
|
|
132
|
-
"@interfere/test-utils": "^9.
|
|
133
|
-
"@interfere/typescript-config": "^9.0.
|
|
134
|
-
"@types/node": "^26.
|
|
135
|
-
"@types/react": "19.2.
|
|
136
|
-
"@types/react-dom": "19.2.
|
|
137
|
-
"@
|
|
138
|
-
"@vitest/browser": "4.1.
|
|
139
|
-
"@vitest/
|
|
140
|
-
"@vitest/coverage-v8": "4.1.9",
|
|
132
|
+
"@interfere/test-utils": "^9.1.1",
|
|
133
|
+
"@interfere/typescript-config": "^9.0.2",
|
|
134
|
+
"@types/node": "^26.2.0",
|
|
135
|
+
"@types/react": "19.2.18",
|
|
136
|
+
"@types/react-dom": "19.2.4",
|
|
137
|
+
"@vitest/browser": "4.1.10",
|
|
138
|
+
"@vitest/browser-playwright": "4.1.10",
|
|
139
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
141
140
|
"fflate": "^0.8.3",
|
|
142
|
-
"playwright": "^1.
|
|
141
|
+
"playwright": "^1.62.1",
|
|
143
142
|
"react": "^19.2.6",
|
|
144
|
-
"react-dom": "
|
|
143
|
+
"react-dom": "19.2.8",
|
|
145
144
|
"tsdown": "^0.22.2",
|
|
146
|
-
"
|
|
145
|
+
"typescript": "^7.0.2",
|
|
146
|
+
"vitest": "4.1.10",
|
|
147
147
|
"vitest-browser-react": "2.2.0"
|
|
148
148
|
}
|
|
149
149
|
}
|