@interfere/react 11.1.1 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/activity-BfYxFVcK.d.mts +36 -0
- package/dist/api-wpS4xoMY.d.mts +70 -0
- package/dist/api.d.mts +8 -7
- package/dist/api.mjs +1 -1
- package/dist/config-Rm-ct4K1.d.mts +12 -0
- package/dist/device-vFtWy12y.d.mts +35 -0
- package/dist/error-boundary.d.mts +7 -7
- package/dist/error-boundary.mjs +1 -1
- package/dist/exporter-logs-CeGwMiMo.d.mts +51 -0
- package/dist/geo-PDfIZY4L.d.mts +16 -0
- package/dist/index-CatPKbtv.d.mts +4 -0
- package/dist/instrumentations-Bgm2EazU.d.mts +91 -0
- package/dist/internal/activity.d.mts +1 -35
- package/dist/internal/activity.mjs +1 -1
- package/dist/internal/browser-context.d.mts +44 -4
- package/dist/internal/browser-context.mjs +1 -1
- package/dist/internal/capture-boundary.d.mts +6 -5
- package/dist/internal/capture-boundary.mjs +1 -1
- package/dist/internal/capture.d.mts +3 -4
- package/dist/internal/capture.mjs +1 -1
- package/dist/internal/config.d.mts +26 -27
- package/dist/internal/config.mjs +1 -1
- package/dist/internal/consent.d.mts +16 -8
- package/dist/internal/console-patch.d.mts +6 -7
- package/dist/internal/console-patch.mjs +1 -1
- package/dist/internal/dom/actionable.d.mts +5 -7
- package/dist/internal/dom/actionable.mjs +1 -1
- package/dist/internal/hydration.d.mts +2 -3
- package/dist/internal/kernel-registry.d.mts +5 -6
- package/dist/internal/kernel.d.mts +2 -292
- package/dist/internal/kernel.mjs +1 -1
- package/dist/internal/otel/exporter-logs.d.mts +1 -12
- package/dist/internal/otel/exporter-logs.mjs +1 -1
- package/dist/internal/otel/exporter-metrics.d.mts +2 -14
- package/dist/internal/otel/exporter-metrics.mjs +1 -1
- package/dist/internal/otel/exporter.d.mts +2 -29
- package/dist/internal/otel/exporter.mjs +1 -1
- package/dist/internal/otel/http-span-route.d.mts +4 -0
- package/dist/internal/otel/http-span-route.mjs +1 -0
- package/dist/internal/otel/index.d.mts +7 -5
- package/dist/internal/otel/index.mjs +1 -1
- package/dist/internal/otel/initial-navigation.d.mts +3 -4
- package/dist/internal/otel/initial-navigation.mjs +1 -1
- package/dist/internal/otel/instrumentations.d.mts +2 -89
- package/dist/internal/otel/instrumentations.mjs +1 -1
- package/dist/internal/otel/navigation.d.mts +2 -0
- package/dist/internal/otel/navigation.mjs +1 -0
- package/dist/internal/otel/normalize-path.d.mts +9 -0
- package/dist/internal/otel/normalize-path.mjs +1 -0
- package/dist/internal/otel/page-scope-context-manager.d.mts +2 -31
- package/dist/internal/otel/page-scope-context-manager.mjs +1 -1
- package/dist/internal/otel/propagation.d.mts +1 -19
- package/dist/internal/otel/propagation.mjs +1 -1
- package/dist/internal/otel/provider.d.mts +1 -86
- package/dist/internal/otel/provider.mjs +1 -1
- package/dist/internal/otel/url-attrs.d.mts +7 -0
- package/dist/internal/otel/url-attrs.mjs +1 -0
- package/dist/internal/otel/web-vitals.d.mts +1 -33
- package/dist/internal/otel/web-vitals.mjs +1 -1
- package/dist/internal/page-lifecycle.d.mts +4 -5
- package/dist/internal/page-lifecycle.mjs +1 -1
- package/dist/internal/plugin-runtime.d.mts +1 -38
- package/dist/internal/plugin-runtime.mjs +1 -1
- package/dist/internal/react-context.d.mts +1 -43
- package/dist/internal/react-context.mjs +1 -1
- package/dist/internal/server-digest.d.mts +4 -3
- package/dist/internal/server-digest.mjs +1 -1
- package/dist/internal/sw-queue.d.mts +4 -5
- package/dist/internal/sw-queue.mjs +1 -1
- package/dist/internal/third-party-hosts.d.mts +2 -3
- package/dist/internal/third-party-hosts.mjs +1 -1
- package/dist/internal/version.d.mts +4 -5
- package/dist/internal/version.mjs +1 -1
- package/dist/internal/wrapper-singleton.d.mts +5 -6
- package/dist/internal/wrapper-singleton.mjs +1 -1
- package/dist/navigation-CkiV6lJY.d.mts +54 -0
- package/dist/page-scope-context-manager-r92ZMJok.d.mts +17 -0
- package/dist/plugins/device.d.mts +5 -0
- package/dist/plugins/device.mjs +1 -0
- package/dist/plugins/errors.d.mts +1 -1
- package/dist/plugins/errors.mjs +1 -1
- package/dist/plugins/lib/loader.d.mts +2 -11
- package/dist/plugins/lib/loader.mjs +1 -1
- package/dist/plugins/lib/types.d.mts +2 -24
- package/dist/plugins/logs.d.mts +1 -1
- package/dist/plugins/logs.mjs +1 -1
- package/dist/plugins/page-events.d.mts +5 -0
- package/dist/plugins/page-events.mjs +1 -0
- package/dist/plugins/rage-clicks.d.mts +1 -1
- package/dist/plugins/rage-clicks.mjs +1 -1
- package/dist/plugins/replay/buffer.d.mts +9 -6
- package/dist/plugins/replay/buffer.mjs +1 -1
- package/dist/plugins/replay/chunking.d.mts +18 -9
- package/dist/plugins/replay/chunking.mjs +1 -1
- package/dist/plugins/replay/compressor-host.d.mts +3 -4
- package/dist/plugins/replay/compressor-host.mjs +1 -1
- package/dist/plugins/replay/config.d.mts +1 -11
- package/dist/plugins/replay/config.mjs +1 -1
- package/dist/plugins/replay/delivery.d.mts +8 -9
- package/dist/plugins/replay/delivery.mjs +1 -1
- package/dist/plugins/replay/index.d.mts +1 -1
- package/dist/plugins/replay/index.mjs +1 -1
- package/dist/plugins/replay/recorder.d.mts +12 -6
- package/dist/plugins/replay/recorder.mjs +1 -1
- package/dist/plugins/replay/sequence.d.mts +5 -4
- package/dist/plugins/replay/sequence.mjs +1 -1
- package/dist/plugins/replay/upload.d.mts +4 -0
- package/dist/plugins/replay/upload.mjs +1 -0
- package/dist/propagation-CssinKCy.d.mts +26 -0
- package/dist/provider-50LbJ4Tl.d.mts +466 -0
- package/dist/provider-Cp6noibq.d.mts +101 -0
- package/dist/provider.d.mts +2 -58
- package/dist/provider.mjs +1 -1
- package/dist/react-error-handler.d.mts +9 -11
- package/dist/react-error-handler.mjs +1 -1
- package/dist/rrweb-CE6GKCEq.mjs +150 -0
- package/dist/tracking/api.d.mts +1 -54
- package/dist/tracking/api.mjs +1 -1
- package/dist/tracking/device.d.mts +1 -32
- package/dist/tracking/device.mjs +1 -1
- package/dist/tracking/geo.d.mts +1 -13
- package/dist/tracking/geo.mjs +1 -1
- package/dist/tracking/session.d.mts +6 -7
- package/dist/tracking/session.mjs +1 -1
- package/dist/util/bot.d.mts +2 -3
- package/dist/util/bot.mjs +1 -1
- package/dist/util/global.d.mts +4 -10
- package/dist/util/global.mjs +1 -1
- package/dist/util/log.d.mts +11 -7
- package/dist/util/log.mjs +1 -1
- package/dist/util/stringify.d.mts +2 -3
- package/dist/version-BniHrwLD.mjs +1 -0
- package/dist/web-vitals-x8y70B5x.d.mts +34 -0
- package/package.json +67 -48
- package/dist/package.mjs +0 -1
|
@@ -1,89 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
|
|
4
|
-
//#region src/internal/otel/instrumentations.d.ts
|
|
5
|
-
interface FetchErrorShape {
|
|
6
|
-
message: string;
|
|
7
|
-
status?: number;
|
|
8
|
-
}
|
|
9
|
-
type FetchSpanRequest = Request | RequestInfo | RequestInit | URL;
|
|
10
|
-
/**
|
|
11
|
-
* Inclusive HTTP status range. `[400, 499]` matches every 4xx;
|
|
12
|
-
* a bare number matches that exact code.
|
|
13
|
-
*/
|
|
14
|
-
type HttpStatusCodeRange = number | readonly [number, number];
|
|
15
|
-
interface CaptureFailedRequestsOptions {
|
|
16
|
-
/** Defaults to `[[400, 599]]` (4xx + 5xx) when not specified. */
|
|
17
|
-
statusCodes?: readonly HttpStatusCodeRange[];
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Promote tracked failed fetch/XHR responses to captured exception events.
|
|
21
|
-
* On by default (same-origin 4xx + 5xx, deduped + rate-limited); `false`
|
|
22
|
-
* disables, `{ statusCodes }` tunes the matched ranges.
|
|
23
|
-
*/
|
|
24
|
-
type CaptureFailedRequestsOption = boolean | CaptureFailedRequestsOptions;
|
|
25
|
-
declare function resolveFailureRanges(opt: CaptureFailedRequestsOption | undefined): readonly HttpStatusCodeRange[] | null;
|
|
26
|
-
/**
|
|
27
|
-
* Per-instrumentation gate that keeps failed-request capture low-noise:
|
|
28
|
-
* dedupes by `method+status+path` (so a flapping endpoint is captured once)
|
|
29
|
-
* and rate-limits to {@link FAILURE_RATE_MAX} captures per
|
|
30
|
-
* {@link FAILURE_RATE_WINDOW_MS} as a flood backstop. State lives in the
|
|
31
|
-
* closure — one gate per kernel instance, no module-level mutable state.
|
|
32
|
-
*/
|
|
33
|
-
declare function createFailureGate(): (key: string) => boolean;
|
|
34
|
-
type NetworkErrorKind = "abort" | "network" | "timeout";
|
|
35
|
-
declare function classifyNetworkError(message: string): NetworkErrorKind;
|
|
36
|
-
declare function applyFetchSpanAttrs(span: Span, request: FetchSpanRequest, result: Response | FetchErrorShape, failureRanges: readonly HttpStatusCodeRange[] | null, isTrackedUrl: (url: string) => boolean, resolveRoute: ((pathname: string) => string | undefined) | undefined, gate?: (key: string) => boolean): void;
|
|
37
|
-
declare function applyXhrSpanAttrs(span: Span, xhr: Pick<XMLHttpRequest, "responseURL" | "status">, failureRanges: readonly HttpStatusCodeRange[] | null, isTrackedUrl: (url: string) => boolean, gate?: (key: string) => boolean): void;
|
|
38
|
-
declare const _testOnlyApplyFetchSpanAttrs: typeof applyFetchSpanAttrs;
|
|
39
|
-
declare const _testOnlyApplyXhrSpanAttrs: typeof applyXhrSpanAttrs;
|
|
40
|
-
declare const _testOnlyResolveFailureRanges: typeof resolveFailureRanges;
|
|
41
|
-
declare const _testOnlyCreateFailureGate: typeof createFailureGate;
|
|
42
|
-
declare const _testOnlyClassifyNetworkError: typeof classifyNetworkError;
|
|
43
|
-
interface InstrumentationsInput {
|
|
44
|
-
/**
|
|
45
|
-
* Hostnames the fetch + XHR instrumentations inject `traceparent` onto,
|
|
46
|
-
* matched by host (any path/port/scheme). Same-origin always propagates.
|
|
47
|
-
*/
|
|
48
|
-
apiHosts?: string[];
|
|
49
|
-
/** See {@link CaptureFailedRequestsOption}. On by default. */
|
|
50
|
-
captureFailedRequests?: CaptureFailedRequestsOption;
|
|
51
|
-
/**
|
|
52
|
-
* URLs the SDK exempts from `fetch` and `XHR` instrumentation —
|
|
53
|
-
* typically our own ingest/OTLP endpoints, so the SDK doesn't trace
|
|
54
|
-
* its own export requests in an infinite loop. Combined with the
|
|
55
|
-
* `THIRD_PARTY_IGNORE` defaults and the customer's `ignoreUrls`.
|
|
56
|
-
*/
|
|
57
|
-
ignoreUrls: (string | RegExp)[];
|
|
58
|
-
loggerProvider?: LoggerProvider;
|
|
59
|
-
/**
|
|
60
|
-
* Customer-supplied URL patterns the fetch + XHR instrumentations
|
|
61
|
-
* inject `traceparent` + `baggage` headers on. Same-origin requests
|
|
62
|
-
* always propagate.
|
|
63
|
-
*/
|
|
64
|
-
propagateContextUrls?: (string | RegExp)[];
|
|
65
|
-
/**
|
|
66
|
-
* Pathname → low-cardinality route template (e.g. `/blog/[slug]`).
|
|
67
|
-
* Drives span renaming + `url.path` / `http.route` attrs across
|
|
68
|
-
* fetch, document-load, user-interaction, and long-task.
|
|
69
|
-
*/
|
|
70
|
-
resolveRoute?: (pathname: string) => string | undefined;
|
|
71
|
-
/**
|
|
72
|
-
* The kernel's private provider — instrumentations register against
|
|
73
|
-
* it, not the global one, so customer OTel setups stay untouched.
|
|
74
|
-
*/
|
|
75
|
-
tracerProvider: WebTracerProvider;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Registers every browser auto-instrumentation against the kernel's
|
|
79
|
-
* private provider. Every instrumentation is enriched with the same
|
|
80
|
-
* route-template-aware URL attributes so dashboards can slice fetch /
|
|
81
|
-
* interaction / long-task / resource-load by `http.route` without
|
|
82
|
-
* cardinality blow-up from raw pathnames.
|
|
83
|
-
*
|
|
84
|
-
* Returns a disposer that unregisters everything; called by
|
|
85
|
-
* `kernel.dispose()`.
|
|
86
|
-
*/
|
|
87
|
-
declare function registerBundledInstrumentations(input: InstrumentationsInput): () => void;
|
|
88
|
-
//#endregion
|
|
89
|
-
export { CaptureFailedRequestsOption, CaptureFailedRequestsOptions, HttpStatusCodeRange, InstrumentationsInput, _testOnlyApplyFetchSpanAttrs, _testOnlyApplyXhrSpanAttrs, _testOnlyClassifyNetworkError, _testOnlyCreateFailureGate, _testOnlyResolveFailureRanges, registerBundledInstrumentations };
|
|
1
|
+
import { a as InstrumentationsInput, c as testOnlyApplyXhrSpanAttrs, d as testOnlyResolveFailureRanges, i as HttpStatusCodeRange, l as testOnlyClassifyNetworkError, n as CaptureFailedRequestsOption, o as registerBundledInstrumentations, r as CaptureFailedRequestsOptions, s as testOnlyApplyFetchSpanAttrs, t as BundledInstrumentations, u as testOnlyCreateFailureGate } from "../../instrumentations-Bgm2EazU.mjs";
|
|
2
|
+
export { BundledInstrumentations, CaptureFailedRequestsOption, CaptureFailedRequestsOptions, HttpStatusCodeRange, InstrumentationsInput, registerBundledInstrumentations, testOnlyApplyFetchSpanAttrs, testOnlyApplyXhrSpanAttrs, testOnlyClassifyNetworkError, testOnlyCreateFailureGate, testOnlyResolveFailureRanges };
|
|
@@ -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/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};
|
|
1
|
+
import{THIRD_PARTY_IGNORE}from"../third-party-hosts.mjs";import{ATTR_URL_FULL,ATTR_URL_PATH,stampUrlAttrs}from"./url-attrs.mjs";import{stampHttpSpanRoute}from"./http-span-route.mjs";import{describeActionable,isActionable}from"../dom/actionable.mjs";import{MECHANISM_TYPE}from"@interfere/types/sdk/errors";import{omitUndefined}from"@interfere/helpers/omit-undefined";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 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.message":`HTTP ${status} ${method} ${url}`,"exception.type":`HttpResponseError`,"interfere.exception.handled":!1,"interfere.exception.kind":`error`,"interfere.exception.mechanism":mechanism})}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.message":`Network ${kind} ${method} ${url}`,"exception.type":`NetworkError`,"interfere.exception.handled":!1,"interfere.exception.kind":`error`,"interfere.exception.mechanism":mechanism,"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);tracked&&(stampHttpSpanRoute(span,method,parsed.pathname,resolveRoute),result instanceof Response?maybeCaptureFetchFailure(span,result,failureRanges,method,url,gate):maybeCaptureNetworkFailure(span,result,failureRanges,method,url,gate))}function applyXhrSpanAttrs(span,xhr,failureRanges,isTrackedUrl,gate,resolveRoute){let url=xhr.responseURL||spanUrlFull(span);if(!(url&&isTrackedUrl(url)))return;let parsed;try{parsed=new URL(url)}catch{return}let method=isReadableSpan(span)?String(span.attributes[`http.request.method`]??span.attributes[`http.method`]??`XHR`):`XHR`;if(span.setAttribute(ATTR_URL_PATH,parsed.pathname),stampHttpSpanRoute(span,method,parsed.pathname,resolveRoute),!failureRanges)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[`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(!(typeof value==`object`&&value&&key in value))return null;let prop=Reflect.get(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}=globalThis.location;return RegExp(`^${protocol}//${host.replace(ESCAPE_RE,String.raw`\$&`)}`,`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)}const PREFETCH_HEADER_NAMES=[`next-router-prefetch`,`next-router-segment-prefetch`];function isPrefetchRequest(request){let headers;try{headers=typeof Request<`u`&&request instanceof Request?request.headers:new Headers(request.headers)}catch{return!1}return PREFETCH_HEADER_NAMES.some(name=>headers.has(name))||headers.get(`purpose`)===`prefetch`||headers.get(`sec-purpose`)?.includes(`prefetch`)===!0}function escapeRegExp(value){return value.replaceAll(/[.*+?^${}()|[\]\\]/gu,String.raw`\$&`)}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(host=>apiHostToMatcher(host)),...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))}let pageEventsEnabled=!1,coreInstrumentations=[new FetchInstrumentation({applyCustomAttributesOnSpan:(span,request,result)=>{applyFetchSpanAttrs(span,request,result,failureRanges,isTrackedUrl,resolveRoute,failureGate)},ignoreUrls:fetchIgnore,measureRequestSize:!0,propagateTraceHeaderCorsUrls:[origin,...propagateMatchers],requestHook:(span,request)=>{isPrefetchRequest(request)&&span.setAttribute(`http.request.prefetch`,!0)}}),new XMLHttpRequestInstrumentation({applyCustomAttributesOnSpan:(span,xhr)=>{applyXhrSpanAttrs(span,xhr,failureRanges,isTrackedUrl,failureGate,resolveRoute)},ignoreUrls:fetchIgnore,propagateTraceHeaderCorsUrls:[origin,...propagateMatchers]}),new LongTaskInstrumentation({observerCallback:(span,info)=>{stampUrlAttrs(span,resolveRoute);let containerType=info.longtaskEntry.attribution[0]?.containerType;containerType&&span.setAttribute(`longtask.container_type`,containerType)}}),new UserInteractionInstrumentation({eventNames:[`click`,`submit`],shouldPreventSpanCreation:(eventType,element,span)=>!pageEventsEnabled||eventType===`click`&&!isActionable(element)||(stampInteractionAttrs(eventType,element,span),stampUrlAttrs(span,resolveRoute),!1)})],pageEventsInstrumentations=[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!==globalThis.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)}},enabled:!1}),new BrowserNavigationInstrumentation({enabled:!1,sanitizeUrl:defaultSanitizeUrl})],unregisterCore=registerInstrumentations(omitUndefined({tracerProvider,loggerProvider,instrumentations:coreInstrumentations})),unregisterPageEvents=null,setPageEventsEnabled=enabled=>{if(pageEventsEnabled=enabled,enabled){unregisterPageEvents??=registerInstrumentations(omitUndefined({tracerProvider,loggerProvider,instrumentations:pageEventsInstrumentations}));return}unregisterPageEvents?.(),unregisterPageEvents=null};return{dispose:()=>{setPageEventsEnabled(!1),unregisterCore()},setPageEventsEnabled}}export{registerBundledInstrumentations,testOnlyApplyFetchSpanAttrs,testOnlyApplyXhrSpanAttrs,testOnlyClassifyNetworkError,testOnlyCreateFailureGate,testOnlyResolveFailureRanges};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as seedPageScope, i as installNavigationScope, n as PAGE_SCOPE_MAX_AGE_MS, r as createPageScopeRotator, t as NavigationScopeInput } from "../../navigation-CkiV6lJY.mjs";
|
|
2
|
+
export { NavigationScopeInput, PAGE_SCOPE_MAX_AGE_MS, createPageScopeRotator, installNavigationScope, seedPageScope };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{stampUrlAttrs}from"./url-attrs.mjs";import{ROOT_CONTEXT,trace}from"@opentelemetry/api";const PAGE_SCOPE_MAX_AGE_MS=18e5;function startScopeContext(input,trigger,options={}){let span=input.tracer.startSpan(`navigation`,{...options,attributes:{"navigation.trigger":trigger}},ROOT_CONTEXT);return stampUrlAttrs(span,input.resolveRoute),span.end(),trace.setSpan(ROOT_CONTEXT,span)}function seedPageScope(input){return startScopeContext(input,`pageload`,{startTime:0})}function createPageScopeRotator(input){return(previous=>{let previousSpan=trace.getSpan(previous);return startScopeContext(input,`max_age`,{links:previousSpan?[{context:previousSpan.spanContext()}]:[]})})}function comparableHref(href){let url=new URL(href),pathname=url.pathname!==`/`&&url.pathname.endsWith(`/`)?url.pathname.slice(0,-1):url.pathname;return`${url.origin}${pathname}${url.search}`}function installNavigationScope(input){if(typeof window>`u`)return(()=>{});let lastHref=comparableHref(globalThis.location.href),onNavigate=trigger=>{let current=comparableHref(globalThis.location.href);current!==lastHref&&(lastHref=current,input.setPageScope(startScopeContext(input,trigger)))},originalPushState=globalThis.history.pushState,originalReplaceState=globalThis.history.replaceState;globalThis.history.pushState=function(...args){originalPushState.apply(this,args),onNavigate(`push`)},globalThis.history.replaceState=function(...args){originalReplaceState.apply(this,args),onNavigate(`replace`)};let onPopstate=()=>{onNavigate(`popstate`)};return globalThis.addEventListener(`popstate`,onPopstate),(()=>{globalThis.history.pushState=originalPushState,globalThis.history.replaceState=originalReplaceState,globalThis.removeEventListener(`popstate`,onPopstate)})}export{PAGE_SCOPE_MAX_AGE_MS,createPageScopeRotator,installNavigationScope,seedPageScope};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/internal/otel/normalize-path.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Low-cardinality fallback for span names when the app supplies no
|
|
4
|
+
* `resolveRoute` template: UUIDs → `{id}`, 16+-char hex runs → `{hash}`,
|
|
5
|
+
* 2+-digit runs → `{n}`. MUST stay in lockstep with `NORMALIZED_PATH` in
|
|
6
|
+
* the tinybird request-failures pipe — the two sides group by this shape.
|
|
7
|
+
*/
|
|
8
|
+
export declare function normalizePath(pathname: string): string;
|
|
9
|
+
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const UUID_SEGMENT_RE=/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/gu,LONG_HEX_RE=/[0-9a-fA-F]{16,}/gu,DIGIT_RUN_RE=/[0-9]{2,}/gu;function normalizePath(pathname){return pathname.replaceAll(UUID_SEGMENT_RE,`{id}`).replaceAll(LONG_HEX_RE,`{hash}`).replaceAll(DIGIT_RUN_RE,`{n}`)}export{normalizePath};
|
|
@@ -1,31 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
//#region src/internal/otel/page-scope-context-manager.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* `ZoneContextManager` returns `ROOT_CONTEXT` whenever `context.active()` is
|
|
6
|
-
* called outside a zone the manager itself created — which in practice is
|
|
7
|
-
* most of the time:
|
|
8
|
-
*
|
|
9
|
-
* - app code calling `fetch(...)` from a `useEffect`
|
|
10
|
-
* - `PerformanceObserver` callbacks (long-task, paint timing, …)
|
|
11
|
-
* - top-level await / module init code
|
|
12
|
-
* - listeners attached before the SDK booted
|
|
13
|
-
*
|
|
14
|
-
* Spans created from those code paths default to "no parent" → root, which
|
|
15
|
-
* is why a single page load produces N disjoint traces (one per
|
|
16
|
-
* instrumentation) instead of one waterfall.
|
|
17
|
-
*
|
|
18
|
-
* This subclass adds a single fallback: if the underlying zone lookup yields
|
|
19
|
-
* `ROOT_CONTEXT`, return the page-scope context instead. The page-scope
|
|
20
|
-
* context is built once at SDK init from the `<meta name="traceparent">`
|
|
21
|
-
* tag emitted by the SSR layout. If the meta tag is absent the page scope
|
|
22
|
-
* stays at `ROOT_CONTEXT` and the manager behaves like a stock
|
|
23
|
-
* `ZoneContextManager`.
|
|
24
|
-
*/
|
|
25
|
-
declare class PageScopeContextManager extends ZoneContextManager {
|
|
26
|
-
private pageScope;
|
|
27
|
-
setPageScope(ctx: Context): void;
|
|
28
|
-
active(): Context;
|
|
29
|
-
}
|
|
30
|
-
//#endregion
|
|
31
|
-
export { PageScopeContextManager };
|
|
1
|
+
import { n as PageScopeRotator, t as PageScopeContextManager } from "../../page-scope-context-manager-r92ZMJok.mjs";
|
|
2
|
+
export { PageScopeContextManager, PageScopeRotator };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ROOT_CONTEXT}from"@opentelemetry/api";import{ZoneContextManager}from"@opentelemetry/context-zone";var PageScopeContextManager=class extends ZoneContextManager{pageScope=ROOT_CONTEXT;setPageScope(ctx){this.pageScope=ctx}active(){let ctx=super.active();return ctx===ROOT_CONTEXT
|
|
1
|
+
import{ROOT_CONTEXT,trace}from"@opentelemetry/api";import{ZoneContextManager}from"@opentelemetry/context-zone";var PageScopeContextManager=class extends ZoneContextManager{pageScope=ROOT_CONTEXT;pageSpans=new WeakSet;pageScopeSetAtMs=0;rotator=null;maxAgeMs=1/0;rotating=!1;setPageScope(ctx){this.pageScope=ctx,this.pageScopeSetAtMs=Date.now();let span=trace.getSpan(ctx);span&&this.pageSpans.add(span)}setRotation(rotator,maxAgeMs){this.rotator=rotator,this.maxAgeMs=maxAgeMs}active(){let ctx=super.active(),span=trace.getSpan(ctx);if(span&&!this.pageSpans.has(span))return ctx;if(this.pageScope!==ROOT_CONTEXT&&this.rotator&&!this.rotating&&Date.now()-this.pageScopeSetAtMs>this.maxAgeMs){this.rotating=!0;try{this.setPageScope(this.rotator(this.pageScope))}finally{this.rotating=!1}}if(ctx===ROOT_CONTEXT)return this.pageScope;let pageSpan=trace.getSpan(this.pageScope);return pageSpan?trace.setSpan(ctx,pageSpan):ctx}};export{PageScopeContextManager};
|
|
@@ -1,20 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region src/internal/otel/propagation.d.ts
|
|
3
|
-
/**
|
|
4
|
-
* Reads the W3C `traceparent` (and optional `tracestate` / `baggage`) meta
|
|
5
|
-
* tags from the document head and extracts an OTel `Context`. SSR renderers (Next, Vite,
|
|
6
|
-
* …) inject the server-side request span's context so the client SDK can
|
|
7
|
-
* stitch its spans onto the same trace.
|
|
8
|
-
*
|
|
9
|
-
* Returns `null` when:
|
|
10
|
-
* - called server-side (no `document`)
|
|
11
|
-
* - no `traceparent` meta tag is present
|
|
12
|
-
* - the extracted context has an invalid trace id
|
|
13
|
-
* - the `sampled` flag is unset — the browser doesn't honor the bit when
|
|
14
|
-
* creating child spans, so an unsampled parent would orphan every
|
|
15
|
-
* browser span under a trace with no recorded server segments. Drop
|
|
16
|
-
* instead so spans fall back to a fresh root.
|
|
17
|
-
*/
|
|
18
|
-
declare function readPropagationFromDocument(): Context | null;
|
|
19
|
-
//#endregion
|
|
1
|
+
import { t as readPropagationFromDocument } from "../../propagation-CssinKCy.mjs";
|
|
20
2
|
export { readPropagationFromDocument };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ROOT_CONTEXT,defaultTextMapGetter,trace}from"@opentelemetry/api";import{W3CBaggagePropagator,W3CTraceContextPropagator}from"@opentelemetry/core";const TRACESTATE_META=`tracestate`,BAGGAGE_META=`baggage`,W3C_PROPAGATOR=new W3CTraceContextPropagator,W3C_BAGGAGE_PROPAGATOR=new W3CBaggagePropagator;function readMeta(name){return typeof document>`u`?null:document.querySelector(`meta[name="${name}"]`)?.getAttribute(`content`)??null}function readPropagationFromDocument(){let traceparent=readMeta(`traceparent`);if(!traceparent)return null;let carrier={traceparent
|
|
1
|
+
import{ROOT_CONTEXT,defaultTextMapGetter,trace}from"@opentelemetry/api";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{W3CBaggagePropagator,W3CTraceContextPropagator}from"@opentelemetry/core";import{TRACESTATE_FRESHNESS_KEY,isFreshnessStampExpired}from"@interfere/types/sdk/trace-freshness";const TRACESTATE_META=`tracestate`,BAGGAGE_META=`baggage`,W3C_PROPAGATOR=new W3CTraceContextPropagator,W3C_BAGGAGE_PROPAGATOR=new W3CBaggagePropagator;function readMeta(name){return typeof document>`u`?null:document.querySelector(`meta[name="${name}"]`)?.getAttribute(`content`)??null}function readTracestateMeta(){if(typeof document>`u`)return null;let contents=[...document.querySelectorAll(`meta[name="${TRACESTATE_META}"]`)].flatMap(el=>{let content=el.getAttribute(`content`);return content===null?[]:[content]});return contents.find(content=>RegExp(`(?:^|,)\\s*${TRACESTATE_FRESHNESS_KEY}=`,`u`).test(content))??contents.at(0)??null}function readPropagationFromDocument(){let traceparent=readMeta(`traceparent`);if(!traceparent)return null;let carrier=omitUndefined({traceparent,[TRACESTATE_META]:readTracestateMeta()||void 0,[BAGGAGE_META]:readMeta(BAGGAGE_META)||void 0}),withTrace=W3C_PROPAGATOR.extract(ROOT_CONTEXT,carrier,defaultTextMapGetter),extracted=W3C_BAGGAGE_PROPAGATOR.extract(withTrace,carrier,defaultTextMapGetter),spanCtx=trace.getSpanContext(extracted);return!spanCtx||spanCtx.traceId===`00000000000000000000000000000000`||!(spanCtx.traceFlags&1)||isStaleTraceparent(spanCtx.traceState?.get(TRACESTATE_FRESHNESS_KEY))?null:extracted}function isStaleTraceparent(freshnessStamp){return freshnessStamp?isFreshnessStampExpired(freshnessStamp,Date.now()):!1}export{readPropagationFromDocument};
|
|
@@ -1,87 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ReleaseSlug } from "@interfere/types/releases/slug";
|
|
3
|
-
import { MeterProvider, MetricReader } from "@opentelemetry/sdk-metrics";
|
|
4
|
-
import { LogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs";
|
|
5
|
-
import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
6
|
-
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
|
|
7
|
-
import { SessionId } from "@interfere/types/data/session";
|
|
8
|
-
//#region src/internal/otel/provider.d.ts
|
|
9
|
-
interface OtelProviderInput {
|
|
10
|
-
/** @internal */
|
|
11
|
-
additionalLogRecordProcessors?: LogRecordProcessor[];
|
|
12
|
-
/** @internal */
|
|
13
|
-
additionalMetricReaders?: MetricReader[];
|
|
14
|
-
/** @internal */
|
|
15
|
-
additionalSpanProcessors?: SpanProcessor[];
|
|
16
|
-
/**
|
|
17
|
-
* Auth + content headers for OTLP exports. Sourced from the kernel's
|
|
18
|
-
* already-resolved ingest target so the OTLP path inherits the same
|
|
19
|
-
* transport metadata the rest of the SDK uses — no separate auth knob.
|
|
20
|
-
*/
|
|
21
|
-
authHeaders: Headers;
|
|
22
|
-
/** OTLP base URL — the opaque sink path is appended by the exporters. */
|
|
23
|
-
collectorUrl: string;
|
|
24
|
-
/**
|
|
25
|
-
* Returns the current `DeviceManager` device id. Wired into per-span
|
|
26
|
-
* and per-log processors so a mid-page device rotation
|
|
27
|
-
* (`identity.clear()` after logout) lands on subsequent spans / logs
|
|
28
|
-
* without the kernel rebuilding the providers. Returns `null` before
|
|
29
|
-
* the device manager has bootstrapped, or in SSR / test boot paths.
|
|
30
|
-
*
|
|
31
|
-
* Not applied to metrics: the MeterProvider takes a static resource
|
|
32
|
-
* and rebuilding it mid-page would destroy the periodic reader; metrics
|
|
33
|
-
* remain stamped with the boot-time device id.
|
|
34
|
-
*/
|
|
35
|
-
getDeviceId: () => string | null;
|
|
36
|
-
/**
|
|
37
|
-
* Returns the kernel's current session id. Wired into per-span /
|
|
38
|
-
* per-log session processors so a mid-page session rotation (30-min
|
|
39
|
-
* idle expiry, manual reset) lands on subsequent spans without the
|
|
40
|
-
* kernel having to rebuild the providers. Returns `null` before the
|
|
41
|
-
* `SessionTracker` has bootstrapped.
|
|
42
|
-
*/
|
|
43
|
-
getSessionId: () => SessionId | null;
|
|
44
|
-
/** Optional release slug — top-level `release.slug` attr (Phase 6 wire identity). */
|
|
45
|
-
releaseSlug?: ReleaseSlug | null | undefined;
|
|
46
|
-
/**
|
|
47
|
-
* Producer chain for the `interfere.sdk.stack` resource attribute —
|
|
48
|
-
* e.g. `["@interfere/next@10.0.0", "@interfere/react@10.0.0"]`.
|
|
49
|
-
* Wrapper-injected; the kernel just forwards.
|
|
50
|
-
*/
|
|
51
|
-
sdkStack: string[];
|
|
52
|
-
/**
|
|
53
|
-
* Override the OTel `service.name` resource attribute. Defaults to
|
|
54
|
-
* `"interfere-sdk"`.
|
|
55
|
-
*/
|
|
56
|
-
serviceName?: string;
|
|
57
|
-
}
|
|
58
|
-
interface OtelProviderHandle {
|
|
59
|
-
/**
|
|
60
|
-
* Page-scope context manager. The kernel calls `setPageScope(ctx)` after
|
|
61
|
-
* extracting the SSR `traceparent` meta tag so spans without a more
|
|
62
|
-
* specific zone descend from the server-side parent.
|
|
63
|
-
*/
|
|
64
|
-
contextManager: PageScopeContextManager;
|
|
65
|
-
/** Force-flush all three providers — call from kernel.flush() / on visibility hidden. */
|
|
66
|
-
flush: () => Promise<void>;
|
|
67
|
-
loggerProvider: LoggerProvider;
|
|
68
|
-
meterProvider: MeterProvider;
|
|
69
|
-
metricReader: MetricReader;
|
|
70
|
-
/** Tear down all three providers — called from kernel.dispose(). */
|
|
71
|
-
shutdown: () => Promise<void>;
|
|
72
|
-
tracerProvider: WebTracerProvider;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Constructs the SDK's private OTel providers. `WebTracerProvider` is never
|
|
76
|
-
* installed globally — it's passed to `registerInstrumentations(...)`
|
|
77
|
-
* explicitly. `ContextManager` and `TextMapPropagator` are process-wide
|
|
78
|
-
* singletons; we install ours only when nothing else has claimed them
|
|
79
|
-
* (`setGlobal*` returns `false` on duplicate). If we lose that race, the
|
|
80
|
-
* SSR-embedded pageload parent isn't honored — everything else works.
|
|
81
|
-
*
|
|
82
|
-
* Kept out of `Kernel` so the error-only bundle path never imports the
|
|
83
|
-
* OTel SDK.
|
|
84
|
-
*/
|
|
85
|
-
declare function buildOtelProvider(input: OtelProviderInput): OtelProviderHandle;
|
|
86
|
-
//#endregion
|
|
1
|
+
import { n as OtelProviderInput, r as buildOtelProvider, t as OtelProviderHandle } from "../../provider-Cp6noibq.mjs";
|
|
87
2
|
export { OtelProviderHandle, OtelProviderInput, buildOtelProvider };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{getStringGlobal}from"../../util/global.mjs";import{n as SDK_NAME,r as SDK_VERSION}from"../../version-BniHrwLD.mjs";import{createLogExporter,createMetricExporter,createTraceExporter}from"./exporter.mjs";import{PageScopeContextManager}from"./page-scope-context-manager.mjs";import{context,propagation}from"@opentelemetry/api";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{ENVIRONMENT_GLOBAL}from"@interfere/types/sdk/injected-globals";import{CompositePropagator,W3CBaggagePropagator,W3CTraceContextPropagator}from"@opentelemetry/core";import{detectResources,resourceFromAttributes}from"@opentelemetry/resources";import{MeterProvider,PeriodicExportingMetricReader}from"@opentelemetry/sdk-metrics";import{resolveEnvironment}from"@interfere/types/sdk/runtime";import{browserDetector}from"@opentelemetry/opentelemetry-browser-detector";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={exportTimeoutMillis:1e4,maxExportBatchSize:10,maxQueueSize:2048,scheduledDelayMillis:2e3};function createDeviceSpanProcessor(opts){return{forceFlush:async()=>Promise.resolve(),onEnd(){},onStart(span){let id=opts.getDeviceId();id&&span.setAttribute(ATTR_DEVICE_ID,id)},shutdown:async()=>Promise.resolve()}}function createDeviceLogRecordProcessor(opts){return{forceFlush:async()=>Promise.resolve(),onEmit(logRecord){let id=opts.getDeviceId();id&&logRecord.setAttribute(ATTR_DEVICE_ID,id)},shutdown:async()=>Promise.resolve()}}function stampGatedAttributes(target,opts){if(opts.isEnabled())for(let[key,value]of Object.entries(opts.attributes))value!==void 0&&target.setAttribute(key,value)}function createDeviceInfoSpanProcessor(opts){return{forceFlush:async()=>Promise.resolve(),onEnd(){},onStart:span=>{stampGatedAttributes(span,opts)},shutdown:async()=>Promise.resolve()}}function createDeviceInfoLogRecordProcessor(opts){return{forceFlush:async()=>Promise.resolve(),onEmit:logRecord=>{stampGatedAttributes(logRecord,opts)},shutdown:async()=>Promise.resolve()}}function buildOtelProvider(input){let environment=resolveEnvironment({explicit:getStringGlobal(ENVIRONMENT_GLOBAL),env:typeof process>`u`?{}:{INTERFERE_ENVIRONMENT:process.env.INTERFERE_ENVIRONMENT,NODE_ENV:process.env.NODE_ENV,VERCEL_ENV:process.env.VERCEL_ENV}}),resource=resourceFromAttributes(omitUndefined({"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(`, `),"release.slug":input.releaseSlug||void 0})),deviceInfoAttributes=detectResources({detectors:[browserDetector]}).attributes,isDeviceInfoEnabled=input.isDeviceInfoEnabled??(()=>!1),traceExporter=createTraceExporter({clientOptions:input.clientOptions}),{getSessionId}=input,{getDeviceId}=input,spanProcessors=[createSessionSpanProcessor({getSessionId}),createDeviceSpanProcessor({getDeviceId}),createDeviceInfoSpanProcessor({attributes:deviceInfoAttributes,isEnabled:isDeviceInfoEnabled}),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({clientOptions:input.clientOptions},{attributes:deviceInfoAttributes,isEnabled:isDeviceInfoEnabled}),metricReader=new PeriodicExportingMetricReader({exportIntervalMillis:3e4,exporter:metricExporter}),meterProvider=new MeterProvider({readers:[metricReader,...input.additionalMetricReaders??[]],resource}),logExporter=createLogExporter({clientOptions:input.clientOptions}),loggerProvider=new LoggerProvider({processors:[createSessionLogRecordProcessor({getSessionId}),createDeviceLogRecordProcessor({getDeviceId}),createDeviceInfoLogRecordProcessor({attributes:deviceInfoAttributes,isEnabled:isDeviceInfoEnabled}),new BatchLogRecordProcessor({exporter:logExporter,...BROWSER_BATCH_OPTIONS}),...input.additionalLogRecordProcessors??[]],resource});return{contextManager,flush:async()=>{await Promise.allSettled([tracerProvider.forceFlush(),metricReader.forceFlush(),loggerProvider.forceFlush()])},loggerProvider,meterProvider,metricReader,shutdown:async()=>{await Promise.allSettled([tracerProvider.shutdown(),meterProvider.shutdown(),loggerProvider.shutdown()])},tracerProvider}}export{buildOtelProvider};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Span } from "@opentelemetry/api";
|
|
2
|
+
//#region src/internal/otel/url-attrs.d.ts
|
|
3
|
+
export declare const ATTR_HTTP_ROUTE: "http.route";
|
|
4
|
+
export declare const ATTR_URL_FULL: "url.full";
|
|
5
|
+
export declare const ATTR_URL_PATH: "url.path";
|
|
6
|
+
export declare function stampUrlAttrs(span: Span, resolveRoute: ((pathname: string) => string | undefined) | undefined): void;
|
|
7
|
+
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const ATTR_HTTP_ROUTE=`http.route`,ATTR_URL_FULL=`url.full`,ATTR_URL_PATH=`url.path`;function stampUrlAttrs(span,resolveRoute){if(typeof window>`u`)return;let{pathname}=globalThis.location,route=resolveRoute?.(pathname);span.setAttribute(ATTR_URL_PATH,route??pathname),span.setAttribute(ATTR_URL_FULL,globalThis.location.href),route&&span.setAttribute(ATTR_HTTP_ROUTE,route)}export{ATTR_HTTP_ROUTE,ATTR_URL_FULL,ATTR_URL_PATH,stampUrlAttrs};
|
|
@@ -1,34 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region src/internal/otel/web-vitals.d.ts
|
|
3
|
-
interface WebVitalsInput {
|
|
4
|
-
/**
|
|
5
|
-
* Called after the page-load vitals have all fired. The kernel passes
|
|
6
|
-
* its own `flush()` so short-visit exports don't depend on the
|
|
7
|
-
* `visibilitychange` listener firing.
|
|
8
|
-
*/
|
|
9
|
-
flush: () => Promise<void>;
|
|
10
|
-
/** OTel meter from the kernel's private provider. */
|
|
11
|
-
meter: Meter;
|
|
12
|
-
/**
|
|
13
|
-
* Pathname → low-cardinality route template (e.g. `/blog/[slug]`).
|
|
14
|
-
* Without this, dynamic routes produce one unique `url.path` label
|
|
15
|
-
* per visited slug — a metric cardinality hazard.
|
|
16
|
-
*/
|
|
17
|
-
resolveRoute?: (pathname: string) => string | undefined;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Subscribes to Core Web Vitals (LCP/FCP/TTFB/INP/CLS) and records OTel
|
|
21
|
-
* histograms. Time-based vitals are converted to seconds per
|
|
22
|
-
* observability convention; CLS is dimensionless and recorded as-is.
|
|
23
|
-
*
|
|
24
|
-
* Each record carries `web_vital.rating`, `web_vital.navigation_type`,
|
|
25
|
-
* and `url.path` (templated when `resolveRoute` is provided). Browser
|
|
26
|
-
* and device dimensions come from the resource attached to the
|
|
27
|
-
* MeterProvider, so every histogram datapoint already carries them.
|
|
28
|
-
*
|
|
29
|
-
* Bucket boundaries match the rum.ts SDK — same dashboards, same
|
|
30
|
-
* histograms, same percentile shape.
|
|
31
|
-
*/
|
|
32
|
-
declare function captureWebVitals(input: WebVitalsInput): void;
|
|
33
|
-
//#endregion
|
|
1
|
+
import { n as captureWebVitals, t as WebVitalsInput } from "../../web-vitals-x8y70B5x.mjs";
|
|
34
2
|
export { WebVitalsInput, captureWebVitals };
|
|
@@ -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`,{
|
|
1
|
+
import{omitUndefined}from"@interfere/helpers/omit-undefined";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`,{advice:{explicitBucketBoundaries:[.5,1,1.5,2,2.5,3,4,5,7.5,10]},description:`Largest Contentful Paint`,unit:`s`}),fcp=meter.createHistogram(`web_vitals.fcp`,{advice:{explicitBucketBoundaries:[.5,1,1.5,1.8,2,2.5,3,4,5,7.5]},description:`First Contentful Paint`,unit:`s`}),ttfb=meter.createHistogram(`web_vitals.ttfb`,{advice:{explicitBucketBoundaries:[.1,.2,.3,.5,.8,1,1.5,2,3,5]},description:`Time to First Byte`,unit:`s`}),inp=meter.createHistogram(`web_vitals.inp`,{advice:{explicitBucketBoundaries:[.05,.1,.15,.2,.3,.4,.5,.75,1,2]},description:`Interaction to Next Paint`,unit:`s`}),vitals={CLS:{histogram:meter.createHistogram(`web_vitals.cls`,{advice:{explicitBucketBoundaries:[.01,.025,.05,.075,.1,.15,.2,.25,.5,1]},description:`Cumulative Layout Shift`,unit:`1`}),toSeconds:!1},FCP:{histogram:fcp,toSeconds:!0},INP:{histogram:inp,toSeconds:!0},LCP:{histogram:lcp,toSeconds:!0},TTFB:{histogram:ttfb,toSeconds:!0}},received=new Set,flushed=!1;function report(metric){let entry=vitals[metric.name];if(entry===void 0)return;let value=entry.toSeconds?metric.value*.001:metric.value,{pathname}=globalThis.location,route=resolveRoute?.(pathname)??pathname;entry.histogram.record(value,omitUndefined({"url.path":route,"web_vital.rating":metric.rating||void 0,"web_vital.navigation_type":metric.navigationType||void 0})),!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,8 +1,7 @@
|
|
|
1
1
|
//#region src/internal/page-lifecycle.d.ts
|
|
2
2
|
type Handler = () => void;
|
|
3
|
-
declare function onPageHidden(handler: Handler): () => void;
|
|
4
|
-
declare function onPageShown(handler: Handler): () => void;
|
|
3
|
+
export declare function onPageHidden(handler: Handler): () => void;
|
|
4
|
+
export declare function onPageShown(handler: Handler): () => void;
|
|
5
5
|
/** Test hook — drops every handler and unbinds the listeners. */
|
|
6
|
-
declare function
|
|
7
|
-
//#endregion
|
|
8
|
-
export { _resetPageLifecycleForTests, onPageHidden, onPageShown };
|
|
6
|
+
export declare function resetPageLifecycleForTests(): void;
|
|
7
|
+
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const hiddenHandlers=new Set,shownHandlers=new Set;let installed=!1;function fire(set){for(let handler of set)handler()}function fireHidden(){fire(hiddenHandlers)}function fireShown(){fire(shownHandlers)}function onVisibilityChange(){if(!(typeof document>`u`)){if(document.visibilityState===`hidden`){fireHidden();return}
|
|
1
|
+
const hiddenHandlers=new Set,shownHandlers=new Set;let installed=!1;function fire(set){for(let handler of set)handler()}function fireHidden(){fire(hiddenHandlers)}function fireShown(){fire(shownHandlers)}function onVisibilityChange(){if(!(typeof document>`u`)){if(document.visibilityState===`hidden`){fireHidden();return}fireShown()}}function install(){installed||typeof globalThis.addEventListener!=`function`||(installed=!0,globalThis.addEventListener(`visibilitychange`,onVisibilityChange),globalThis.addEventListener(`beforeunload`,fireHidden))}function onPageHidden(handler){return install(),hiddenHandlers.add(handler),(()=>{hiddenHandlers.delete(handler)})}function onPageShown(handler){return install(),shownHandlers.add(handler),(()=>{shownHandlers.delete(handler)})}function resetPageLifecycleForTests(){hiddenHandlers.clear(),shownHandlers.clear(),installed&&typeof globalThis.removeEventListener==`function`&&(globalThis.removeEventListener(`visibilitychange`,onVisibilityChange),globalThis.removeEventListener(`beforeunload`,fireHidden)),installed=!1}export{onPageHidden,onPageShown,resetPageLifecycleForTests};
|
|
@@ -1,39 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { PluginConfigState, PluginContext } from "../plugins/lib/types.mjs";
|
|
3
|
-
import { PluginOverrides } from "../plugins/lib/loader.mjs";
|
|
4
|
-
import { ConsentState, PluginKey } from "@interfere/types/sdk/plugins/manifest";
|
|
5
|
-
import { RemotePluginConfig } from "@interfere/types/sdk/remote-config";
|
|
6
|
-
//#region src/internal/plugin-runtime.d.ts
|
|
7
|
-
declare class PluginRuntime {
|
|
8
|
-
private readonly activeCleanups;
|
|
9
|
-
private readonly pending;
|
|
10
|
-
private readonly context;
|
|
11
|
-
private features;
|
|
12
|
-
private readonly pluginConfigListeners;
|
|
13
|
-
private remoteConfig;
|
|
14
|
-
private remoteConfigSettled;
|
|
15
|
-
private consentState;
|
|
16
|
-
private configSettled;
|
|
17
|
-
private replayConfig;
|
|
18
|
-
private syncVersion;
|
|
19
|
-
private disposed;
|
|
20
|
-
constructor(context: PluginContext);
|
|
21
|
-
getConsent(): ConsentState | null;
|
|
22
|
-
getReplayConfig(): ReplayConfig;
|
|
23
|
-
applyProviderConfig(overrides: PluginOverrides | undefined, consent: ConsentState | undefined): void;
|
|
24
|
-
setConsent(nextConsent?: ConsentState): void;
|
|
25
|
-
resetConsent(): void;
|
|
26
|
-
applyRemoteConfig(config: RemotePluginConfig): void;
|
|
27
|
-
applyRemoteConfigDefaults(): void;
|
|
28
|
-
start(): void;
|
|
29
|
-
dispose(): Promise<void>;
|
|
30
|
-
private shouldEnablePlugin;
|
|
31
|
-
getPluginConfigState(key: PluginKey): PluginConfigState;
|
|
32
|
-
subscribePluginConfigState(key: PluginKey, listener: (state: PluginConfigState) => void): () => void;
|
|
33
|
-
private emitPluginConfigState;
|
|
34
|
-
private deactivate;
|
|
35
|
-
private activate;
|
|
36
|
-
private sync;
|
|
37
|
-
}
|
|
38
|
-
//#endregion
|
|
1
|
+
import { b as PluginRuntime } from "../provider-50LbJ4Tl.mjs";
|
|
39
2
|
export { PluginRuntime };
|
|
@@ -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;
|
|
1
|
+
import{createLogger}from"../util/log.mjs";import{getPluginConsentCategory,hasConsentChanged,isConsentAllowed,resolveGrantedConsent}from"./consent.mjs";import devicePlugin from"../plugins/device.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;features=resolveFeatures();pluginConfigListeners=new Map;remoteConfig={};remoteConfigSettled=!1;consentState=null;configSettled=!1;replayConfig={};syncVersion=0;disposed=!1;context;hasInitialConsent;constructor(context,consent,remoteConfig={}){this.context=context,this.hasInitialConsent=consent!==void 0,this.consentState=consent===void 0?null:resolveGrantedConsent(consent),this.configSettled=this.hasInitialConsent,this.remoteConfig=remoteConfig}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:{},consent===void 0?this.hasInitialConsent||(this.consentState=null):this.consentState=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(){for(let[key,plugin]of[[`errors`,errorsPlugin],[`device`,devicePlugin]])if(this.shouldEnablePlugin(key)){let cleanup=plugin.setup(this.context);cleanup&&this.activeCleanups.set(key,cleanup)}this.sync()}async dispose(){this.disposed=!0,this.syncVersion+=1;for(let key of this.activeCleanups.keys())this.deactivate(key,`dispose`);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)}}teardownReason(key){let category=getPluginConsentCategory(key);return isConsentAllowed(category,this.consentState)?`disabled`:`consent-revoked`}deactivate(key,reason){let cleanup=this.activeCleanups.get(key);if(cleanup){try{cleanup(reason)}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(this.teardownReason(key));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,this.teardownReason(plugin.name))}}}};export{PluginRuntime};
|
|
@@ -1,44 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ConsentState } from "@interfere/types/sdk/plugins/manifest";
|
|
3
|
-
import { IdentifyParams } from "@interfere/types/sdk/identify";
|
|
4
|
-
//#region src/internal/react-context.d.ts
|
|
5
|
-
interface InterfereContextValue {
|
|
6
|
-
consent: {
|
|
7
|
-
get: () => ConsentState | null;
|
|
8
|
-
set: (state?: ConsentState) => void;
|
|
9
|
-
};
|
|
10
|
-
device: {
|
|
11
|
-
getDeviceId: () => string | null;
|
|
12
|
-
getFpHash: () => string | null;
|
|
13
|
-
};
|
|
14
|
-
identity: {
|
|
15
|
-
clear: () => void;
|
|
16
|
-
get: () => IdentifyParams | null;
|
|
17
|
-
set: (params: IdentifyParams) => Promise<void>;
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* The active kernel, or `null` when the SDK isn't initialized yet — for
|
|
21
|
-
* example during server-side prerender, when `init()` is client-only.
|
|
22
|
-
* Accessor objects (consent/device/identity/session) above stay safe to
|
|
23
|
-
* call in either state; they no-op when the kernel is null.
|
|
24
|
-
*/
|
|
25
|
-
kernel: Kernel | null;
|
|
26
|
-
session: {
|
|
27
|
-
getId: () => string | null;
|
|
28
|
-
getWindowId: () => string | null;
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Context value used when the provider is mounted without a kernel — every
|
|
33
|
-
* accessor returns `null` and mutations are no-ops. Lets `useInterfere()`
|
|
34
|
-
* work seamlessly across SSR/CSR boundaries (server gets nulls, client
|
|
35
|
-
* gets real values once the kernel resolves).
|
|
36
|
-
*/
|
|
37
|
-
declare const NULL_CONTEXT_VALUE: InterfereContextValue;
|
|
38
|
-
/**
|
|
39
|
-
* Lives in its own module so `provider.tsx` and `internal/capture-boundary.tsx`
|
|
40
|
-
* can both import it without forming an import cycle.
|
|
41
|
-
*/
|
|
42
|
-
declare const InterfereContext: import("react").Context<InterfereContextValue | null>;
|
|
43
|
-
//#endregion
|
|
1
|
+
import { a as InterfereContextValue, i as InterfereContext, o as NULL_CONTEXT_VALUE } from "../provider-50LbJ4Tl.mjs";
|
|
44
2
|
export { InterfereContext, InterfereContextValue, NULL_CONTEXT_VALUE };
|
|
@@ -1 +1 @@
|
|
|
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
|
+
"use client";import{createContext}from"react";const NULL_CONTEXT_VALUE={consent:{get:()=>null,set:()=>{}},device:{getDeviceId:()=>null,getFpHash:()=>null},identity:{clear:()=>{},get:()=>null,set:async()=>Promise.resolve()},kernel:null,session:{getId:()=>null,getWindowId:()=>null}},InterfereContext=createContext(null);export{InterfereContext,NULL_CONTEXT_VALUE};
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* the server and was already captured there (`onRequestError`). Client capture
|
|
5
5
|
* sites skip these so one server error isn't recorded again on the client.
|
|
6
6
|
*/
|
|
7
|
-
declare function hasServerDigest(value: unknown):
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
export declare function hasServerDigest(value: unknown): value is {
|
|
8
|
+
digest: string;
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function hasServerDigest(value){return typeof value==`object`&&!!value&&typeof value.digest==`string`}export{hasServerDigest};
|
|
1
|
+
function hasServerDigest(value){return typeof value==`object`&&!!value&&`digest`in value&&typeof value.digest==`string`}export{hasServerDigest};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
//#region src/internal/sw-queue.d.ts
|
|
2
|
-
interface IngestQueueMessage {
|
|
2
|
+
export interface IngestQueueMessage {
|
|
3
3
|
reason?: string;
|
|
4
4
|
type: "interfere.sw.queued" | "interfere.sw.replayed" | "interfere.sw.dropped";
|
|
5
5
|
url: string;
|
|
6
6
|
}
|
|
7
|
-
interface InstallIngestQueueOptions {
|
|
7
|
+
export interface InstallIngestQueueOptions {
|
|
8
8
|
/**
|
|
9
9
|
* Predicate selecting which POSTs to durably queue on failure. Proxy-mode
|
|
10
10
|
* customers leave this unset (same-origin `/api/interfere/*`); a host that
|
|
@@ -21,6 +21,5 @@ interface InstallIngestQueueOptions {
|
|
|
21
21
|
* a host SW (e.g. the app's Workbox precache worker) keeps full ownership
|
|
22
22
|
* of its update lifecycle. Additive listeners coexist with the host's.
|
|
23
23
|
*/
|
|
24
|
-
declare function installIngestQueue(options?: InstallIngestQueueOptions): void;
|
|
25
|
-
//#endregion
|
|
26
|
-
export { IngestQueueMessage, InstallIngestQueueOptions, installIngestQueue };
|
|
24
|
+
export declare function installIngestQueue(options?: InstallIngestQueueOptions): void;
|
|
25
|
+
//#endregion
|
|
@@ -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
|
|
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;meta&&`bytes`in meta&&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;meta&&`bytes`in meta&&typeof meta.bytes==`number`&&(total-=meta.bytes),await postToClients({reason:`queue_size_cap`,type:`interfere.sw.dropped`,url:dropped.request.url})}}async function enqueue(queue,request){let cloned=request.clone(),bytes=(await cloned.clone().arrayBuffer()).byteLength;await pruneOldest(queue,bytes),await queue.pushRequest({metadata:{bytes},request:cloned}),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};
|
|
@@ -9,6 +9,5 @@
|
|
|
9
9
|
* Dependency-free on purpose so the error-only bundle can reuse it without
|
|
10
10
|
* pulling in the OTel SDK that `internal/otel/*` carries.
|
|
11
11
|
*/
|
|
12
|
-
declare const THIRD_PARTY_IGNORE: readonly RegExp[];
|
|
13
|
-
//#endregion
|
|
14
|
-
export { THIRD_PARTY_IGNORE };
|
|
12
|
+
export declare const THIRD_PARTY_IGNORE: readonly RegExp[];
|
|
13
|
+
//#endregion
|