@interfere/react 11.0.0 → 11.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.mts +0 -1
- package/dist/error-boundary.d.mts +1 -1
- package/dist/internal/browser-context.d.mts +0 -1
- package/dist/internal/capture-boundary.d.mts +0 -1
- package/dist/internal/capture.d.mts +0 -1
- package/dist/internal/consent.d.mts +0 -1
- package/dist/internal/consent.mjs +1 -1
- package/dist/internal/kernel-registry.d.mts +0 -1
- package/dist/internal/kernel.d.mts +8 -8
- package/dist/internal/kernel.mjs +1 -1
- package/dist/internal/otel/exporter.d.mts +0 -1
- package/dist/internal/otel/initial-navigation.d.mts +0 -1
- package/dist/internal/otel/instrumentations.d.mts +0 -1
- package/dist/internal/otel/page-scope-context-manager.d.mts +0 -1
- package/dist/internal/otel/propagation.d.mts +0 -1
- package/dist/internal/otel/provider.d.mts +0 -1
- package/dist/internal/otel/web-vitals.d.mts +0 -1
- package/dist/internal/plugin-runtime.d.mts +7 -3
- package/dist/internal/plugin-runtime.mjs +1 -1
- package/dist/internal/react-context.d.mts +0 -1
- package/dist/internal/wrapper-singleton.d.mts +0 -1
- package/dist/package.mjs +1 -1
- package/dist/plugins/errors.d.mts +0 -1
- package/dist/plugins/errors.mjs +1 -1
- package/dist/plugins/lib/loader.d.mts +4 -2
- package/dist/plugins/lib/loader.mjs +1 -1
- package/dist/plugins/lib/types.d.mts +3 -1
- package/dist/plugins/logs.d.mts +0 -1
- package/dist/plugins/rage-clicks.d.mts +0 -1
- package/dist/plugins/replay/buffer.d.mts +0 -1
- package/dist/plugins/replay/buffer.mjs +1 -1
- package/dist/plugins/replay/chunking.d.mts +9 -1
- package/dist/plugins/replay/chunking.mjs +2 -1
- package/dist/plugins/replay/config.d.mts +12 -0
- package/dist/plugins/replay/config.mjs +1 -0
- package/dist/plugins/replay/delivery.d.mts +8 -10
- package/dist/plugins/replay/delivery.mjs +1 -1
- package/dist/plugins/replay/index.d.mts +0 -1
- package/dist/plugins/replay/index.mjs +1 -1
- package/dist/plugins/replay/recorder.d.mts +2 -1
- package/dist/plugins/replay/recorder.mjs +1 -1
- package/dist/plugins/replay/sequence.d.mts +7 -0
- package/dist/plugins/replay/sequence.mjs +1 -0
- package/dist/provider.d.mts +24 -8
- package/dist/provider.mjs +1 -1
- package/dist/react-error-handler.d.mts +0 -1
- package/dist/tracking/api.d.mts +0 -1
- package/dist/tracking/session.d.mts +0 -1
- package/dist/tracking/session.mjs +1 -1
- package/package.json +7 -5
package/dist/api.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InterfereContext, InterfereContextValue } from "./internal/react-context.mjs";
|
|
2
|
+
import "./provider.mjs";
|
|
2
3
|
import { Component, ContextType, ErrorInfo, ReactNode } from "react";
|
|
3
|
-
|
|
4
4
|
//#region src/error-boundary.d.ts
|
|
5
5
|
interface ErrorBoundaryProps {
|
|
6
6
|
children: ReactNode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{PLUGIN_MANIFEST}from"@interfere/types/sdk/plugins/manifest";const DEFAULT_CONSENT={analytics:!0,replay:!0},PLUGIN_CONSENT_BY_KEY=Object.fromEntries(PLUGIN_MANIFEST.map(plugin=>[plugin.name,plugin.consentCategory])),EVENT_CONSENT_BY_TYPE=Object.fromEntries(PLUGIN_MANIFEST.flatMap(plugin=>plugin.events.map(event=>[event.name,plugin.consentCategory]))),GATEABLE_CATEGORIES=Object.keys(DEFAULT_CONSENT);function getPluginConsentCategory(key){return PLUGIN_CONSENT_BY_KEY[key]}function isConsentAllowed(category,consentState){return category===`necessary`||consentState===null||consentState[category]===!0}function shouldCaptureEvent(type,consentState){return isConsentAllowed(EVENT_CONSENT_BY_TYPE[type],consentState)}function resolveGrantedConsent(consent){return
|
|
1
|
+
import{PLUGIN_MANIFEST}from"@interfere/types/sdk/plugins/manifest";const DEFAULT_CONSENT={analytics:!0,replay:!0},PLUGIN_CONSENT_BY_KEY=Object.fromEntries(PLUGIN_MANIFEST.map(plugin=>[plugin.name,plugin.consentCategory])),EVENT_CONSENT_BY_TYPE=Object.fromEntries(PLUGIN_MANIFEST.flatMap(plugin=>plugin.events.map(event=>[event.name,plugin.consentCategory]))),GATEABLE_CATEGORIES=Object.keys(DEFAULT_CONSENT);function getPluginConsentCategory(key){return PLUGIN_CONSENT_BY_KEY[key]}function isConsentAllowed(category,consentState){return category===`necessary`||consentState===null||consentState[category]===!0}function shouldCaptureEvent(type,consentState){return isConsentAllowed(EVENT_CONSENT_BY_TYPE[type],consentState)}function resolveGrantedConsent(consent){return{...DEFAULT_CONSENT,...consent}}function hasConsentChanged(current,next){for(let category of GATEABLE_CATEGORIES)if(current?.[category]!==next?.[category])return!0;return!1}export{DEFAULT_CONSENT,getPluginConsentCategory,hasConsentChanged,isConsentAllowed,resolveGrantedConsent,shouldCaptureEvent};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { ReplayConfig } from "../plugins/replay/config.mjs";
|
|
1
2
|
import { SessionResumeReason } from "./activity.mjs";
|
|
2
3
|
import { PluginConfigState, PluginContext } from "../plugins/lib/types.mjs";
|
|
3
4
|
import { PluginOverrides } from "../plugins/lib/loader.mjs";
|
|
4
5
|
import { SessionTracker } from "../tracking/api.mjs";
|
|
5
6
|
import { CaptureFailedRequestsOption, CaptureFailedRequestsOptions, HttpStatusCodeRange } from "./otel/instrumentations.mjs";
|
|
6
7
|
import { OtelProviderHandle } from "./otel/provider.mjs";
|
|
8
|
+
import "./otel/index.mjs";
|
|
7
9
|
import { PluginRuntime } from "./plugin-runtime.mjs";
|
|
8
10
|
import { NonErrorException } from "@interfere/types/sdk/errors";
|
|
9
11
|
import { ConsentState, PluginKey } from "@interfere/types/sdk/plugins/manifest";
|
|
@@ -15,7 +17,6 @@ import { IngestedFrame } from "@interfere/types/data/frame";
|
|
|
15
17
|
import { SessionId } from "@interfere/types/data/session";
|
|
16
18
|
import { IdentifyParams } from "@interfere/types/sdk/identify";
|
|
17
19
|
import { ErrorMechanism } from "@interfere/types/sdk/plugins/payload/errors";
|
|
18
|
-
|
|
19
20
|
//#region src/internal/kernel.d.ts
|
|
20
21
|
declare function buildSdkStack(wrapperVersions?: string[]): string[];
|
|
21
22
|
interface KernelOptions {
|
|
@@ -26,7 +27,6 @@ interface KernelOptions {
|
|
|
26
27
|
* survive ad-blockers, or to a regional endpoint. Must be a full URL.
|
|
27
28
|
*/
|
|
28
29
|
apiHost?: string;
|
|
29
|
-
consent?: ConsentState;
|
|
30
30
|
/**
|
|
31
31
|
* Override the automatic dev-mode guard. When `undefined`, the SDK
|
|
32
32
|
* auto-detects: it disables itself if `process.env["NODE_ENV"]` is not
|
|
@@ -37,7 +37,6 @@ interface KernelOptions {
|
|
|
37
37
|
enabled?: boolean;
|
|
38
38
|
/** Override `globalThis.fetch` for tests / non-browser hosts. */
|
|
39
39
|
fetch?: typeof globalThis.fetch;
|
|
40
|
-
plugins?: PluginOverrides;
|
|
41
40
|
/**
|
|
42
41
|
* Override the OTel `service.name` resource attribute. Defaults to
|
|
43
42
|
* `"interfere-sdk"`. Customers running multiple frontends in one
|
|
@@ -218,9 +217,11 @@ declare class Kernel implements PluginContext {
|
|
|
218
217
|
private requireRuntime;
|
|
219
218
|
/** PluginContext shim: plugins expect a non-null string. */
|
|
220
219
|
getSessionId(): string;
|
|
220
|
+
getWindowId(): string;
|
|
221
221
|
onSessionPause(handler: () => void): () => void;
|
|
222
222
|
onSessionResume(handler: (reason: SessionResumeReason) => void): () => void;
|
|
223
223
|
getPluginConfigState(key: PluginKey): PluginConfigState;
|
|
224
|
+
getReplayConfig(): ReplayConfig;
|
|
224
225
|
subscribePluginConfigState(key: PluginKey, listener: (state: PluginConfigState) => void): () => void;
|
|
225
226
|
/**
|
|
226
227
|
* Records an exception event on the active OTel span. No envelope
|
|
@@ -273,11 +274,10 @@ declare class Kernel implements PluginContext {
|
|
|
273
274
|
* `span()` / `capture()` helpers in `api.ts`.
|
|
274
275
|
*/
|
|
275
276
|
attachOtel(handle: OtelProviderHandle, dispose: () => void): void;
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
syncConsent(value: ConsentState | undefined): void;
|
|
277
|
+
syncProviderConfig(config: {
|
|
278
|
+
consent?: ConsentState | undefined;
|
|
279
|
+
plugins?: PluginOverrides | undefined;
|
|
280
|
+
}): void;
|
|
281
281
|
dispose(): Promise<void>;
|
|
282
282
|
}
|
|
283
283
|
interface CreateKernelInput {
|
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{releaseSlugSchema}from"@interfere/types/releases/slug";const log=createLogger(`kernel`),TRAILING_SLASH_RE=/\/$/,REPLAY_UPLOAD_RE=/\/v2\/replay\/upload\//;function buildSdkStack(wrapperVersions){return[...wrapperVersions??[],PRODUCER_VERSION]}function buildExceptionEventAttrs(value,opts){let attrs={"interfere.exception.mechanism":opts.mechanism.type,"interfere.exception.handled":opts.mechanism.handled};if(opts.errorDigest&&(attrs[`interfere.error.digest`]=opts.errorDigest),opts.subtype&&(attrs[`interfere.exception.subtype`]=opts.subtype),isNonErrorException(value))return attrs[`exception.type`]=value.type,attrs[`exception.message`]=value.value,attrs[`interfere.exception.kind`]=`non-error`,attrs[`interfere.exception.serialized`]=safeStringify(value.serialized),attrs;attrs[`exception.type`]=value.name,attrs[`exception.message`]=value.message,attrs[`interfere.exception.kind`]=`error`,value.stack&&(attrs[`exception.stacktrace`]=value.stack),attrs[`interfere.exception.chain`]=safeStringify(toExceptions(value,opts.mechanism));let debugIds=globalThis._debugIds;return debugIds&&typeof debugIds==`object`&&Object.keys(debugIds).length>0&&(attrs[`interfere.exception.debug_ids`]=safeStringify(debugIds)),opts.appendFrames&&opts.appendFrames.length>0&&(attrs[`interfere.exception.appended_frames`]=safeStringify(opts.appendFrames)),opts.fallbackFrames&&opts.fallbackFrames.length>0&&(attrs[`interfere.exception.fallback_frames`]=safeStringify(opts.fallbackFrames)),attrs}var Kernel=class{consent;identity;device;session;tracker;seen=new WeakSet;initialNavigationSeen=new Set;runtime=null;otel=null;otelDispose=null;unsubscribePageHidden=null;constructor(tracker){this.tracker=tracker,this.consent={get:()=>this.requireRuntime().getConsent(),set:value=>{let runtime=this.requireRuntime();value?runtime.setConsent(value):runtime.resetConsent()}},this.identity={clear:()=>this.tracker.reset(),get:()=>this.tracker.getIdentity(),set:params=>this.tracker.identify(params)},this.device={getDeviceId:()=>this.tracker.getDeviceId(),getFpHash:()=>this.tracker.getFpHash()},this.session={getId:()=>this.tracker.sessionId(),getWindowId:()=>this.tracker.windowId()},this.unsubscribePageHidden=onPageHidden(()=>{this.flush().catch(()=>void 0)})}attachRuntime(runtime){this.runtime=runtime}requireRuntime(){if(!this.runtime)throw Error(`Kernel runtime not attached. createKernel must call attachRuntime before any consent/dispose path runs.`);return this.runtime}getSessionId(){return this.tracker.sessionId()??``}onSessionPause(handler){return this.tracker.onSessionPause(handler)}onSessionResume(handler){return this.tracker.onSessionResume(handler)}getPluginConfigState(key){return this.requireRuntime().getPluginConfigState(key)}subscribePluginConfigState(key,listener){return this.requireRuntime().subscribePluginConfigState(key,listener)}recordException(value,opts){if(!isNonErrorException(value)){if(this.seen.has(value))return;this.seen.add(value)}if(!isNonErrorException(value)){let stacks=[],current=value;for(let depth=0;current&&depth<5;depth+=1)current.stack&&stacks.push(current.stack),current=current.cause instanceof Error?current.cause:void 0;if(shouldDropBrowserExtensionNoise(stacks)||shouldDropUnresolvableStack(stacks))return}let attrs=buildExceptionEventAttrs(value,opts),active=trace.getActiveSpan();if(active?.isRecording()){active.addEvent(`exception`,attrs);return}let span=this.otel?.tracerProvider.getTracer(`@interfere/react/exception`).startSpan(`interfere.exception.capture`);span&&(span.addEvent(`exception`,attrs),span.end())}recordLog(input){this.otel&&this.otel.loggerProvider.getLogger(`@interfere/react`).emit({severityText:input.severityText,severityNumber:input.severityNumber,body:input.body,...input.attributes?{attributes:input.attributes}:{}})}recordInitialNavigation(loggerProvider){recordInitialNavigation({getSessionId:()=>this.session.getId(),getWindowId:()=>this.session.getWindowId(),loggerProvider,seen:this.initialNavigationSeen})}async flush(){this.otel&&await this.otel.flush()}attachOtel(handle,dispose){this.otel=handle,this.otelDispose=dispose}
|
|
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{releaseSlugSchema}from"@interfere/types/releases/slug";import{readBakedPlugins}from"@interfere/types/sdk/remote-config";const log=createLogger(`kernel`),TRAILING_SLASH_RE=/\/$/,REPLAY_UPLOAD_RE=/\/v2\/replay\/upload\//;function buildSdkStack(wrapperVersions){return[...wrapperVersions??[],PRODUCER_VERSION]}function buildExceptionEventAttrs(value,opts){let attrs={"interfere.exception.mechanism":opts.mechanism.type,"interfere.exception.handled":opts.mechanism.handled};if(opts.errorDigest&&(attrs[`interfere.error.digest`]=opts.errorDigest),opts.subtype&&(attrs[`interfere.exception.subtype`]=opts.subtype),isNonErrorException(value))return attrs[`exception.type`]=value.type,attrs[`exception.message`]=value.value,attrs[`interfere.exception.kind`]=`non-error`,attrs[`interfere.exception.serialized`]=safeStringify(value.serialized),attrs;attrs[`exception.type`]=value.name,attrs[`exception.message`]=value.message,attrs[`interfere.exception.kind`]=`error`,value.stack&&(attrs[`exception.stacktrace`]=value.stack),attrs[`interfere.exception.chain`]=safeStringify(toExceptions(value,opts.mechanism));let debugIds=globalThis._debugIds;return debugIds&&typeof debugIds==`object`&&Object.keys(debugIds).length>0&&(attrs[`interfere.exception.debug_ids`]=safeStringify(debugIds)),opts.appendFrames&&opts.appendFrames.length>0&&(attrs[`interfere.exception.appended_frames`]=safeStringify(opts.appendFrames)),opts.fallbackFrames&&opts.fallbackFrames.length>0&&(attrs[`interfere.exception.fallback_frames`]=safeStringify(opts.fallbackFrames)),attrs}var Kernel=class{consent;identity;device;session;tracker;seen=new WeakSet;initialNavigationSeen=new Set;runtime=null;otel=null;otelDispose=null;unsubscribePageHidden=null;constructor(tracker){this.tracker=tracker,this.consent={get:()=>this.requireRuntime().getConsent(),set:value=>{let runtime=this.requireRuntime();value?runtime.setConsent(value):runtime.resetConsent()}},this.identity={clear:()=>this.tracker.reset(),get:()=>this.tracker.getIdentity(),set:params=>this.tracker.identify(params)},this.device={getDeviceId:()=>this.tracker.getDeviceId(),getFpHash:()=>this.tracker.getFpHash()},this.session={getId:()=>this.tracker.sessionId(),getWindowId:()=>this.tracker.windowId()},this.unsubscribePageHidden=onPageHidden(()=>{this.flush().catch(()=>void 0)})}attachRuntime(runtime){this.runtime=runtime}requireRuntime(){if(!this.runtime)throw Error(`Kernel runtime not attached. createKernel must call attachRuntime before any consent/dispose path runs.`);return this.runtime}getSessionId(){return this.tracker.sessionId()??``}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(`__INTERFERE_FORCE_ENABLE__`)&&log.warn(`FORCE_ENABLE is active. Events will be dropped by production collectors. This should only be set during local development.`);let tracker=new SessionTracker({target:targets.session,fetcher});opts._wrapperVersions?.length&&(globalThis.__INTERFERE_SDK_STACK__=opts._wrapperVersions);let kernel=new Kernel(tracker),runtime=new PluginRuntime(kernel);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({tracerProvider:handle.tracerProvider,loggerProvider:handle.loggerProvider,ignoreUrls:[`${collectorUrl}/v2/sink`,targets.config.url,targets.session.url,REPLAY_UPLOAD_RE],...tracing.apiHosts?{apiHosts:tracing.apiHosts}:{},...tracing.propagateContextUrls?{propagateContextUrls:tracing.propagateContextUrls}:{},...tracing.resolveRoute?{resolveRoute:tracing.resolveRoute}:{},...tracing.captureFailedRequests===void 0?{}:{captureFailedRequests:tracing.captureFailedRequests}});tracing.webVitals!==!1&&captureWebVitals({meter:handle.meterProvider.getMeter(`@interfere/react/web-vitals`),flush:()=>handle.flush(),...tracing.resolveRoute?{resolveRoute:tracing.resolveRoute}:{}}),kernel.attachOtel(handle,dispose),kernel.recordInitialNavigation(handle.loggerProvider)}async function fetchRemoteConfig(configTarget,fetcher){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(`__INTERFERE_FORCE_ENABLE__`)||process.env.INTERFERE_FORCE_ENABLE)}function readReleaseSlugFromGlobals(){let value=getGlobal(`__INTERFERE_RELEASE_SLUG__`);return typeof value==`string`?releaseSlugSchema.parse(value):null}export{Kernel,buildSdkStack,createKernel,isEnabledByEnvironment,readReleaseSlugFromGlobals};
|
|
@@ -2,7 +2,6 @@ import { ExportResult } from "@opentelemetry/core";
|
|
|
2
2
|
import { AggregationTemporality, PushMetricExporter, ResourceMetrics } from "@opentelemetry/sdk-metrics";
|
|
3
3
|
import { ReadableLogRecord } from "@opentelemetry/sdk-logs";
|
|
4
4
|
import { SpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
5
|
-
|
|
6
5
|
//#region src/internal/otel/exporter.d.ts
|
|
7
6
|
type SignalSender = (payload: Uint8Array | undefined, resultCallback: (result: ExportResult) => void) => void;
|
|
8
7
|
interface ExporterCommon {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Span } from "@opentelemetry/api";
|
|
2
2
|
import { LoggerProvider } from "@opentelemetry/sdk-logs";
|
|
3
3
|
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
|
|
4
|
-
|
|
5
4
|
//#region src/internal/otel/instrumentations.d.ts
|
|
6
5
|
interface FetchErrorShape {
|
|
7
6
|
message: string;
|
|
@@ -5,7 +5,6 @@ import { LogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs";
|
|
|
5
5
|
import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
6
6
|
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
|
|
7
7
|
import { SessionId } from "@interfere/types/data/session";
|
|
8
|
-
|
|
9
8
|
//#region src/internal/otel/provider.d.ts
|
|
10
9
|
interface OtelProviderInput {
|
|
11
10
|
/**
|
|
@@ -1,22 +1,26 @@
|
|
|
1
|
+
import { ReplayConfig } from "../plugins/replay/config.mjs";
|
|
1
2
|
import { PluginConfigState, PluginContext } from "../plugins/lib/types.mjs";
|
|
2
3
|
import { PluginOverrides } from "../plugins/lib/loader.mjs";
|
|
3
4
|
import { ConsentState, PluginKey } from "@interfere/types/sdk/plugins/manifest";
|
|
4
5
|
import { RemotePluginConfig } from "@interfere/types/sdk/remote-config";
|
|
5
|
-
|
|
6
6
|
//#region src/internal/plugin-runtime.d.ts
|
|
7
7
|
declare class PluginRuntime {
|
|
8
8
|
private readonly activeCleanups;
|
|
9
9
|
private readonly pending;
|
|
10
10
|
private readonly context;
|
|
11
|
-
private
|
|
11
|
+
private features;
|
|
12
12
|
private readonly pluginConfigListeners;
|
|
13
13
|
private remoteConfig;
|
|
14
14
|
private remoteConfigSettled;
|
|
15
15
|
private consentState;
|
|
16
|
+
private configSettled;
|
|
17
|
+
private replayConfig;
|
|
16
18
|
private syncVersion;
|
|
17
19
|
private disposed;
|
|
18
|
-
constructor(context: PluginContext
|
|
20
|
+
constructor(context: PluginContext);
|
|
19
21
|
getConsent(): ConsentState | null;
|
|
22
|
+
getReplayConfig(): ReplayConfig;
|
|
23
|
+
applyProviderConfig(overrides: PluginOverrides | undefined, consent: ConsentState | undefined): void;
|
|
20
24
|
setConsent(nextConsent?: ConsentState): void;
|
|
21
25
|
resetConsent(): void;
|
|
22
26
|
applyRemoteConfig(config: RemotePluginConfig): void;
|
|
@@ -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;pluginConfigListeners=new Map;remoteConfig={};remoteConfigSettled=!1;consentState;syncVersion=0;disposed=!1;constructor(context
|
|
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(void 0);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=this.activate(plugin.name).catch(()=>{log.warn(`non-critical plugin loading failed`)});this.pending.add(p),p.finally(()=>this.pending.delete(p));continue}this.deactivate(plugin.name)}}}};export{PluginRuntime};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Kernel } from "./kernel.mjs";
|
|
2
2
|
import { ConsentState } from "@interfere/types/sdk/plugins/manifest";
|
|
3
3
|
import { IdentifyParams } from "@interfere/types/sdk/identify";
|
|
4
|
-
|
|
5
4
|
//#region src/internal/react-context.d.ts
|
|
6
5
|
interface InterfereContextValue {
|
|
7
6
|
consent: {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Kernel, KernelOptions } from "./kernel.mjs";
|
|
2
2
|
import { ConsentState } from "@interfere/types/sdk/plugins/manifest";
|
|
3
3
|
import { IdentifyParams } from "@interfere/types/sdk/identify";
|
|
4
|
-
|
|
5
4
|
//#region src/internal/wrapper-singleton.d.ts
|
|
6
5
|
interface WrapperSingleton {
|
|
7
6
|
close(): Promise<void>;
|
package/dist/package.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var name=`@interfere/react`,version=`11.0.
|
|
1
|
+
var name=`@interfere/react`,version=`11.0.3`;export{name,version};
|
package/dist/plugins/errors.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{hasServerDigest}from"../internal/server-digest.mjs";import{onConsoleCall}from"../internal/console-patch.mjs";import{THIRD_PARTY_IGNORE}from"../internal/third-party-hosts.mjs";import{MECHANISM_TYPE,isNonErrorException,toException}from"@interfere/types/sdk/errors";function capture(ctx,opts){ctx.recordException(opts.error,{mechanism:opts.mechanism,...opts.fallbackFrame&&!isNonErrorException(opts.error)?{fallbackFrames:[opts.fallbackFrame]}:{}})}function findErrorArg(args){return args.find(arg=>arg instanceof Error)??null}function resourceError(target){return target instanceof HTMLImageElement||target instanceof HTMLScriptElement?target.src?{tag:target.localName,url:target.src}:null:target instanceof HTMLLinkElement&&target.href?{tag:target.localName,url:target.href}:null}function isIgnoredResourceUrl(url){return THIRD_PARTY_IGNORE.some(pattern=>pattern.test(url))}function createResourceGate(){let seen=new Set,windowStart=0,windowCount=0;return url=>{if(seen.has(url))return!1;let now=Date.now();return now-windowStart>6e4&&(windowStart=now,windowCount=0),windowCount>=20?!1:(seen.size>=100&&seen.clear(),seen.add(url),windowCount+=1,!0)}}const errorsPlugin={name:`errors`,setup(ctx){let originalOnError=globalThis.onerror,originalStackTraceLimit=Error.stackTraceLimit;Error.stackTraceLimit<50&&(Error.stackTraceLimit=50),globalThis.onerror=(msg,source,line,col,error)=>{if(error instanceof Error&&!hasServerDigest(error)){let fallbackFrame=typeof source==`string`?{id:`fallback-0`,file:source,...typeof line==`number`?{line}:{},...typeof col==`number`?{column:col}:{}}:null;capture(ctx,{error,mechanism:{type:MECHANISM_TYPE.browser.onerror,handled:!1},...fallbackFrame?{fallbackFrame}:{}})}return typeof originalOnError==`function
|
|
1
|
+
import{hasServerDigest}from"../internal/server-digest.mjs";import{onConsoleCall}from"../internal/console-patch.mjs";import{THIRD_PARTY_IGNORE}from"../internal/third-party-hosts.mjs";import{MECHANISM_TYPE,isNonErrorException,toException}from"@interfere/types/sdk/errors";function capture(ctx,opts){ctx.recordException(opts.error,{mechanism:opts.mechanism,...opts.fallbackFrame&&!isNonErrorException(opts.error)?{fallbackFrames:[opts.fallbackFrame]}:{}})}function findErrorArg(args){return args.find(arg=>arg instanceof Error)??null}function resourceError(target){return target instanceof HTMLImageElement||target instanceof HTMLScriptElement?target.src?{tag:target.localName,url:target.src}:null:target instanceof HTMLLinkElement&&target.href?{tag:target.localName,url:target.href}:null}function isIgnoredResourceUrl(url){return THIRD_PARTY_IGNORE.some(pattern=>pattern.test(url))}function createResourceGate(){let seen=new Set,windowStart=0,windowCount=0;return url=>{if(seen.has(url))return!1;let now=Date.now();return now-windowStart>6e4&&(windowStart=now,windowCount=0),windowCount>=20?!1:(seen.size>=100&&seen.clear(),seen.add(url),windowCount+=1,!0)}}const errorsPlugin={name:`errors`,setup(ctx){let originalOnError=globalThis.onerror,originalStackTraceLimit=Error.stackTraceLimit;Error.stackTraceLimit<50&&(Error.stackTraceLimit=50),globalThis.onerror=(msg,source,line,col,error)=>{if(error instanceof Error&&!hasServerDigest(error)){let fallbackFrame=typeof source==`string`?{id:`fallback-0`,file:source,...typeof line==`number`?{line}:{},...typeof col==`number`?{column:col}:{}}:null;capture(ctx,{error,mechanism:{type:MECHANISM_TYPE.browser.onerror,handled:!1},...fallbackFrame?{fallbackFrame}:{}})}return typeof originalOnError==`function`&&originalOnError.call(globalThis,msg,source,line,col,error)};let onUnhandledRejection=event=>{hasServerDigest(event.reason)||capture(ctx,{error:toException(event.reason),mechanism:{type:MECHANISM_TYPE.browser.onunhandledrejection,handled:!1,...event.reason instanceof Error?{}:{synthetic:!0}}})},seenCspReports=new Set,onSecurityPolicyViolation=event=>{let key=`${event.violatedDirective}|${event.blockedURI}`;seenCspReports.has(key)||seenCspReports.size>=5||(seenCspReports.add(key),capture(ctx,{error:{kind:`non-error`,type:`SecurityPolicyViolation`,value:`CSP ${event.violatedDirective} blocked ${event.blockedURI||`inline`}`,serialized:{violatedDirective:event.violatedDirective,blockedURI:event.blockedURI,sourceFile:event.sourceFile,lineNumber:event.lineNumber,columnNumber:event.columnNumber,disposition:event.disposition}},mechanism:{type:MECHANISM_TYPE.browser.securityPolicyViolation,handled:!1}}))},resourceGate=createResourceGate(),onResourceError=event=>{let resource=resourceError(event.target);!resource||isIgnoredResourceUrl(resource.url)||!resourceGate(resource.url)||capture(ctx,{error:{kind:`non-error`,type:`ResourceLoadError`,value:`Failed to load ${resource.tag}: ${resource.url}`,serialized:{url:resource.url}},mechanism:{type:MECHANISM_TYPE.browser.resourceError,handled:!1}})},supportsEventTarget=typeof globalThis.addEventListener==`function`&&typeof globalThis.removeEventListener==`function`;supportsEventTarget&&(globalThis.addEventListener(`unhandledrejection`,onUnhandledRejection),globalThis.addEventListener(`securitypolicyviolation`,onSecurityPolicyViolation),globalThis.addEventListener(`error`,onResourceError,!0));let unsubscribeConsole=onConsoleCall((level,args)=>{if(level!==`error`)return;let error=findErrorArg(args);error&&!hasServerDigest(error)&&capture(ctx,{error,mechanism:{type:MECHANISM_TYPE.browser.consoleError,handled:!0}})});return()=>{Error.stackTraceLimit=originalStackTraceLimit,globalThis.onerror=originalOnError,supportsEventTarget&&(globalThis.removeEventListener(`unhandledrejection`,onUnhandledRejection),globalThis.removeEventListener(`securitypolicyviolation`,onSecurityPolicyViolation),globalThis.removeEventListener(`error`,onResourceError,!0)),unsubscribeConsole()}}};export{errorsPlugin as default};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { ReplayOption } from "../replay/config.mjs";
|
|
1
2
|
import { PluginCleanup, PluginContext } from "./types.mjs";
|
|
2
3
|
import { PluginKey } from "@interfere/types/sdk/plugins/manifest";
|
|
3
|
-
|
|
4
4
|
//#region src/plugins/lib/loader.d.ts
|
|
5
|
-
type PluginOverrides = Partial<Record<PluginKey, boolean
|
|
5
|
+
type PluginOverrides = Partial<Record<Exclude<PluginKey, "replay">, boolean>> & {
|
|
6
|
+
replay?: ReplayOption;
|
|
7
|
+
};
|
|
6
8
|
declare function resolveFeatures(overrides?: PluginOverrides): Record<PluginKey, boolean>;
|
|
7
9
|
declare function loadPlugin(key: PluginKey, context: PluginContext): Promise<PluginCleanup | null>;
|
|
8
10
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"../../util/log.mjs";import{PLUGIN_MANIFEST}from"@interfere/types/sdk/plugins/manifest";const log=createLogger(`plugins`),LOADERS={errors:()=>import(`../errors.mjs`),logs:()=>import(`../logs.mjs`),rageClick:()=>import(`../rage-clicks.mjs`),replay:()=>import(`../replay/index.mjs`)},DEFAULTS=Object.fromEntries(PLUGIN_MANIFEST.map(p=>[p.name,p.defaultEnabled]));function resolveFeatures(overrides){return{...DEFAULTS,...overrides}}function resolvePlugin(mod){return`default`in mod&&typeof mod.default.setup==`function`?mod.default:mod}async function loadPlugin(key,context){let loader=LOADERS[key];if(!loader)return null;try{let cleanup=resolvePlugin(await loader()).setup(context);return log.debug(`loaded %s`,key),typeof cleanup==`function`?cleanup:null}catch{return log.error(`failed to load plugin %s`,key),null}}export{loadPlugin,resolveFeatures};
|
|
1
|
+
import{createLogger}from"../../util/log.mjs";import{PLUGIN_MANIFEST}from"@interfere/types/sdk/plugins/manifest";const log=createLogger(`plugins`),LOADERS={errors:()=>import(`../errors.mjs`),logs:()=>import(`../logs.mjs`),rageClick:()=>import(`../rage-clicks.mjs`),replay:()=>import(`../replay/index.mjs`)},DEFAULTS=Object.fromEntries(PLUGIN_MANIFEST.map(p=>[p.name,p.defaultEnabled]));function resolveFeatures(overrides){if(!overrides)return{...DEFAULTS};let{replay,...rest}=overrides;return{...DEFAULTS,...rest,replay:replay===void 0?DEFAULTS.replay:replay!==!1}}function resolvePlugin(mod){return`default`in mod&&typeof mod.default.setup==`function`?mod.default:mod}async function loadPlugin(key,context){let loader=LOADERS[key];if(!loader)return null;try{let cleanup=resolvePlugin(await loader()).setup(context);return log.debug(`loaded %s`,key),typeof cleanup==`function`?cleanup:null}catch{return log.error(`failed to load plugin %s`,key),null}}export{loadPlugin,resolveFeatures};
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
import { ReplayConfig } from "../replay/config.mjs";
|
|
1
2
|
import { SessionResumeReason } from "../../internal/activity.mjs";
|
|
2
3
|
import { RecordExceptionOpts } from "../../internal/kernel.mjs";
|
|
3
4
|
import { NonErrorException } from "@interfere/types/sdk/errors";
|
|
4
5
|
import { PluginKey } from "@interfere/types/sdk/plugins/manifest";
|
|
5
|
-
|
|
6
6
|
//#region src/plugins/lib/types.d.ts
|
|
7
7
|
type PluginCleanup = () => void;
|
|
8
8
|
type PluginConfigState = "disabled" | "enabled" | "pending";
|
|
9
9
|
interface PluginContext {
|
|
10
10
|
getPluginConfigState(key: PluginKey): PluginConfigState;
|
|
11
|
+
getReplayConfig(): ReplayConfig;
|
|
11
12
|
getSessionId(): string;
|
|
13
|
+
getWindowId(): string;
|
|
12
14
|
onSessionPause(handler: () => void): () => void;
|
|
13
15
|
onSessionResume(handler: (reason: SessionResumeReason) => void): () => void;
|
|
14
16
|
recordException(value: Error | NonErrorException, opts: RecordExceptionOpts): void;
|
package/dist/plugins/logs.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{utf8ByteLength}from"./chunking.mjs";function createReplayBuffer(opts){let entries=[],totalBytes=0;return{push(event){let json=JSON.stringify(event),bytes=utf8ByteLength(json);entries.push({json,ts:Date.now(),bytes}),totalBytes+=bytes,totalBytes>=opts.softLimitBytes&&opts.onSoftLimit()},drain(){let drained=entries;return entries=[],totalBytes=0,drained}}}export{createReplayBuffer};
|
|
1
|
+
import{classifyReplayEvent,utf8ByteLength}from"./chunking.mjs";function createReplayBuffer(opts){let entries=[],totalBytes=0;return{push(event){let json=JSON.stringify(event),bytes=utf8ByteLength(json),{isActivity,isFullSnapshot}=classifyReplayEvent(event);entries.push({json,ts:Date.now(),bytes,isActivity,isFullSnapshot}),totalBytes+=bytes,totalBytes>=opts.softLimitBytes&&opts.onSoftLimit()},drain(){let drained=entries;return entries=[],totalBytes=0,drained}}}export{createReplayBuffer};
|
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
//#region src/plugins/replay/chunking.d.ts
|
|
2
2
|
interface BufferedReplayEvent {
|
|
3
3
|
bytes: number;
|
|
4
|
+
isActivity: boolean;
|
|
5
|
+
isFullSnapshot: boolean;
|
|
4
6
|
json: string;
|
|
5
7
|
ts: number;
|
|
6
8
|
}
|
|
7
9
|
interface ReplayChunkPayload {
|
|
10
|
+
activityCount: number;
|
|
8
11
|
bodyJson: string;
|
|
9
12
|
eventCount: number;
|
|
10
13
|
firstTs: number | null;
|
|
14
|
+
hasFullSnapshot: boolean;
|
|
11
15
|
lastTs: number | null;
|
|
12
16
|
}
|
|
13
17
|
interface SplitReplayEventsResult {
|
|
14
18
|
chunks: BufferedReplayEvent[][];
|
|
15
19
|
droppedEventBytes: number[];
|
|
16
20
|
}
|
|
21
|
+
declare function classifyReplayEvent(event: unknown): {
|
|
22
|
+
isActivity: boolean;
|
|
23
|
+
isFullSnapshot: boolean;
|
|
24
|
+
};
|
|
17
25
|
declare function toReplayChunkPayload(group: BufferedReplayEvent[]): ReplayChunkPayload;
|
|
18
26
|
declare function splitReplayEvents(buffered: BufferedReplayEvent[]): SplitReplayEventsResult;
|
|
19
27
|
declare function utf8ByteLength(value: string): number;
|
|
20
28
|
//#endregion
|
|
21
|
-
export { BufferedReplayEvent, ReplayChunkPayload, SplitReplayEventsResult, splitReplayEvents, toReplayChunkPayload, utf8ByteLength };
|
|
29
|
+
export { BufferedReplayEvent, ReplayChunkPayload, SplitReplayEventsResult, classifyReplayEvent, splitReplayEvents, toReplayChunkPayload, utf8ByteLength };
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
const textEncoder=new TextEncoder;function
|
|
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.source;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
|
+
`)}\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};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SetRequired } from "@interfere/helpers/set-required";
|
|
2
|
+
import { recordOptions } from "rrweb";
|
|
3
|
+
//#region src/plugins/replay/config.d.ts
|
|
4
|
+
declare const INTERFERE_MASK_TEXT_CLASS = "interfere-mask";
|
|
5
|
+
declare const INTERFERE_BLOCK_CLASS = "interfere-block";
|
|
6
|
+
declare const INTERFERE_IGNORE_CLASS = "interfere-ignore";
|
|
7
|
+
type ReplayConfig = Pick<recordOptions<unknown>, "blockClass" | "blockSelector" | "ignoreClass" | "ignoreSelector" | "maskAllInputs" | "maskInputFn" | "maskInputOptions" | "maskTextClass" | "maskTextFn" | "maskTextSelector" | "recordCanvas">;
|
|
8
|
+
type ReplayOption = boolean | ReplayConfig;
|
|
9
|
+
type ReplayRecordOptions = SetRequired<ReplayConfig, "maskTextClass" | "blockClass" | "ignoreClass">;
|
|
10
|
+
declare function toRecordOptions(config: ReplayConfig): ReplayRecordOptions;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { INTERFERE_BLOCK_CLASS, INTERFERE_IGNORE_CLASS, INTERFERE_MASK_TEXT_CLASS, ReplayConfig, ReplayOption, ReplayRecordOptions, toRecordOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const INTERFERE_MASK_TEXT_CLASS=`interfere-mask`,INTERFERE_BLOCK_CLASS=`interfere-block`,INTERFERE_IGNORE_CLASS=`interfere-ignore`;function toRecordOptions(config){return{...config,maskTextClass:INTERFERE_MASK_TEXT_CLASS,blockClass:INTERFERE_BLOCK_CLASS,ignoreClass:INTERFERE_IGNORE_CLASS}}export{INTERFERE_BLOCK_CLASS,INTERFERE_IGNORE_CLASS,INTERFERE_MASK_TEXT_CLASS,toRecordOptions};
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import { PluginContext } from "../lib/types.mjs";
|
|
2
2
|
import { ReplayChunkPayload } from "./chunking.mjs";
|
|
3
3
|
import { Compressor } from "./compressor-host.mjs";
|
|
4
|
-
|
|
5
4
|
//#region src/plugins/replay/delivery.d.ts
|
|
5
|
+
type DeliverResult = "ok" | "retryable" | "fatal";
|
|
6
|
+
interface ReplayChunkRef {
|
|
7
|
+
segmentId: number;
|
|
8
|
+
windowId: string;
|
|
9
|
+
}
|
|
6
10
|
interface DeliverInput {
|
|
7
11
|
authHeaders: Record<string, string>;
|
|
12
|
+
chunk: ReplayChunkRef;
|
|
8
13
|
compressor: Compressor;
|
|
9
14
|
ctx: PluginContext;
|
|
10
15
|
payload: ReplayChunkPayload;
|
|
11
16
|
skipCompression: boolean;
|
|
12
17
|
uploadBaseUrl: string;
|
|
13
18
|
}
|
|
14
|
-
declare function deliverChunk({
|
|
15
|
-
ctx,
|
|
16
|
-
uploadBaseUrl,
|
|
17
|
-
authHeaders,
|
|
18
|
-
compressor,
|
|
19
|
-
payload,
|
|
20
|
-
skipCompression
|
|
21
|
-
}: DeliverInput): Promise<void>;
|
|
19
|
+
declare function deliverChunk({ ctx, chunk, uploadBaseUrl, authHeaders, compressor, payload, skipCompression }: DeliverInput): Promise<DeliverResult>;
|
|
22
20
|
//#endregion
|
|
23
|
-
export { DeliverInput, deliverChunk };
|
|
21
|
+
export { DeliverInput, DeliverResult, ReplayChunkRef, deliverChunk };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"../../util/log.mjs";import{appendPathBeforeQuery}from"../../internal/config.mjs";import{trace}from"@opentelemetry/api";const log=createLogger(`replay`),textEncoder=new TextEncoder;async function prepareBody(compressor,rawBytes){if(!compressor.gzip)return{body:rawBytes,encoding:null};try{return{body:await compressor.gzip(rawBytes),encoding:`gzip`}}catch(error){return log.warn(`gzip failed for chunk; sending uncompressed: %o`,error),{body:rawBytes,encoding:null}}}async function deliverChunk({ctx,uploadBaseUrl,authHeaders,compressor,payload,skipCompression}){let sessionId=ctx.getSessionId();if(!sessionId)return
|
|
1
|
+
import{createLogger}from"../../util/log.mjs";import{appendPathBeforeQuery}from"../../internal/config.mjs";import{trace}from"@opentelemetry/api";const log=createLogger(`replay`),textEncoder=new TextEncoder;async function prepareBody(compressor,rawBytes){if(!compressor.gzip)return{body:rawBytes,encoding:null};try{return{body:await compressor.gzip(rawBytes),encoding:`gzip`}}catch(error){return log.warn(`gzip failed for chunk; sending uncompressed: %o`,error),{body:rawBytes,encoding:null}}}function encodeReplayMeta(chunk,payload){return btoa(JSON.stringify({windowId:chunk.windowId,segmentId:chunk.segmentId,firstTs:payload.firstTs,lastTs:payload.lastTs,eventCount:payload.eventCount,hasFullSnapshot:payload.hasFullSnapshot,activityCount:payload.activityCount}))}async function deliverChunk({ctx,chunk,uploadBaseUrl,authHeaders,compressor,payload,skipCompression}){let sessionId=ctx.getSessionId();if(!sessionId)return`ok`;let rawBytes=textEncoder.encode(payload.bodyJson),uncompressedBytes=rawBytes.byteLength,prepared=skipCompression?{body:rawBytes,encoding:null}:await prepareBody(compressor,rawBytes),headers={...authHeaders,"content-type":`application/json`,"x-replay-meta":encodeReplayMeta(chunk,payload),...prepared.encoding===`gzip`?{"content-encoding":`gzip`}:{}},res;try{res=await fetch(appendPathBeforeQuery(uploadBaseUrl,`/v2/replay/upload/${encodeURIComponent(sessionId)}`),{method:`POST`,headers,credentials:`omit`,body:prepared.body})}catch(error){return log.warn(`replay chunk upload failed: %o`,error),`retryable`}if(!res.ok)return log.warn(`replay chunk upload rejected (%d)`,res.status),res.status>=400&&res.status<500?`fatal`:`retryable`;let objectKey=await res.json().catch(()=>({}));return trace.getTracer(`@interfere/react/replay`).startSpan(`replay.chunk`,{attributes:{...objectKey.objectKey?{"replay.chunk.uri":objectKey.objectKey}:{},"replay.chunk.size_bytes":uncompressedBytes,...prepared.encoding===`gzip`?{"replay.chunk.compressed_bytes":prepared.body.byteLength}:{},"replay.chunk.event_count":payload.eventCount,...payload.firstTs===null?{}:{"replay.chunk.first_event_ts":payload.firstTs},...payload.lastTs===null?{}:{"replay.chunk.last_event_ts":payload.lastTs}}}).end(),`ok`}export{deliverChunk};
|
|
@@ -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{deliverChunk}from"./delivery.mjs";import{startReplayRecorder}from"./recorder.mjs";const log=createLogger(`replay`),replayPlugin={name:`replay`,setup(ctx){let targets=resolveTargets(),uploadBaseUrl=targets.collectorBaseUrl,authHeaders=Object.fromEntries(targets.ingest.headers.entries()),recorder=null,flushTimer=null,unsubscribeConfig=null,unsubscribeHidden=null,unsubscribeShown=null,unsubscribePause=null,unsubscribeResume=null,isPaused=!1,compressor=createCompressor();function flush(opts){if(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,uploadBaseUrl,authHeaders,compressor,payload:toReplayChunkPayload(group),skipCompression:opts?.skipCompression??!1}).catch(error=>log.warn(`replay chunk dropped: %o`,error))}let buffer=createReplayBuffer({softLimitBytes:4194304,onSoftLimit:flush}),pauseSending=unload=>{isPaused||(isPaused=!0,flush(unload?{skipCompression:!0}:void 0))},resumeSending=()=>{isPaused&&(isPaused=!1,recorder?.takeFullSnapshot())};return(async()=>{recorder=await startReplayRecorder(event=>{isPaused||buffer.push(event)}),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
|
+
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:toReplayChunkPayload(group),skipCompression:opts?.skipCompression??!1}).then(handleResult).catch(error=>{log.warn(`replay chunk dropped: %o`,error),handleResult(`retryable`)})}let buffer=createReplayBuffer({softLimitBytes:4194304,onSoftLimit:flush}),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,9 @@
|
|
|
1
|
+
import { ReplayRecordOptions } from "./config.mjs";
|
|
1
2
|
//#region src/plugins/replay/recorder.d.ts
|
|
2
3
|
interface ReplayRecorder {
|
|
3
4
|
stop(): void;
|
|
4
5
|
takeFullSnapshot(): void;
|
|
5
6
|
}
|
|
6
|
-
declare function startReplayRecorder(onEvent: (event: unknown) => void): Promise<ReplayRecorder | null>;
|
|
7
|
+
declare function startReplayRecorder(onEvent: (event: unknown) => void, maskingOptions: ReplayRecordOptions): Promise<ReplayRecorder | null>;
|
|
7
8
|
//#endregion
|
|
8
9
|
export { ReplayRecorder, startReplayRecorder };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"../../util/log.mjs";const log=createLogger(`replay`);async function startReplayRecorder(onEvent){try{let rrweb=await import(`rrweb`),stop=rrweb.record({emit:onEvent,recordAfter:`load`,inlineStylesheet:!0,slimDOMOptions:`all`,sampling:{mousemove:50,scroll:150,media:800}})??null;return{stop:()=>{stop?.()},takeFullSnapshot:()=>{try{rrweb.record.takeFullSnapshot?.(!0)}catch(error){log.warn(`takeFullSnapshot failed: %o`,error)}}}}catch{return log.error(`rrweb failed to load, replay disabled`),null}}export{startReplayRecorder};
|
|
1
|
+
import{createLogger}from"../../util/log.mjs";const log=createLogger(`replay`);async function startReplayRecorder(onEvent,maskingOptions){try{let rrweb=await import(`rrweb`),stop=rrweb.record({emit:onEvent,recordAfter:`load`,inlineStylesheet:!0,slimDOMOptions:`all`,checkoutEveryNms:6e4,sampling:{mousemove:50,scroll:150,media:800},...maskingOptions})??null;return{stop:()=>{stop?.()},takeFullSnapshot:()=>{try{rrweb.record.takeFullSnapshot?.(!0)}catch(error){log.warn(`takeFullSnapshot failed: %o`,error)}}}}catch{return log.error(`rrweb failed to load, replay disabled`),null}}export{startReplayRecorder};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function sessionStore(){try{let store=globalThis.sessionStorage,probe=`__interfere_replay_seq_probe__`;return store.setItem(probe,`1`),store.removeItem(probe),store}catch{return null}}function createSegmentSequence(windowId){let key=`interfere:replay_seq:${windowId}`,store=sessionStore(),stored=store?.getItem(key),next=stored?Number(stored):0;return(!Number.isInteger(next)||next<0)&&(next=0),{take(){let seq=next;return next+=1,store?.setItem(key,String(next)),seq}}}export{createSegmentSequence};
|
package/dist/provider.d.mts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
import { ReplayConfig, ReplayOption } from "./plugins/replay/config.mjs";
|
|
2
|
+
import { PluginOverrides } from "./plugins/lib/loader.mjs";
|
|
1
3
|
import { Kernel } from "./internal/kernel.mjs";
|
|
2
4
|
import { InterfereContext, InterfereContextValue } from "./internal/react-context.mjs";
|
|
3
5
|
import { PropsWithChildren, ReactNode } from "react";
|
|
4
6
|
import { ConsentState } from "@interfere/types/sdk/plugins/manifest";
|
|
5
|
-
|
|
6
7
|
//#region src/provider.d.ts
|
|
7
8
|
interface InterfereProviderProps extends PropsWithChildren {
|
|
9
|
+
/**
|
|
10
|
+
* Consent state gating which plugin categories may run. Omit (or pass
|
|
11
|
+
* `undefined`) to leave consent unmanaged — every category is allowed.
|
|
12
|
+
* This is the only place consent is configured; `init()` no longer
|
|
13
|
+
* accepts it.
|
|
14
|
+
*/
|
|
8
15
|
consent?: ConsentState | undefined;
|
|
9
16
|
/**
|
|
10
17
|
* Auto-install an internal capture boundary around `children` so
|
|
@@ -30,14 +37,23 @@ interface InterfereProviderProps extends PropsWithChildren {
|
|
|
30
37
|
* and switches to the real kernel once it's available.
|
|
31
38
|
*/
|
|
32
39
|
kernel: Kernel | null;
|
|
40
|
+
/**
|
|
41
|
+
* Per-plugin overrides, merged over the manifest defaults. Generic plugins
|
|
42
|
+
* are booleans (e.g. `{ pageEvents: false }`). `replay` additionally accepts
|
|
43
|
+
* a config object — `{ replay: { maskAllInputs: true } }` enables replay
|
|
44
|
+
* with that masking config; `{ replay: false }` disables it. Privacy classes
|
|
45
|
+
* are always applied, regardless of config: `interfere-block` elements are
|
|
46
|
+
* dropped and replay as a same-size placeholder, `interfere-mask` masks the
|
|
47
|
+
* text of an element and all its children, and `interfere-ignore` stops an
|
|
48
|
+
* element's input events from being recorded. `input[type="password"]` is
|
|
49
|
+
* masked by default; use `maskInputOptions`/`maskAllInputs` to mask other
|
|
50
|
+
* input values. `init()` no longer accepts plugin config; this is the only
|
|
51
|
+
* place it lives.
|
|
52
|
+
*/
|
|
53
|
+
plugins?: PluginOverrides | undefined;
|
|
33
54
|
}
|
|
34
|
-
declare function InterfereProvider({
|
|
35
|
-
kernel,
|
|
36
|
-
children,
|
|
37
|
-
consent,
|
|
38
|
-
errorBoundary
|
|
39
|
-
}: InterfereProviderProps): ReactNode;
|
|
55
|
+
declare function InterfereProvider({ kernel, children, consent, plugins, errorBoundary }: InterfereProviderProps): ReactNode;
|
|
40
56
|
declare function useInterfere(): InterfereContextValue;
|
|
41
57
|
declare function useSession(): string | null;
|
|
42
58
|
//#endregion
|
|
43
|
-
export { InterfereContext, type InterfereContextValue, InterfereProvider, useInterfere, useSession };
|
|
59
|
+
export { InterfereContext, type InterfereContextValue, InterfereProvider, type PluginOverrides, type ReplayConfig, type ReplayOption, useInterfere, useSession };
|
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,errorBoundary=!0}){return useEffect(()=>{kernel?.
|
|
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}){return useEffect(()=>{kernel?.syncProviderConfig({consent,plugins})},[kernel,consent,plugins]),jsx(InterfereContext,{value:kernel?{consent:kernel.consent,device:kernel.device,identity:kernel.identity,kernel,session:kernel.session}:NULL_CONTEXT_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
|
@@ -4,7 +4,6 @@ import { DeviceManager } from "./device.mjs";
|
|
|
4
4
|
import { GeoDetector } from "./geo.mjs";
|
|
5
5
|
import { SessionId } from "@interfere/types/data/session";
|
|
6
6
|
import { IdentifyParams } from "@interfere/types/sdk/identify";
|
|
7
|
-
|
|
8
7
|
//#region src/tracking/api.d.ts
|
|
9
8
|
interface SessionTrackerOptions {
|
|
10
9
|
device?: DeviceManager;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{v7}from"uuid";const SESSION_ID_KEY=`interfere:session_id`,LAST_ACTIVITY_KEY=`interfere:last_activity`,SESSION_STARTED_KEY=`interfere:session_started`,WINDOW_ID_KEY=`interfere:window_id`;function tryStorage(type){try{let s=globalThis[type],key=`__interfere_probe__`;return s.setItem(key,`1`),s.removeItem(key),s}catch{return null}}var SessionManager=class{sessionId=null;windowId=null;local;session;onRotate=null;lastActivityMs=0;sessionStartedMs=0;constructor(onRotate){this.local=tryStorage(`localStorage`),this.session=tryStorage(`sessionStorage`),this.onRotate=onRotate??null,this.restore()}getSessionId(){return this.sessionId?this.sessionId:this.rotate()}markActivity(){this.touch()}getSessionStartedAt(){return this.sessionStartedMs}getWindowId(){if(this.windowId)return this.windowId;let stored=this.session?.getItem(WINDOW_ID_KEY);return stored?(this.windowId=stored,stored):(this.windowId=`win_${crypto.randomUUID()}`,this.session?.setItem(WINDOW_ID_KEY,this.windowId),this.windowId)}reset(){return this.rotate()}restore(){let stored=this.local?.getItem(SESSION_ID_KEY),startedRaw=this.local?.getItem(SESSION_STARTED_KEY);this.sessionStartedMs=startedRaw?Number(startedRaw):0,stored&&!this.isExpired()?(this.sessionId=stored,this.touch()):this.rotate()}rotate(){return this.sessionId=v7(),this.sessionStartedMs=Date.now(),this.local?.setItem(SESSION_ID_KEY,this.sessionId),this.local?.setItem(SESSION_STARTED_KEY,String(this.sessionStartedMs)),this.touch(),this.onRotate?.(this.sessionId),this.sessionId}touch(){this.lastActivityMs=Date.now(),this.local?.setItem(LAST_ACTIVITY_KEY,String(this.lastActivityMs))}isExpired(){if(this.sessionStartedMs>0&&Date.now()-this.sessionStartedMs>864e5)return!0;let raw=this.local?.getItem(LAST_ACTIVITY_KEY),ts=raw?Number(raw):this.lastActivityMs;return ts===0
|
|
1
|
+
import{v7}from"uuid";const SESSION_ID_KEY=`interfere:session_id`,LAST_ACTIVITY_KEY=`interfere:last_activity`,SESSION_STARTED_KEY=`interfere:session_started`,WINDOW_ID_KEY=`interfere:window_id`;function tryStorage(type){try{let s=globalThis[type],key=`__interfere_probe__`;return s.setItem(key,`1`),s.removeItem(key),s}catch{return null}}var SessionManager=class{sessionId=null;windowId=null;local;session;onRotate=null;lastActivityMs=0;sessionStartedMs=0;constructor(onRotate){this.local=tryStorage(`localStorage`),this.session=tryStorage(`sessionStorage`),this.onRotate=onRotate??null,this.restore()}getSessionId(){return this.sessionId?this.sessionId:this.rotate()}markActivity(){this.touch()}getSessionStartedAt(){return this.sessionStartedMs}getWindowId(){if(this.windowId)return this.windowId;let stored=this.session?.getItem(WINDOW_ID_KEY);return stored?(this.windowId=stored,stored):(this.windowId=`win_${crypto.randomUUID()}`,this.session?.setItem(WINDOW_ID_KEY,this.windowId),this.windowId)}reset(){return this.rotate()}restore(){let stored=this.local?.getItem(SESSION_ID_KEY),startedRaw=this.local?.getItem(SESSION_STARTED_KEY);this.sessionStartedMs=startedRaw?Number(startedRaw):0,stored&&!this.isExpired()?(this.sessionId=stored,this.touch()):this.rotate()}rotate(){return this.sessionId=v7(),this.sessionStartedMs=Date.now(),this.local?.setItem(SESSION_ID_KEY,this.sessionId),this.local?.setItem(SESSION_STARTED_KEY,String(this.sessionStartedMs)),this.touch(),this.onRotate?.(this.sessionId),this.sessionId}touch(){this.lastActivityMs=Date.now(),this.local?.setItem(LAST_ACTIVITY_KEY,String(this.lastActivityMs))}isExpired(){if(this.sessionStartedMs>0&&Date.now()-this.sessionStartedMs>864e5)return!0;let raw=this.local?.getItem(LAST_ACTIVITY_KEY),ts=raw?Number(raw):this.lastActivityMs;return ts===0||Date.now()-ts>9e5}};export{SessionManager};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@interfere/react",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Client-side React SDK for Interfere. Error tracking, session replay, and analytics.",
|
|
6
6
|
"keywords": [
|
|
@@ -87,8 +87,9 @@
|
|
|
87
87
|
},
|
|
88
88
|
"dependencies": {
|
|
89
89
|
"@fingerprintjs/fingerprintjs": "^5.2.0",
|
|
90
|
-
"@interfere/constants": "^9.0.
|
|
91
|
-
"@interfere/
|
|
90
|
+
"@interfere/constants": "^9.0.3",
|
|
91
|
+
"@interfere/helpers": "^9.0.3",
|
|
92
|
+
"@interfere/types": "^10.0.1",
|
|
92
93
|
"@opentelemetry/api": "^1.9.1",
|
|
93
94
|
"@opentelemetry/api-logs": "^0.219.0",
|
|
94
95
|
"@opentelemetry/context-zone": "^2.7.1",
|
|
@@ -108,6 +109,7 @@
|
|
|
108
109
|
"@opentelemetry/sdk-trace-base": "^2.7.0",
|
|
109
110
|
"@opentelemetry/sdk-trace-web": "^2.7.1",
|
|
110
111
|
"@opentelemetry/web-common": "^0.219.0",
|
|
112
|
+
"@rrweb/types": "2.1.0",
|
|
111
113
|
"@ua-parser-js/pro-enterprise": "^2.0.6",
|
|
112
114
|
"rrweb": "2.1.0",
|
|
113
115
|
"uuid": "^14.0.1",
|
|
@@ -127,8 +129,8 @@
|
|
|
127
129
|
}
|
|
128
130
|
},
|
|
129
131
|
"devDependencies": {
|
|
130
|
-
"@interfere/test-utils": "^9.0.
|
|
131
|
-
"@interfere/typescript-config": "^9.0.1
|
|
132
|
+
"@interfere/test-utils": "^9.0.4",
|
|
133
|
+
"@interfere/typescript-config": "^9.0.1",
|
|
132
134
|
"@types/node": "^26.1.0",
|
|
133
135
|
"@types/react": "19.2.17",
|
|
134
136
|
"@types/react-dom": "19.2.3",
|