@interfere/react 9.0.2 → 10.0.1-canary.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/api.d.mts +25 -0
- package/dist/api.d.mts.map +1 -0
- package/dist/api.mjs +1 -0
- package/dist/api.mjs.map +1 -0
- package/dist/error-boundary.d.mts +10 -4
- package/dist/error-boundary.d.mts.map +1 -1
- package/dist/error-boundary.mjs +1 -39
- package/dist/error-boundary.mjs.map +1 -1
- package/dist/internal/browser-context.d.mts +6 -0
- package/dist/internal/browser-context.d.mts.map +1 -0
- package/dist/internal/browser-context.mjs +1 -0
- package/dist/internal/browser-context.mjs.map +1 -0
- package/dist/internal/capture-boundary.d.mts +4 -1
- package/dist/internal/capture-boundary.d.mts.map +1 -1
- package/dist/internal/capture-boundary.mjs +1 -44
- package/dist/internal/capture-boundary.mjs.map +1 -1
- package/dist/internal/capture.d.mts +16 -5
- package/dist/internal/capture.d.mts.map +1 -1
- package/dist/internal/capture.mjs +1 -23
- package/dist/internal/capture.mjs.map +1 -1
- package/dist/internal/config.d.mts +22 -4
- package/dist/internal/config.d.mts.map +1 -1
- package/dist/internal/config.mjs +1 -33
- package/dist/internal/config.mjs.map +1 -1
- package/dist/internal/consent.d.mts.map +1 -1
- package/dist/internal/consent.mjs +1 -25
- package/dist/internal/consent.mjs.map +1 -1
- package/dist/internal/console-patch.d.mts +19 -0
- package/dist/internal/console-patch.d.mts.map +1 -0
- package/dist/internal/console-patch.mjs +1 -0
- package/dist/internal/console-patch.mjs.map +1 -0
- package/dist/internal/dom/actionable.d.mts +27 -0
- package/dist/internal/dom/actionable.d.mts.map +1 -0
- package/dist/internal/dom/actionable.mjs +1 -0
- package/dist/internal/dom/actionable.mjs.map +1 -0
- package/dist/internal/kernel-registry.d.mts +8 -0
- package/dist/internal/kernel-registry.d.mts.map +1 -0
- package/dist/internal/kernel-registry.mjs +1 -0
- package/dist/internal/kernel-registry.mjs.map +1 -0
- package/dist/internal/kernel.d.mts +267 -0
- package/dist/internal/kernel.d.mts.map +1 -0
- package/dist/internal/kernel.mjs +1 -0
- package/dist/internal/kernel.mjs.map +1 -0
- package/dist/internal/otel/exporter.d.mts +85 -0
- package/dist/internal/otel/exporter.d.mts.map +1 -0
- package/dist/internal/otel/exporter.mjs +1 -0
- package/dist/internal/otel/exporter.mjs.map +1 -0
- package/dist/internal/otel/index.d.mts +6 -0
- package/dist/internal/otel/index.mjs +1 -0
- package/dist/internal/otel/instrumentations.d.mts +42 -0
- package/dist/internal/otel/instrumentations.d.mts.map +1 -0
- package/dist/internal/otel/instrumentations.mjs +1 -0
- package/dist/internal/otel/instrumentations.mjs.map +1 -0
- package/dist/internal/otel/page-scope-context-manager.d.mts +32 -0
- package/dist/internal/otel/page-scope-context-manager.d.mts.map +1 -0
- package/dist/internal/otel/page-scope-context-manager.mjs +1 -0
- package/dist/internal/otel/page-scope-context-manager.mjs.map +1 -0
- package/dist/internal/otel/propagation.d.mts +21 -0
- package/dist/internal/otel/propagation.d.mts.map +1 -0
- package/dist/internal/otel/propagation.mjs +1 -0
- package/dist/internal/otel/propagation.mjs.map +1 -0
- package/dist/internal/otel/provider.d.mts +106 -0
- package/dist/internal/otel/provider.d.mts.map +1 -0
- package/dist/internal/otel/provider.mjs +1 -0
- package/dist/internal/otel/provider.mjs.map +1 -0
- package/dist/internal/otel/web-vitals.d.mts +35 -0
- package/dist/internal/otel/web-vitals.d.mts.map +1 -0
- package/dist/internal/otel/web-vitals.mjs +1 -0
- package/dist/internal/otel/web-vitals.mjs.map +1 -0
- package/dist/internal/page-lifecycle.d.mts +21 -0
- package/dist/internal/page-lifecycle.d.mts.map +1 -0
- package/dist/internal/page-lifecycle.mjs +1 -0
- package/dist/internal/page-lifecycle.mjs.map +1 -0
- package/dist/internal/plugin-runtime.d.mts +0 -2
- package/dist/internal/plugin-runtime.d.mts.map +1 -1
- package/dist/internal/plugin-runtime.mjs +1 -107
- package/dist/internal/plugin-runtime.mjs.map +1 -1
- package/dist/internal/react-context.d.mts +44 -0
- package/dist/internal/react-context.d.mts.map +1 -0
- package/dist/internal/react-context.mjs +1 -0
- package/dist/internal/react-context.mjs.map +1 -0
- package/dist/internal/sw.d.mts +22 -2
- package/dist/internal/sw.d.mts.map +1 -1
- package/dist/internal/sw.mjs +1 -10
- package/dist/internal/sw.mjs.map +1 -1
- package/dist/internal/version.d.mts +3 -1
- package/dist/internal/version.d.mts.map +1 -1
- package/dist/internal/version.mjs +1 -5
- package/dist/internal/version.mjs.map +1 -1
- package/dist/internal/wrapper-singleton.d.mts +47 -0
- package/dist/internal/wrapper-singleton.d.mts.map +1 -0
- package/dist/internal/wrapper-singleton.mjs +1 -0
- package/dist/internal/wrapper-singleton.mjs.map +1 -0
- package/dist/package.mjs +1 -5
- package/dist/plugins/errors.d.mts.map +1 -1
- package/dist/plugins/errors.mjs +1 -91
- package/dist/plugins/errors.mjs.map +1 -1
- package/dist/plugins/lib/loader.d.mts +1 -2
- package/dist/plugins/lib/loader.d.mts.map +1 -1
- package/dist/plugins/lib/loader.mjs +1 -43
- package/dist/plugins/lib/loader.mjs.map +1 -1
- package/dist/plugins/lib/types.d.mts +3 -2
- package/dist/plugins/lib/types.d.mts.map +1 -1
- package/dist/plugins/lib/types.mjs +1 -1
- package/dist/plugins/logs.d.mts +13 -0
- package/dist/plugins/logs.d.mts.map +1 -0
- package/dist/plugins/logs.mjs +1 -0
- package/dist/plugins/logs.mjs.map +1 -0
- package/dist/plugins/rage-clicks.d.mts.map +1 -1
- package/dist/plugins/rage-clicks.mjs +1 -53
- package/dist/plugins/rage-clicks.mjs.map +1 -1
- package/dist/plugins/replay.d.mts.map +1 -1
- package/dist/plugins/replay.mjs +1 -62
- package/dist/plugins/replay.mjs.map +1 -1
- package/dist/provider.d.mts +11 -20
- package/dist/provider.d.mts.map +1 -1
- package/dist/provider.mjs +1 -32
- package/dist/provider.mjs.map +1 -1
- package/dist/react-error-handler.d.mts +21 -5
- package/dist/react-error-handler.d.mts.map +1 -1
- package/dist/react-error-handler.mjs +1 -54
- package/dist/react-error-handler.mjs.map +1 -1
- package/dist/sw.d.mts +2 -0
- package/dist/sw.mjs +2 -0
- package/dist/tracking/api.d.mts +41 -15
- package/dist/tracking/api.d.mts.map +1 -1
- package/dist/tracking/api.mjs +1 -134
- package/dist/tracking/api.mjs.map +1 -1
- package/dist/tracking/device.d.mts +30 -7
- package/dist/tracking/device.d.mts.map +1 -1
- package/dist/tracking/device.mjs +1 -80
- package/dist/tracking/device.mjs.map +1 -1
- package/dist/tracking/geo.d.mts +11 -3
- package/dist/tracking/geo.d.mts.map +1 -1
- package/dist/tracking/geo.mjs +2 -44
- package/dist/tracking/geo.mjs.map +1 -1
- package/dist/tracking/session.d.mts +3 -1
- package/dist/tracking/session.d.mts.map +1 -1
- package/dist/tracking/session.mjs +1 -75
- package/dist/tracking/session.mjs.map +1 -1
- package/dist/util/bot.d.mts +10 -0
- package/dist/util/bot.d.mts.map +1 -0
- package/dist/util/bot.mjs +1 -0
- package/dist/util/bot.mjs.map +1 -0
- package/dist/util/global.d.mts +10 -0
- package/dist/util/global.d.mts.map +1 -0
- package/dist/util/global.mjs +1 -0
- package/dist/util/global.mjs.map +1 -0
- package/dist/util/log.d.mts.map +1 -1
- package/dist/util/log.mjs +1 -37
- package/dist/util/log.mjs.map +1 -1
- package/dist/util/stringify.d.mts +9 -0
- package/dist/util/stringify.d.mts.map +1 -0
- package/dist/util/stringify.mjs +1 -0
- package/dist/util/stringify.mjs.map +1 -0
- package/package.json +79 -25
- package/dist/internal/client.d.mts +0 -48
- package/dist/internal/client.d.mts.map +0 -1
- package/dist/internal/client.mjs +0 -146
- package/dist/internal/client.mjs.map +0 -1
- package/dist/internal/context.d.mts +0 -6
- package/dist/internal/context.d.mts.map +0 -1
- package/dist/internal/context.mjs +0 -32
- package/dist/internal/context.mjs.map +0 -1
- package/dist/internal/envelope.d.mts +0 -15
- package/dist/internal/envelope.d.mts.map +0 -1
- package/dist/internal/envelope.mjs +0 -24
- package/dist/internal/envelope.mjs.map +0 -1
- package/dist/internal/errors.d.mts +0 -4
- package/dist/internal/errors.d.mts.map +0 -1
- package/dist/internal/errors.mjs +0 -4
- package/dist/internal/errors.mjs.map +0 -1
- package/dist/plugins/device.d.mts +0 -6
- package/dist/plugins/device.d.mts.map +0 -1
- package/dist/plugins/device.mjs +0 -13
- package/dist/plugins/device.mjs.map +0 -1
- package/dist/plugins/pages.d.mts +0 -6
- package/dist/plugins/pages.d.mts.map +0 -1
- package/dist/plugins/pages.mjs +0 -102
- package/dist/plugins/pages.mjs.map +0 -1
- package/dist/transport/http.d.mts +0 -25
- package/dist/transport/http.d.mts.map +0 -1
- package/dist/transport/http.mjs +0 -80
- package/dist/transport/http.mjs.map +0 -1
- package/dist/transport/queue.d.mts +0 -34
- package/dist/transport/queue.d.mts.map +0 -1
- package/dist/transport/queue.mjs +0 -100
- package/dist/transport/queue.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
<a href="https://interfere.com">
|
|
3
3
|
<picture>
|
|
4
4
|
<source media="(prefers-color-scheme: dark)" srcset="https://qyzkf4cgb8ydxtq1.public.blob.vercel-storage.com/v2/header/logo-dark.png">
|
|
5
|
-
<img src="https://qyzkf4cgb8ydxtq1.public.blob.vercel-storage.com/v2/header/logo-light.png" height="64">
|
|
5
|
+
<img src="https://qyzkf4cgb8ydxtq1.public.blob.vercel-storage.com/v2/header/logo-light.png" height="64" alt="Interfere">
|
|
6
6
|
</picture>
|
|
7
7
|
</a>
|
|
8
8
|
<h1 align="center">@interfere/react</h1>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<a href="https://www.npmjs.com/package/@interfere/react"><img src="https://img.shields.io/npm/v/@interfere/react.svg" /></a>
|
|
13
|
-
<a href="https://github.com/interfere-inc/interfere/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@interfere/react.svg" /></a>
|
|
14
|
-
<a href="https://www.npmjs.com/package/@interfere/react"><img src="https://img.shields.io/npm/dm/@interfere/react.svg" /></a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/@interfere/react"><img src="https://img.shields.io/npm/v/@interfere/react.svg" alt="npm version" /></a>
|
|
13
|
+
<a href="https://github.com/interfere-inc/interfere/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@interfere/react.svg" alt="License" /></a>
|
|
14
|
+
<a href="https://www.npmjs.com/package/@interfere/react"><img src="https://img.shields.io/npm/dm/@interfere/react.svg" alt="npm downloads" /></a>
|
|
15
15
|
</p>
|
|
16
16
|
|
|
17
17
|
<p align="center">
|
package/dist/api.d.mts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Span, SpanOptions } from "@opentelemetry/api";
|
|
2
|
+
|
|
3
|
+
//#region src/api.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Wraps `fn` in an OTel span. The span becomes the active context for the
|
|
6
|
+
* duration of the call (sync) or the awaited promise (async). Errors thrown
|
|
7
|
+
* by `fn` are recorded on the span before re-throwing — caller still owns
|
|
8
|
+
* the error.
|
|
9
|
+
*
|
|
10
|
+
* Works pre-init: `trace.getTracer()` returns a no-op tracer that still
|
|
11
|
+
* runs `fn` and propagates its return value, so call sites don't need
|
|
12
|
+
* defensive checks.
|
|
13
|
+
*/
|
|
14
|
+
declare function span<T>(name: string, fn: (span: Span) => T, options?: SpanOptions): T;
|
|
15
|
+
/**
|
|
16
|
+
* Captures an error. Records on the active OTel span (when present) AND on
|
|
17
|
+
* the legacy envelope path. Accepts any thrown value; non-Error inputs that
|
|
18
|
+
* carry structure (objects without a recoverable nested Error) are preserved
|
|
19
|
+
* via `toException` so the original shape survives to the wire.
|
|
20
|
+
*
|
|
21
|
+
* Safe to call before init — drops silently if no kernel is registered.
|
|
22
|
+
*/
|
|
23
|
+
declare function capture(error: unknown): void;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { capture, span };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.mts","names":[],"sources":["../src/api.ts"],"mappings":";;;;;AAuCA;;;;;;;;iBAAgB,IAAA,GAAA,CACd,IAAA,UACA,EAAA,GAAK,IAAA,EAAM,IAAA,KAAS,CAAA,EACpB,OAAA,GAAU,WAAA,GACT,CAAA;;;;;;;;;iBAuCa,OAAA,CAAQ,KAAc"}
|
package/dist/api.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{activeKernel}from"./internal/kernel-registry.mjs";import{MECHANISM_TYPE,toError,toException}from"@interfere/types/sdk/errors";import{SpanStatusCode,trace}from"@opentelemetry/api";function tracer(){return trace.getTracer(`@interfere/react`)}function isPromise(value){return!!value&&(typeof value==`object`||typeof value==`function`)&&typeof value.then==`function`}function span(name,fn,options){return tracer().startActiveSpan(name,options??{},s=>{try{let result=fn(s);return isPromise(result)?result.then(value=>(s.end(),value),err=>{let e=toError(err);throw s.recordException(e),s.setStatus({code:SpanStatusCode.ERROR,message:e.message}),s.end(),err}):(s.end(),result)}catch(err){let e=toError(err);throw s.recordException(e),s.setStatus({code:SpanStatusCode.ERROR,message:e.message}),s.end(),err}})}function capture(error){activeKernel()?.recordException(toException(error),{mechanism:{type:MECHANISM_TYPE.manual.capture,handled:!0}})}export{capture,span};
|
package/dist/api.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.mjs","names":[],"sources":["../src/api.ts"],"sourcesContent":["import {\n MECHANISM_TYPE,\n toError,\n toException,\n} from \"@interfere/types/sdk/errors\";\n\nimport {\n type Span,\n type SpanOptions,\n SpanStatusCode,\n trace,\n} from \"@opentelemetry/api\";\n\nimport { activeKernel } from \"./internal/kernel-registry.js\";\n\nconst TRACER_NAME = \"@interfere/react\";\n\nfunction tracer() {\n return trace.getTracer(TRACER_NAME);\n}\n\nfunction isPromise<T>(value: unknown): value is Promise<T> {\n return (\n !!value &&\n (typeof value === \"object\" || typeof value === \"function\") &&\n typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\n/**\n * Wraps `fn` in an OTel span. The span becomes the active context for the\n * duration of the call (sync) or the awaited promise (async). Errors thrown\n * by `fn` are recorded on the span before re-throwing — caller still owns\n * the error.\n *\n * Works pre-init: `trace.getTracer()` returns a no-op tracer that still\n * runs `fn` and propagates its return value, so call sites don't need\n * defensive checks.\n */\nexport function span<T>(\n name: string,\n fn: (span: Span) => T,\n options?: SpanOptions\n): T {\n return tracer().startActiveSpan(name, options ?? {}, (s): T => {\n try {\n const result = fn(s);\n if (isPromise<T>(result)) {\n return result.then(\n (value) => {\n s.end();\n return value;\n },\n (err: unknown) => {\n const e = toError(err);\n s.recordException(e);\n s.setStatus({ code: SpanStatusCode.ERROR, message: e.message });\n s.end();\n throw err;\n }\n ) as T;\n }\n s.end();\n return result;\n } catch (err) {\n const e = toError(err);\n s.recordException(e);\n s.setStatus({ code: SpanStatusCode.ERROR, message: e.message });\n s.end();\n throw err;\n }\n });\n}\n\n/**\n * Captures an error. Records on the active OTel span (when present) AND on\n * the legacy envelope path. Accepts any thrown value; non-Error inputs that\n * carry structure (objects without a recoverable nested Error) are preserved\n * via `toException` so the original shape survives to the wire.\n *\n * Safe to call before init — drops silently if no kernel is registered.\n */\nexport function capture(error: unknown): void {\n activeKernel()?.recordException(toException(error), {\n mechanism: { type: MECHANISM_TYPE.manual.capture, handled: true },\n });\n}\n"],"mappings":"0LAiBA,SAAS,QAAS,CAChB,OAAO,MAAM,UAAU,kBAAW,CACpC,CAEA,SAAS,UAAa,MAAqC,CACzD,MACE,CAAC,CAAC,QACD,OAAO,OAAU,UAAY,OAAO,OAAU,aAC/C,OAAQ,MAA6B,MAAS,UAElD,CAYA,SAAgB,KACd,KACA,GACA,QACG,CACH,OAAO,OAAO,EAAE,gBAAgB,KAAM,SAAW,CAAC,EAAI,GAAS,CAC7D,GAAI,CACF,IAAM,OAAS,GAAG,CAAC,EAiBnB,OAhBI,UAAa,MAAM,EACd,OAAO,KACX,QACC,EAAE,IAAI,EACC,OAER,KAAiB,CAChB,IAAM,EAAI,QAAQ,GAAG,EAIrB,MAHA,EAAE,gBAAgB,CAAC,EACnB,EAAE,UAAU,CAAE,KAAM,eAAe,MAAO,QAAS,EAAE,OAAQ,CAAC,EAC9D,EAAE,IAAI,EACA,GACR,CACF,GAEF,EAAE,IAAI,EACC,OACT,OAAS,IAAK,CACZ,IAAM,EAAI,QAAQ,GAAG,EAIrB,MAHA,EAAE,gBAAgB,CAAC,EACnB,EAAE,UAAU,CAAE,KAAM,eAAe,MAAO,QAAS,EAAE,OAAQ,CAAC,EAC9D,EAAE,IAAI,EACA,GACR,CACF,CAAC,CACH,CAUA,SAAgB,QAAQ,MAAsB,CAC5C,aAAa,GAAG,gBAAgB,YAAY,KAAK,EAAG,CAClD,UAAW,CAAE,KAAM,eAAe,OAAO,QAAS,QAAS,EAAK,CAClE,CAAC,CACH"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InterfereContext, InterfereContextValue } from "./internal/react-context.mjs";
|
|
2
|
+
import { Component, ContextType, ErrorInfo, ReactNode } from "react";
|
|
2
3
|
|
|
3
4
|
//#region src/error-boundary.d.ts
|
|
4
5
|
interface ErrorBoundaryProps {
|
|
@@ -11,15 +12,20 @@ interface ErrorBoundaryState {
|
|
|
11
12
|
}
|
|
12
13
|
/**
|
|
13
14
|
* Catches render-phase React errors, reports them to the SDK, and renders a
|
|
14
|
-
* fallback.
|
|
15
|
-
*
|
|
15
|
+
* fallback. Reads the kernel from `InterfereContext`; mounting outside the
|
|
16
|
+
* provider is supported — capture is silently skipped, fallback + `onError`
|
|
17
|
+
* still fire.
|
|
16
18
|
*
|
|
17
19
|
* Class component required — React has no hook-based error boundary API.
|
|
18
20
|
*/
|
|
19
21
|
declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
22
|
+
static contextType: import("react").Context<InterfereContextValue | null>;
|
|
23
|
+
context: ContextType<typeof InterfereContext>;
|
|
20
24
|
state: ErrorBoundaryState;
|
|
21
25
|
static getDerivedStateFromError(error: Error): ErrorBoundaryState;
|
|
22
|
-
componentDidCatch(error: Error
|
|
26
|
+
componentDidCatch(error: Error & {
|
|
27
|
+
digest?: string;
|
|
28
|
+
}, info: ErrorInfo): void;
|
|
23
29
|
private readonly reset;
|
|
24
30
|
render(): ReactNode;
|
|
25
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-boundary.d.mts","names":[],"sources":["../src/error-boundary.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"error-boundary.d.mts","names":[],"sources":["../src/error-boundary.tsx"],"mappings":";;;;UAciB,kBAAA;EACf,QAAA,EAAU,SAAA;EACV,QAAA,GAAW,SAAA,KAAc,KAAA,EAAO,KAAA,EAAO,KAAA,iBAAsB,SAAA;EAC7D,OAAA,IAAW,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,SAAA;AAAA;AAAA,UAGvB,kBAAA;EACR,KAAA,EAAO,KAAK;AAAA;;;;;;;;;cAWD,aAAA,SAAsB,SAAA,CACjC,kBAAA,EACA,kBAAA;EAAA,OAEgB,WAAA,kBAAW,OAAA,CAFT,qBAAA;EAGV,OAAA,EAAS,WAAA,QAAmB,gBAAA;EAE3B,KAAA,EAAO,kBAAA;EAAA,OAET,wBAAA,CAAyB,KAAA,EAAO,KAAA,GAAQ,kBAAA;EAItC,iBAAA,CACP,KAAA,EAAO,KAAA;IAAU,MAAA;EAAA,GACjB,IAAA,EAAM,SAAA;EAAA,iBAeS,KAAA;EAIR,MAAA,CAAA,GAAM,SAAA;AAAA"}
|
package/dist/error-boundary.mjs
CHANGED
|
@@ -1,39 +1 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { captureReactError } from "./internal/capture.mjs";
|
|
3
|
-
import { MECHANISM_TYPE } from "@interfere/types/sdk/errors";
|
|
4
|
-
import { Component } from "react";
|
|
5
|
-
//#region src/error-boundary.tsx
|
|
6
|
-
/**
|
|
7
|
-
* Catches render-phase React errors, reports them to the SDK, and renders a
|
|
8
|
-
* fallback. Requires the SDK to be bootstrapped via `init()` before React
|
|
9
|
-
* renders so `capture` has an active runtime.
|
|
10
|
-
*
|
|
11
|
-
* Class component required — React has no hook-based error boundary API.
|
|
12
|
-
*/
|
|
13
|
-
var ErrorBoundary = class extends Component {
|
|
14
|
-
state = { error: null };
|
|
15
|
-
static getDerivedStateFromError(error) {
|
|
16
|
-
return { error };
|
|
17
|
-
}
|
|
18
|
-
componentDidCatch(error, info) {
|
|
19
|
-
captureReactError(error, info.componentStack, {
|
|
20
|
-
type: MECHANISM_TYPE.react.errorBoundary,
|
|
21
|
-
handled: true
|
|
22
|
-
});
|
|
23
|
-
this.props.onError?.(error, info);
|
|
24
|
-
}
|
|
25
|
-
reset = () => {
|
|
26
|
-
this.setState({ error: null });
|
|
27
|
-
};
|
|
28
|
-
render() {
|
|
29
|
-
const { error } = this.state;
|
|
30
|
-
if (error) {
|
|
31
|
-
const { fallback } = this.props;
|
|
32
|
-
if (typeof fallback === "function") return fallback(error, this.reset);
|
|
33
|
-
return fallback ?? null;
|
|
34
|
-
}
|
|
35
|
-
return this.props.children;
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
//#endregion
|
|
39
|
-
export { ErrorBoundary };
|
|
1
|
+
"use client";import{captureReactError}from"./internal/capture.mjs";import{InterfereContext}from"./internal/react-context.mjs";import{MECHANISM_TYPE}from"@interfere/types/sdk/errors";import{Component}from"react";var ErrorBoundary=class extends Component{static contextType=InterfereContext;state={error:null};static getDerivedStateFromError(error){return{error}}componentDidCatch(error,info){captureReactError(this.context?.kernel??null,error,info.componentStack,{type:MECHANISM_TYPE.react.errorBoundary,handled:!0}),this.props.onError?.(error,info)}reset=()=>{this.setState({error:null})};render(){let{error}=this.state;if(error){let{fallback}=this.props;return typeof fallback==`function`?fallback(error,this.reset):fallback??null}return this.props.children}};export{ErrorBoundary};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-boundary.mjs","names":[],"sources":["../src/error-boundary.tsx"],"sourcesContent":["\"use client\";\n\nimport { MECHANISM_TYPE } from \"@interfere/types/sdk/errors\";\n\nimport {
|
|
1
|
+
{"version":3,"file":"error-boundary.mjs","names":[],"sources":["../src/error-boundary.tsx"],"sourcesContent":["\"use client\";\n\nimport { MECHANISM_TYPE } from \"@interfere/types/sdk/errors\";\n\nimport {\n Component,\n type ContextType,\n type ErrorInfo,\n type ReactNode,\n} from \"react\";\n\nimport { captureReactError } from \"./internal/capture.js\";\nimport { InterfereContext } from \"./internal/react-context.js\";\n\nexport interface ErrorBoundaryProps {\n children: ReactNode;\n fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);\n onError?: (error: Error, info: ErrorInfo) => void;\n}\n\ninterface ErrorBoundaryState {\n error: Error | null;\n}\n\n/**\n * Catches render-phase React errors, reports them to the SDK, and renders a\n * fallback. Reads the kernel from `InterfereContext`; mounting outside the\n * provider is supported — capture is silently skipped, fallback + `onError`\n * still fire.\n *\n * Class component required — React has no hook-based error boundary API.\n */\nexport class ErrorBoundary extends Component<\n ErrorBoundaryProps,\n ErrorBoundaryState\n> {\n static override contextType = InterfereContext;\n declare context: ContextType<typeof InterfereContext>;\n\n override state: ErrorBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { error };\n }\n\n override componentDidCatch(\n error: Error & { digest?: string },\n info: ErrorInfo\n ) {\n captureReactError(\n this.context?.kernel ?? null,\n error,\n info.componentStack,\n {\n type: MECHANISM_TYPE.react.errorBoundary,\n handled: true,\n }\n );\n\n this.props.onError?.(error, info);\n }\n\n private readonly reset = () => {\n this.setState({ error: null });\n };\n\n override render() {\n const { error } = this.state;\n\n if (error) {\n const { fallback } = this.props;\n if (typeof fallback === \"function\") {\n return fallback(error, this.reset);\n }\n return fallback ?? null;\n }\n\n return this.props.children;\n }\n}\n"],"mappings":"mNAgCA,IAAa,cAAb,cAAmC,SAGjC,CACA,OAAgB,YAAc,iBAG9B,MAAqC,CAAE,MAAO,IAAK,EAEnD,OAAO,yBAAyB,MAAkC,CAChE,MAAO,CAAE,KAAM,CACjB,CAEA,kBACE,MACA,KACA,CACA,kBACE,KAAK,SAAS,QAAU,KACxB,MACA,KAAK,eACL,CACE,KAAM,eAAe,MAAM,cAC3B,QAAS,EACX,CACF,EAEA,KAAK,MAAM,UAAU,MAAO,IAAI,CAClC,CAEA,UAA+B,CAC7B,KAAK,SAAS,CAAE,MAAO,IAAK,CAAC,CAC/B,EAEA,QAAkB,CAChB,GAAM,CAAE,OAAU,KAAK,MAEvB,GAAI,MAAO,CACT,GAAM,CAAE,UAAa,KAAK,MAI1B,OAHI,OAAO,UAAa,WACf,SAAS,MAAO,KAAK,KAAK,EAE5B,UAAY,IACrB,CAEA,OAAO,KAAK,MAAM,QACpB,CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-context.d.mts","names":[],"sources":["../../src/internal/browser-context.ts"],"mappings":";;;iBA0GsB,cAAA,CAAA,GAAkB,OAAO,CAAC,cAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{UAParser}from"@ua-parser-js/pro-enterprise";import{InApps}from"@ua-parser-js/pro-enterprise/extensions";import{isFrozenUA}from"@ua-parser-js/pro-enterprise/helpers";const NOT_A_BRAND_REGEX=/not.a.brand/i,HIGH_ENTROPY_HINTS=[`bitness`,`platformVersion`,`fullVersionList`];async function getDeviceMetadata(){if(typeof navigator>`u`)return null;let uaData=navigator.userAgentData,[parsed,hints]=await Promise.all([UAParser(navigator.userAgent,InApps).withClientHints(),uaData?.getHighEntropyValues?uaData.getHighEntropyValues(HIGH_ENTROPY_HINTS).catch(()=>({})):Promise.resolve({})]);if(!parsed)return null;let fullVersion=hints.fullVersionList?.find(b=>!NOT_A_BRAND_REGEX.test(b.brand))?.version;return{...parsed,browser:{...parsed.browser,...fullVersion?{fullVersion}:{}},cpu:{...parsed.cpu,...hints.bitness?{bitness:hints.bitness}:{}},os:{...parsed.os,...hints.platformVersion?{versionFull:hints.platformVersion}:{}},frozenUa:isFrozenUA(navigator.userAgent)}}function getBrowserMetadata(){if([typeof navigator,typeof screen,typeof globalThis].some(x=>x===`undefined`))return null;let{language}=navigator;return{language,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,display:{screen:{height:screen.availHeight,width:screen.availWidth,orientation:screen.orientation?.type}}}}async function collectContext(){return{runtime:`browser`,browser:getBrowserMetadata(),device:await getDeviceMetadata()}}export{collectContext};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-context.mjs","names":[],"sources":["../../src/internal/browser-context.ts"],"sourcesContent":["import type {\n BrowserContext,\n BrowserMetadata,\n DeviceMetadata,\n} from \"@interfere/types/sdk/plugins/context/browser\";\n\nimport { UAParser } from \"@ua-parser-js/pro-enterprise\";\nimport { InApps } from \"@ua-parser-js/pro-enterprise/extensions\";\nimport { isFrozenUA } from \"@ua-parser-js/pro-enterprise/helpers\";\n\ninterface NavigatorUABrand {\n brand: string;\n version: string;\n}\n\ninterface NavigatorUAData {\n brands?: NavigatorUABrand[];\n getHighEntropyValues?: (hints: string[]) => Promise<HighEntropyValues>;\n}\n\ninterface HighEntropyValues {\n bitness?: string;\n fullVersionList?: NavigatorUABrand[];\n platformVersion?: string;\n}\n\nconst NOT_A_BRAND_REGEX = /not.a.brand/i;\nconst HIGH_ENTROPY_HINTS = [\"bitness\", \"platformVersion\", \"fullVersionList\"];\n\nasync function getDeviceMetadata(): Promise<DeviceMetadata | null> {\n if (typeof navigator === \"undefined\") {\n return null;\n }\n\n const uaData = (navigator as Navigator & { userAgentData?: NavigatorUAData })\n .userAgentData;\n\n // `withClientHints()` resolves with UA-only data on non-Chromium and never\n // rejects. We separately request the raw high-entropy values so we can\n // surface `bitness`, the unrounded `platformVersion`, and the full\n // browser version — fields the parser folds into existing slots and\n // therefore loses precision on. `getHighEntropyValues` can reject with\n // `NotAllowedError` (Permissions Policy / Privacy Budget); swallow it.\n // The `InApps` extension is merged into the default regex map (see\n // `extend(defaultRegexes, extensions)` in ua-parser-js), so we still get\n // normal browser/OS detection plus tagging for in-app browsers (Slack,\n // Teams, Discord, VS Code, etc.) — those land as `browser.type: \"inapp\"`.\n const [parsed, hints] = await Promise.all([\n UAParser(navigator.userAgent, InApps).withClientHints(),\n uaData?.getHighEntropyValues\n ? uaData\n .getHighEntropyValues(HIGH_ENTROPY_HINTS)\n .catch((): HighEntropyValues => ({}))\n : Promise.resolve<HighEntropyValues>({}),\n ]);\n\n if (!parsed) {\n return null;\n }\n\n const fullVersion = hints.fullVersionList?.find(\n (b) => !NOT_A_BRAND_REGEX.test(b.brand)\n )?.version;\n\n return {\n ...parsed,\n browser: {\n ...parsed.browser,\n ...(fullVersion ? { fullVersion } : {}),\n },\n cpu: {\n ...parsed.cpu,\n ...(hints.bitness ? { bitness: hints.bitness } : {}),\n },\n os: {\n ...parsed.os,\n ...(hints.platformVersion ? { versionFull: hints.platformVersion } : {}),\n },\n frozenUa: isFrozenUA(navigator.userAgent),\n };\n}\n\nfunction getBrowserMetadata(): BrowserMetadata | null {\n if (\n [typeof navigator, typeof screen, typeof globalThis].some(\n (x) => x === \"undefined\"\n )\n ) {\n return null;\n }\n\n const { language } = navigator;\n\n return {\n language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n display: {\n screen: {\n height: screen.availHeight,\n width: screen.availWidth,\n orientation: screen.orientation?.type,\n },\n },\n };\n}\n\nexport async function collectContext(): Promise<BrowserContext> {\n return {\n runtime: \"browser\",\n browser: getBrowserMetadata(),\n device: await getDeviceMetadata(),\n };\n}\n"],"mappings":"4KA0BA,MAAM,kBAAoB,eACpB,mBAAqB,CAAC,UAAW,kBAAmB,iBAAiB,EAE3E,eAAe,mBAAoD,CACjE,GAAI,OAAO,UAAc,IACvB,OAAO,KAGT,IAAM,OAAU,UACb,cAYG,CAAC,OAAQ,OAAS,MAAM,QAAQ,IAAI,CACxC,SAAS,UAAU,UAAW,MAAM,EAAE,gBAAgB,EACtD,QAAQ,qBACJ,OACG,qBAAqB,kBAAkB,EACvC,WAAgC,CAAC,EAAE,EACtC,QAAQ,QAA2B,CAAC,CAAC,CAC3C,CAAC,EAED,GAAI,CAAC,OACH,OAAO,KAGT,IAAM,YAAc,MAAM,iBAAiB,KACxC,GAAM,CAAC,kBAAkB,KAAK,EAAE,KAAK,CACxC,GAAG,QAEH,MAAO,CACL,GAAG,OACH,QAAS,CACP,GAAG,OAAO,QACV,GAAI,YAAc,CAAE,WAAY,EAAI,CAAC,CACvC,EACA,IAAK,CACH,GAAG,OAAO,IACV,GAAI,MAAM,QAAU,CAAE,QAAS,MAAM,OAAQ,EAAI,CAAC,CACpD,EACA,GAAI,CACF,GAAG,OAAO,GACV,GAAI,MAAM,gBAAkB,CAAE,YAAa,MAAM,eAAgB,EAAI,CAAC,CACxE,EACA,SAAU,WAAW,UAAU,SAAS,CAC1C,CACF,CAEA,SAAS,oBAA6C,CACpD,GACE,CAAC,OAAO,UAAW,OAAO,OAAQ,OAAO,UAAU,EAAE,KAClD,GAAM,IAAM,WACf,EAEA,OAAO,KAGT,GAAM,CAAE,UAAa,UAErB,MAAO,CACL,SACA,SAAU,KAAK,eAAe,EAAE,gBAAgB,EAAE,SAClD,QAAS,CACP,OAAQ,CACN,OAAQ,OAAO,YACf,MAAO,OAAO,WACd,YAAa,OAAO,aAAa,IACnC,CACF,CACF,CACF,CAEA,eAAsB,gBAA0C,CAC9D,MAAO,CACL,QAAS,UACT,QAAS,mBAAmB,EAC5B,OAAQ,MAAM,kBAAkB,CAClC,CACF"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InterfereContext, InterfereContextValue } from "./react-context.mjs";
|
|
2
|
+
import { Component, ContextType, ReactNode } from "react";
|
|
2
3
|
|
|
3
4
|
//#region src/internal/capture-boundary.d.ts
|
|
4
5
|
interface CaptureBoundaryProps {
|
|
@@ -31,6 +32,8 @@ interface CaptureBoundaryState {
|
|
|
31
32
|
* captured the error but did not render a fallback — propagation continues.
|
|
32
33
|
*/
|
|
33
34
|
declare class CaptureBoundary extends Component<CaptureBoundaryProps, CaptureBoundaryState> {
|
|
35
|
+
static contextType: import("react").Context<InterfereContextValue | null>;
|
|
36
|
+
context: ContextType<typeof InterfereContext>;
|
|
34
37
|
state: CaptureBoundaryState;
|
|
35
38
|
static getDerivedStateFromError(error: Error): CaptureBoundaryState;
|
|
36
39
|
render(): ReactNode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture-boundary.d.mts","names":[],"sources":["../../src/internal/capture-boundary.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"capture-boundary.d.mts","names":[],"sources":["../../src/internal/capture-boundary.tsx"],"mappings":";;;;UASU,oBAAA;EACR,QAAA,EAAU,SAAS;AAAA;AAAA,UAGX,oBAAA;EACR,KAAA,EAAO,KAAK;AAAA;AAJO;AAAA;;;;AAIP;AA0Bd;;;;;;;;;;;;;;;;;AA9BqB,cA8BR,eAAA,SAAwB,SAAA,CACnC,oBAAA,EACA,oBAAA;EAAA,OAEgB,WAAA,kBAAW,OAAA,CAFP,qBAAA;EAGZ,OAAA,EAAS,WAAA,QAAmB,gBAAA;EAE3B,KAAA,EAAO,oBAAA;EAAA,OAET,wBAAA,CAAyB,KAAA,EAAO,KAAA,GAAQ,oBAAA;EAItC,MAAA,CAAA,GAAU,SAAA;AAAA"}
|
|
@@ -1,44 +1 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { captureReactError } from "./capture.mjs";
|
|
3
|
-
import { MECHANISM_TYPE } from "@interfere/types/sdk/errors";
|
|
4
|
-
import { Component } from "react";
|
|
5
|
-
//#region src/internal/capture-boundary.tsx
|
|
6
|
-
/**
|
|
7
|
-
* Internal boundary used by `<InterfereProvider>` to capture render-phase
|
|
8
|
-
* React errors without changing the app's UX.
|
|
9
|
-
*
|
|
10
|
-
* Unlike the public `ErrorBoundary`, this boundary always re-throws the
|
|
11
|
-
* captured error in its `render()` so upstream boundaries — the customer's
|
|
12
|
-
* own `ErrorBoundary`, Next.js's `error.tsx` / `global-error.tsx`, or React's
|
|
13
|
-
* default unmount — keep control of what the user sees. The net effect:
|
|
14
|
-
* zero visual change, full capture coverage for any render-phase error in
|
|
15
|
-
* the subtree.
|
|
16
|
-
*
|
|
17
|
-
* Capture happens inside `getDerivedStateFromError` rather than
|
|
18
|
-
* `componentDidCatch` because `componentDidCatch` does not fire on a boundary
|
|
19
|
-
* that re-throws in render — React considers such a boundary to have failed
|
|
20
|
-
* and skips its commit-phase lifecycle. The trade-off: we don't get
|
|
21
|
-
* `errorInfo.componentStack` in this capture. Callers who want the component
|
|
22
|
-
* tree should use the public `ErrorBoundary` (which renders a fallback and
|
|
23
|
-
* therefore receives `componentDidCatch`), or pass
|
|
24
|
-
* {@link reactErrorHandler} to `createRoot()`.
|
|
25
|
-
*
|
|
26
|
-
* `mechanism.handled` is `false` because from Interfere's perspective we
|
|
27
|
-
* captured the error but did not render a fallback — propagation continues.
|
|
28
|
-
*/
|
|
29
|
-
var CaptureBoundary = class extends Component {
|
|
30
|
-
state = { error: null };
|
|
31
|
-
static getDerivedStateFromError(error) {
|
|
32
|
-
captureReactError(error, null, {
|
|
33
|
-
type: MECHANISM_TYPE.react.captureBoundary,
|
|
34
|
-
handled: false
|
|
35
|
-
});
|
|
36
|
-
return { error };
|
|
37
|
-
}
|
|
38
|
-
render() {
|
|
39
|
-
if (this.state.error) throw this.state.error;
|
|
40
|
-
return this.props.children;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
//#endregion
|
|
44
|
-
export { CaptureBoundary };
|
|
1
|
+
"use client";import{captureReactError}from"./capture.mjs";import{InterfereContext}from"./react-context.mjs";import{MECHANISM_TYPE}from"@interfere/types/sdk/errors";import{Component}from"react";var CaptureBoundary=class extends Component{static contextType=InterfereContext;state={error:null};static getDerivedStateFromError(error){return{error}}render(){if(this.state.error)throw captureReactError(this.context?.kernel??null,this.state.error,null,{type:MECHANISM_TYPE.react.captureBoundary,handled:!1}),this.state.error;return this.props.children}};export{CaptureBoundary};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture-boundary.mjs","names":[],"sources":["../../src/internal/capture-boundary.tsx"],"sourcesContent":["\"use client\";\n\nimport { MECHANISM_TYPE } from \"@interfere/types/sdk/errors\";\n\nimport { Component, type ReactNode } from \"react\";\n\nimport { captureReactError } from \"./capture.js\";\n\ninterface CaptureBoundaryProps {\n children: ReactNode;\n}\n\ninterface CaptureBoundaryState {\n error: Error | null;\n}\n\n/**\n * Internal boundary used by `<InterfereProvider>` to capture render-phase\n * React errors without changing the app's UX.\n *\n * Unlike the public `ErrorBoundary`, this boundary always re-throws the\n * captured error in its `render()` so upstream boundaries — the customer's\n * own `ErrorBoundary`, Next.js's `error.tsx` / `global-error.tsx`, or React's\n * default unmount — keep control of what the user sees. The net effect:\n * zero visual change, full capture coverage for any render-phase error in\n * the subtree.\n *\n * Capture happens inside `getDerivedStateFromError` rather than\n * `componentDidCatch` because `componentDidCatch` does not fire on a boundary\n * that re-throws in render — React considers such a boundary to have failed\n * and skips its commit-phase lifecycle. The trade-off: we don't get\n * `errorInfo.componentStack` in this capture. Callers who want the component\n * tree should use the public `ErrorBoundary` (which renders a fallback and\n * therefore receives `componentDidCatch`), or pass\n * {@link reactErrorHandler} to `createRoot()`.\n *\n * `mechanism.handled` is `false` because from Interfere's perspective we\n * captured the error but did not render a fallback — propagation continues.\n */\nexport class CaptureBoundary extends Component<\n CaptureBoundaryProps,\n CaptureBoundaryState\n> {\n override state: CaptureBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: Error): CaptureBoundaryState {\n
|
|
1
|
+
{"version":3,"file":"capture-boundary.mjs","names":[],"sources":["../../src/internal/capture-boundary.tsx"],"sourcesContent":["\"use client\";\n\nimport { MECHANISM_TYPE } from \"@interfere/types/sdk/errors\";\n\nimport { Component, type ContextType, type ReactNode } from \"react\";\n\nimport { captureReactError } from \"./capture.js\";\nimport { InterfereContext } from \"./react-context.js\";\n\ninterface CaptureBoundaryProps {\n children: ReactNode;\n}\n\ninterface CaptureBoundaryState {\n error: Error | null;\n}\n\n/**\n * Internal boundary used by `<InterfereProvider>` to capture render-phase\n * React errors without changing the app's UX.\n *\n * Unlike the public `ErrorBoundary`, this boundary always re-throws the\n * captured error in its `render()` so upstream boundaries — the customer's\n * own `ErrorBoundary`, Next.js's `error.tsx` / `global-error.tsx`, or React's\n * default unmount — keep control of what the user sees. The net effect:\n * zero visual change, full capture coverage for any render-phase error in\n * the subtree.\n *\n * Capture happens inside `getDerivedStateFromError` rather than\n * `componentDidCatch` because `componentDidCatch` does not fire on a boundary\n * that re-throws in render — React considers such a boundary to have failed\n * and skips its commit-phase lifecycle. The trade-off: we don't get\n * `errorInfo.componentStack` in this capture. Callers who want the component\n * tree should use the public `ErrorBoundary` (which renders a fallback and\n * therefore receives `componentDidCatch`), or pass\n * {@link reactErrorHandler} to `createRoot()`.\n *\n * `mechanism.handled` is `false` because from Interfere's perspective we\n * captured the error but did not render a fallback — propagation continues.\n */\nexport class CaptureBoundary extends Component<\n CaptureBoundaryProps,\n CaptureBoundaryState\n> {\n static override contextType = InterfereContext;\n declare context: ContextType<typeof InterfereContext>;\n\n override state: CaptureBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: Error): CaptureBoundaryState {\n return { error };\n }\n\n override render(): ReactNode {\n if (this.state.error) {\n // Capture before re-throwing. `componentDidCatch` never fires when\n // render throws (React treats the boundary itself as failed), so the\n // capture has to happen here. `captureReactError` dedupes on the\n // error instance, so the multiple-render case in concurrent React\n // doesn't cause duplicate captures.\n captureReactError(this.context?.kernel ?? null, this.state.error, null, {\n type: MECHANISM_TYPE.react.captureBoundary,\n handled: false,\n });\n throw this.state.error;\n }\n return this.props.children;\n }\n}\n"],"mappings":"iMAwCA,IAAa,gBAAb,cAAqC,SAGnC,CACA,OAAgB,YAAc,iBAG9B,MAAuC,CAAE,MAAO,IAAK,EAErD,OAAO,yBAAyB,MAAoC,CAClE,MAAO,CAAE,KAAM,CACjB,CAEA,QAA6B,CAC3B,GAAI,KAAK,MAAM,MAUb,MAJA,kBAAkB,KAAK,SAAS,QAAU,KAAM,KAAK,MAAM,MAAO,KAAM,CACtE,KAAM,eAAe,MAAM,gBAC3B,QAAS,EACX,CAAC,EACK,KAAK,MAAM,MAEnB,OAAO,KAAK,MAAM,QACpB,CACF"}
|
|
@@ -1,13 +1,24 @@
|
|
|
1
|
+
import { Kernel } from "./kernel.mjs";
|
|
1
2
|
import { ErrorMechanism } from "@interfere/types/sdk/plugins/payload/errors";
|
|
2
3
|
|
|
3
4
|
//#region src/internal/capture.d.ts
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
* reported by React as additional frames.
|
|
6
|
+
* Routes a React error through `kernel.recordException()`, attaching the
|
|
7
|
+
* component stack reported by React as additional frames. Dedup of the
|
|
8
|
+
* same `Error` instance caught by both the boundary and `window.onerror`
|
|
9
|
+
* happens inside `recordException`.
|
|
7
10
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
11
|
+
* `error.digest` is React's identifier for an RSC server-side throw that
|
|
12
|
+
* was sanitised and rebuilt on the client — promote it so enrichment can
|
|
13
|
+
* fingerprint the redacted client capture into the same problem as the
|
|
14
|
+
* unredacted server capture.
|
|
15
|
+
*
|
|
16
|
+
* Accepts a nullable kernel — error boundaries can mount outside the
|
|
17
|
+
* `<InterfereProvider>` tree, in which case we silently skip capture and
|
|
18
|
+
* let the host render its fallback / call its `onError` callback.
|
|
10
19
|
*/
|
|
11
|
-
declare function captureReactError(
|
|
20
|
+
declare function captureReactError(kernel: Kernel | null, error: Error & {
|
|
21
|
+
digest?: string;
|
|
22
|
+
}, componentStack: string | null | undefined, mechanism: ErrorMechanism): void;
|
|
12
23
|
//#endregion
|
|
13
24
|
export { captureReactError };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.d.mts","names":[],"sources":["../../src/internal/capture.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"capture.d.mts","names":[],"sources":["../../src/internal/capture.ts"],"mappings":";;;;;;AAoBA;;;;;;;;;;;;;iBAAgB,iBAAA,CACd,MAAA,EAAQ,MAAA,SACR,KAAA,EAAO,KAAA;EAAU,MAAA;AAAA,GACjB,cAAA,6BACA,SAAA,EAAW,cAAA"}
|
|
@@ -1,23 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { getClient } from "./client.mjs";
|
|
3
|
-
import { parseReactComponentStack, shouldDropBrowserExtensionNoise, toExceptions } from "@interfere/types/sdk/errors";
|
|
4
|
-
//#region src/internal/capture.ts
|
|
5
|
-
/**
|
|
6
|
-
* Captures a React error through the SDK, attaching the component stack
|
|
7
|
-
* reported by React as additional frames. Dedupes on the Error instance.
|
|
8
|
-
*
|
|
9
|
-
* Swallows errors from `getClient()` so callers can run before the SDK is
|
|
10
|
-
* initialized (e.g. an error boundary catching a pre-init crash).
|
|
11
|
-
*/
|
|
12
|
-
function captureReactError(error, componentStack, mechanism) {
|
|
13
|
-
if (seen.has(error)) return;
|
|
14
|
-
seen.add(error);
|
|
15
|
-
try {
|
|
16
|
-
const exceptions = toExceptions(error, mechanism);
|
|
17
|
-
if (componentStack && exceptions[0]) exceptions[0].frames.push(...parseReactComponentStack(componentStack));
|
|
18
|
-
if (shouldDropBrowserExtensionNoise(exceptions)) return;
|
|
19
|
-
getClient().capture("error", { exceptions });
|
|
20
|
-
} catch {}
|
|
21
|
-
}
|
|
22
|
-
//#endregion
|
|
23
|
-
export { captureReactError };
|
|
1
|
+
import{parseReactComponentStack}from"@interfere/types/sdk/errors";function captureReactError(kernel,error,componentStack,mechanism){kernel&&kernel.recordException(error,{mechanism,...componentStack?{appendFrames:parseReactComponentStack(componentStack)}:{},...error.digest?{errorDigest:error.digest}:{}})}export{captureReactError};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.mjs","names":[],"sources":["../../src/internal/capture.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"capture.mjs","names":[],"sources":["../../src/internal/capture.ts"],"sourcesContent":["import { parseReactComponentStack } from \"@interfere/types/sdk/errors\";\nimport type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport type { Kernel } from \"./kernel.js\";\n\n/**\n * Routes a React error through `kernel.recordException()`, attaching the\n * component stack reported by React as additional frames. Dedup of the\n * same `Error` instance caught by both the boundary and `window.onerror`\n * happens inside `recordException`.\n *\n * `error.digest` is React's identifier for an RSC server-side throw that\n * was sanitised and rebuilt on the client — promote it so enrichment can\n * fingerprint the redacted client capture into the same problem as the\n * unredacted server capture.\n *\n * Accepts a nullable kernel — error boundaries can mount outside the\n * `<InterfereProvider>` tree, in which case we silently skip capture and\n * let the host render its fallback / call its `onError` callback.\n */\nexport function captureReactError(\n kernel: Kernel | null,\n error: Error & { digest?: string },\n componentStack: string | null | undefined,\n mechanism: ErrorMechanism\n): void {\n if (!kernel) {\n return;\n }\n kernel.recordException(error, {\n mechanism,\n ...(componentStack\n ? { appendFrames: parseReactComponentStack(componentStack) }\n : {}),\n ...(error.digest ? { errorDigest: error.digest } : {}),\n });\n}\n"],"mappings":"kEAoBA,SAAgB,kBACd,OACA,MACA,eACA,UACM,CACD,QAGL,OAAO,gBAAgB,MAAO,CAC5B,UACA,GAAI,eACA,CAAE,aAAc,yBAAyB,cAAc,CAAE,EACzD,CAAC,EACL,GAAI,MAAM,OAAS,CAAE,YAAa,MAAM,MAAO,EAAI,CAAC,CACtD,CAAC,CACH"}
|
|
@@ -1,10 +1,28 @@
|
|
|
1
|
-
import { IngestTarget } from "../transport/http.mjs";
|
|
2
|
-
|
|
3
1
|
//#region src/internal/config.d.ts
|
|
2
|
+
interface IngestTarget {
|
|
3
|
+
headers: Headers;
|
|
4
|
+
url: string;
|
|
5
|
+
}
|
|
6
|
+
interface AuthHeaders {
|
|
7
|
+
headers: Headers;
|
|
8
|
+
}
|
|
9
|
+
declare function withQueryParam(url: string, key: string, value: string): string;
|
|
10
|
+
declare function appendPathBeforeQuery(url: string, path: string): string;
|
|
4
11
|
declare function resolveTargets(): {
|
|
12
|
+
/**
|
|
13
|
+
* Base URL for OTLP exports — the trace/metric exporters append the
|
|
14
|
+
* sink path themselves. Avoids the kernel having to regex-strip
|
|
15
|
+
* `/v2/sink` off the configured URL to derive a base.
|
|
16
|
+
*/
|
|
17
|
+
collectorBaseUrl: string;
|
|
5
18
|
config: IngestTarget;
|
|
6
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Identity headers shared across every collector-bound request
|
|
21
|
+
* (OTLP exporters, replay upload, session sync). One source of
|
|
22
|
+
* truth for auth + force-enable.
|
|
23
|
+
*/
|
|
24
|
+
ingest: AuthHeaders;
|
|
7
25
|
session: IngestTarget;
|
|
8
26
|
};
|
|
9
27
|
//#endregion
|
|
10
|
-
export { resolveTargets };
|
|
28
|
+
export { AuthHeaders, IngestTarget, appendPathBeforeQuery, resolveTargets, withQueryParam };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.mts","names":[],"sources":["../../src/internal/config.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.mts","names":[],"sources":["../../src/internal/config.ts"],"mappings":";UAUiB,YAAA;EACf,OAAA,EAAS,OAAO;EAChB,GAAA;AAAA;AAAA,UAGe,WAAA;EACf,OAAA,EAAS,OAAO;AAAA;AAAA,iBA0CF,cAAA,CACd,GAAA,UACA,GAAA,UACA,KAAA;AAAA,iBAWc,qBAAA,CAAsB,GAAA,UAAa,IAAY;AAAA,iBAO/C,cAAA,CAAA;EAhEC;;;;AACC;EAqEhB,gBAAA;EACA,MAAA,EAAQ,YAAA;;;;;;EAMR,MAAA,EAAQ,WAAA;EACR,OAAA,EAAS,YAAA;AAAA"}
|
package/dist/internal/config.mjs
CHANGED
|
@@ -1,33 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
//#region src/internal/config.ts
|
|
3
|
-
const DEFAULT_PROXY_URL = "/api/interfere";
|
|
4
|
-
const DEFAULT_SESSION_PATH = "/v1/session";
|
|
5
|
-
const DEFAULT_CONFIG_PATH = "/v1/config";
|
|
6
|
-
function resolvePublicKey() {
|
|
7
|
-
const injected = globalThis["__INTERFERE_PUBLIC_KEY__"];
|
|
8
|
-
if (injected) return injected;
|
|
9
|
-
if (typeof process !== "undefined") return process.env["INTERFERE_PUBLIC_KEY"] ?? void 0;
|
|
10
|
-
}
|
|
11
|
-
function resolveTargets() {
|
|
12
|
-
const publicKey = resolvePublicKey();
|
|
13
|
-
const headers = new Headers({ "content-type": "application/json" });
|
|
14
|
-
if (publicKey) headers.set("x-interfere-pub-token", publicKey);
|
|
15
|
-
const baseUrl = publicKey ? API_URL : DEFAULT_PROXY_URL;
|
|
16
|
-
const sessionPath = API_PATHS.SESSION ?? DEFAULT_SESSION_PATH;
|
|
17
|
-
return {
|
|
18
|
-
config: {
|
|
19
|
-
url: `${baseUrl}${API_PATHS.CONFIG ?? DEFAULT_CONFIG_PATH}`,
|
|
20
|
-
headers
|
|
21
|
-
},
|
|
22
|
-
ingest: {
|
|
23
|
-
url: `${baseUrl}${API_PATHS.INGEST}`,
|
|
24
|
-
headers
|
|
25
|
-
},
|
|
26
|
-
session: {
|
|
27
|
-
url: `${baseUrl}${sessionPath}`,
|
|
28
|
-
headers
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
//#endregion
|
|
33
|
-
export { resolveTargets };
|
|
1
|
+
import{getGlobal}from"../util/global.mjs";import{API_PATHS,API_URL}from"@interfere/constants/api";import{resolveRoutePrefix}from"@interfere/constants/route-prefix";const TRAILING_SLASH_RE=/\/$/,RELATIVE_URL_BASE=`https://interfere.local`;function resolvePublicKey(){let injected=getGlobal(`__INTERFERE_PUBLIC_KEY__`);if(injected)return injected;if(typeof process<`u`)return process.env.INTERFERE_PUBLIC_KEY??void 0}function resolveApiUrlOverride(){let injected=getGlobal(`__INTERFERE_API_URL__`);if(injected)return injected}function isForceEnableActive(){return!!getGlobal(`__INTERFERE_FORCE_ENABLE__`)}function isProxyModeActive(){return!!getGlobal(`__INTERFERE_PROXY_MODE__`)}function serializeUrl(input,url){return input.startsWith(`/`)?`${url.pathname}${url.search}`:url.toString()}function withQueryParam(url,key,value){let next=new URL(url,RELATIVE_URL_BASE);return next.searchParams.set(key,value),serializeUrl(url,next)}function appendPublicKey(url,publicKey){return publicKey?withQueryParam(url,`pk`,publicKey):url}function appendPathBeforeQuery(url,path){let next=new URL(url,RELATIVE_URL_BASE),suffix=path.startsWith(`/`)?path:`/${path}`;return next.pathname=`${next.pathname.replace(TRAILING_SLASH_RE,``)}${suffix}`,serializeUrl(url,next)}function resolveTargets(){let publicKey=resolvePublicKey(),headers=new Headers({"content-type":`application/json`});isForceEnableActive()&&headers.set(`x-interfere-force-enable`,`1`);let baseUrl=publicKey&&!isProxyModeActive()?resolveApiUrlOverride()??API_URL:resolveRoutePrefix();return{collectorBaseUrl:appendPublicKey(baseUrl,publicKey),config:{url:appendPublicKey(`${baseUrl}${API_PATHS.CONFIG}`,publicKey),headers},ingest:{headers},session:{url:appendPublicKey(`${baseUrl}${API_PATHS.SESSION}`,publicKey),headers}}}export{appendPathBeforeQuery,resolveTargets,withQueryParam};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.mjs","names":[],"sources":["../../src/internal/config.ts"],"sourcesContent":["import { API_PATHS, API_URL } from \"@interfere/constants/api\";\n\nimport
|
|
1
|
+
{"version":3,"file":"config.mjs","names":[],"sources":["../../src/internal/config.ts"],"sourcesContent":["import { API_PATHS, API_URL } from \"@interfere/constants/api\";\nimport { resolveRoutePrefix } from \"@interfere/constants/route-prefix\";\n\nimport { getGlobal } from \"../util/global.js\";\n\nconst PUBLIC_KEY_QUERY = \"pk\";\nconst PROXY_MODE_GLOBAL = \"__INTERFERE_PROXY_MODE__\";\nconst TRAILING_SLASH_RE = /\\/$/;\nconst RELATIVE_URL_BASE = \"https://interfere.local\";\n\nexport interface IngestTarget {\n headers: Headers;\n url: string;\n}\n\nexport interface AuthHeaders {\n headers: Headers;\n}\n\nfunction resolvePublicKey(): string | undefined {\n // Vite plugin injects the key onto globalThis at build time\n const injected = getGlobal<string>(\"__INTERFERE_PUBLIC_KEY__\");\n if (injected) {\n return injected;\n }\n\n // Node / webpack / Next.js: read from process.env\n if (typeof process !== \"undefined\") {\n return process.env[\"INTERFERE_PUBLIC_KEY\"] ?? undefined;\n }\n\n return;\n}\n\nfunction resolveApiUrlOverride(): string | undefined {\n const injected = getGlobal<string>(\"__INTERFERE_API_URL__\");\n if (injected) {\n return injected;\n }\n\n return;\n}\n\nfunction isForceEnableActive(): boolean {\n return !!getGlobal<boolean>(\"__INTERFERE_FORCE_ENABLE__\");\n}\n\nfunction isProxyModeActive(): boolean {\n return !!getGlobal<boolean>(PROXY_MODE_GLOBAL);\n}\n\nfunction serializeUrl(input: string, url: URL): string {\n if (input.startsWith(\"/\")) {\n return `${url.pathname}${url.search}`;\n }\n return url.toString();\n}\n\nexport function withQueryParam(\n url: string,\n key: string,\n value: string\n): string {\n const next = new URL(url, RELATIVE_URL_BASE);\n next.searchParams.set(key, value);\n return serializeUrl(url, next);\n}\n\nfunction appendPublicKey(url: string, publicKey: string | undefined): string {\n return publicKey ? withQueryParam(url, PUBLIC_KEY_QUERY, publicKey) : url;\n}\n\nexport function appendPathBeforeQuery(url: string, path: string): string {\n const next = new URL(url, RELATIVE_URL_BASE);\n const suffix = path.startsWith(\"/\") ? path : `/${path}`;\n next.pathname = `${next.pathname.replace(TRAILING_SLASH_RE, \"\")}${suffix}`;\n return serializeUrl(url, next);\n}\n\nexport function resolveTargets(): {\n /**\n * Base URL for OTLP exports — the trace/metric exporters append the\n * sink path themselves. Avoids the kernel having to regex-strip\n * `/v2/sink` off the configured URL to derive a base.\n */\n collectorBaseUrl: string;\n config: IngestTarget;\n /**\n * Identity headers shared across every collector-bound request\n * (OTLP exporters, replay upload, session sync). One source of\n * truth for auth + force-enable.\n */\n ingest: AuthHeaders;\n session: IngestTarget;\n} {\n const publicKey = resolvePublicKey();\n const headers = new Headers({ \"content-type\": \"application/json\" });\n // Single chokepoint: every collector request (OTLP exporters, replay\n // upload, session, config) reuses these headers, so the force-enable\n // flag travels everywhere a customer event might. Production\n // collectors drop on this header (ENG-1356).\n if (isForceEnableActive()) {\n headers.set(\"x-interfere-force-enable\", \"1\");\n }\n\n const baseUrl =\n publicKey && !isProxyModeActive()\n ? (resolveApiUrlOverride() ?? API_URL)\n : resolveRoutePrefix();\n const authenticatedBaseUrl = appendPublicKey(baseUrl, publicKey);\n\n return {\n collectorBaseUrl: authenticatedBaseUrl,\n config: {\n url: appendPublicKey(`${baseUrl}${API_PATHS.CONFIG}`, publicKey),\n headers,\n },\n ingest: { headers },\n session: {\n url: appendPublicKey(`${baseUrl}${API_PATHS.SESSION}`, publicKey),\n headers,\n },\n };\n}\n"],"mappings":"oKAKA,MAEM,kBAAoB,MACpB,kBAAoB,0BAW1B,SAAS,kBAAuC,CAE9C,IAAM,SAAW,UAAkB,0BAA0B,EAC7D,GAAI,SACF,OAAO,SAIT,GAAI,OAAO,QAAY,IACrB,OAAO,QAAQ,IAAI,sBAA2B,IAAA,EAIlD,CAEA,SAAS,uBAA4C,CACnD,IAAM,SAAW,UAAkB,uBAAuB,EAC1D,GAAI,SACF,OAAO,QAIX,CAEA,SAAS,qBAA+B,CACtC,MAAO,CAAC,CAAC,UAAmB,4BAA4B,CAC1D,CAEA,SAAS,mBAA6B,CACpC,MAAO,CAAC,CAAC,UAAmB,0BAAiB,CAC/C,CAEA,SAAS,aAAa,MAAe,IAAkB,CAIrD,OAHI,MAAM,WAAW,GAAG,EACf,GAAG,IAAI,WAAW,IAAI,SAExB,IAAI,SAAS,CACtB,CAEA,SAAgB,eACd,IACA,IACA,MACQ,CACR,IAAM,KAAO,IAAI,IAAI,IAAK,iBAAiB,EAE3C,OADA,KAAK,aAAa,IAAI,IAAK,KAAK,EACzB,aAAa,IAAK,IAAI,CAC/B,CAEA,SAAS,gBAAgB,IAAa,UAAuC,CAC3E,OAAO,UAAY,eAAe,IAAK,KAAkB,SAAS,EAAI,GACxE,CAEA,SAAgB,sBAAsB,IAAa,KAAsB,CACvE,IAAM,KAAO,IAAI,IAAI,IAAK,iBAAiB,EACrC,OAAS,KAAK,WAAW,GAAG,EAAI,KAAO,IAAI,OAEjD,MADA,MAAK,SAAW,GAAG,KAAK,SAAS,QAAQ,kBAAmB,EAAE,IAAI,SAC3D,aAAa,IAAK,IAAI,CAC/B,CAEA,SAAgB,gBAed,CACA,IAAM,UAAY,iBAAiB,EAC7B,QAAU,IAAI,QAAQ,CAAE,eAAgB,kBAAmB,CAAC,EAK9D,oBAAoB,GACtB,QAAQ,IAAI,2BAA4B,GAAG,EAG7C,IAAM,QACJ,WAAa,CAAC,kBAAkB,EAC3B,sBAAsB,GAAK,QAC5B,mBAAmB,EAGzB,MAAO,CACL,iBAH2B,gBAAgB,QAAS,SAGf,EACrC,OAAQ,CACN,IAAK,gBAAgB,GAAG,UAAU,UAAU,SAAU,SAAS,EAC/D,OACF,EACA,OAAQ,CAAE,OAAQ,EAClB,QAAS,CACP,IAAK,gBAAgB,GAAG,UAAU,UAAU,UAAW,SAAS,EAChE,OACF,CACF,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consent.d.mts","names":[],"sources":["../../src/internal/consent.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"consent.d.mts","names":[],"sources":["../../src/internal/consent.ts"],"mappings":";;;;cASa,eAAA;EAAA,SAGoB,SAAA;EAAA,SAAA,MAAA;AAAA;AAAA,iBAcjB,wBAAA,CAAyB,GAAA,EAAK,SAAA,GAAY,eAAe;AAAA,iBAIzD,gBAAA,CACd,QAAA,EAAU,eAAA,EACV,YAAA,EAAc,YAAY;AAAA,iBASZ,kBAAA,CACd,IAAA,EAAM,SAAA,EACN,YAAA,EAAc,YAAY;AAAA,iBAKZ,qBAAA,CAAsB,OAAA,GAAU,YAAA,GAAe,YAAY;AAAA,iBAI3D,iBAAA,CACd,OAAA,EAAS,YAAA,SACT,IAAA,EAAM,YAAY"}
|
|
@@ -1,25 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
//#region src/internal/consent.ts
|
|
3
|
-
const DEFAULT_CONSENT = {
|
|
4
|
-
analytics: true,
|
|
5
|
-
replay: true
|
|
6
|
-
};
|
|
7
|
-
const PLUGIN_CONSENT_BY_KEY = Object.fromEntries(PLUGIN_MANIFEST.map((plugin) => [plugin.name, plugin.consentCategory]));
|
|
8
|
-
const EVENT_CONSENT_BY_TYPE = Object.fromEntries(PLUGIN_MANIFEST.flatMap((plugin) => plugin.events.map((event) => [event.name, plugin.consentCategory])));
|
|
9
|
-
function getPluginConsentCategory(key) {
|
|
10
|
-
return PLUGIN_CONSENT_BY_KEY[key];
|
|
11
|
-
}
|
|
12
|
-
function isConsentAllowed(category, consentState) {
|
|
13
|
-
return category === "necessary" || consentState === null || consentState[category] === true;
|
|
14
|
-
}
|
|
15
|
-
function shouldCaptureEvent(type, consentState) {
|
|
16
|
-
return isConsentAllowed(EVENT_CONSENT_BY_TYPE[type], consentState);
|
|
17
|
-
}
|
|
18
|
-
function resolveGrantedConsent(consent) {
|
|
19
|
-
return consent ?? { ...DEFAULT_CONSENT };
|
|
20
|
-
}
|
|
21
|
-
function hasConsentChanged(current, next) {
|
|
22
|
-
return current?.analytics !== next?.analytics || current?.replay !== next?.replay;
|
|
23
|
-
}
|
|
24
|
-
//#endregion
|
|
25
|
-
export { DEFAULT_CONSENT, getPluginConsentCategory, hasConsentChanged, isConsentAllowed, resolveGrantedConsent, shouldCaptureEvent };
|
|
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 consent??{...DEFAULT_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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consent.mjs","names":[],"sources":["../../src/internal/consent.ts"],"sourcesContent":["import type { EventType } from \"@interfere/types/sdk/envelope\";\nimport {\n type ConsentCategory,\n type ConsentState,\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\n\nexport const DEFAULT_CONSENT = {\n analytics: true,\n replay: true,\n} as const satisfies ConsentState;\n\nconst PLUGIN_CONSENT_BY_KEY = Object.fromEntries(\n PLUGIN_MANIFEST.map((plugin) => [plugin.name, plugin.consentCategory])\n) as Record<PluginKey, ConsentCategory>;\n\nconst EVENT_CONSENT_BY_TYPE = Object.fromEntries(\n PLUGIN_MANIFEST.flatMap((plugin) =>\n plugin.events.map((event) => [event.name, plugin.consentCategory] as const)\n )\n) as Record<EventType, ConsentCategory>;\n\nexport function getPluginConsentCategory(key: PluginKey): ConsentCategory {\n return PLUGIN_CONSENT_BY_KEY[key];\n}\n\nexport function isConsentAllowed(\n category: ConsentCategory,\n consentState: ConsentState | null\n): boolean {\n return (\n category === \"necessary\" ||\n consentState === null ||\n consentState[category] === true\n );\n}\n\nexport function shouldCaptureEvent(\n type: EventType,\n consentState: ConsentState | null\n): boolean {\n return isConsentAllowed(EVENT_CONSENT_BY_TYPE[type], consentState);\n}\n\nexport function resolveGrantedConsent(consent?: ConsentState): ConsentState {\n return consent ?? { ...DEFAULT_CONSENT };\n}\n\nexport function hasConsentChanged(\n current: ConsentState | null,\n next: ConsentState | null\n): boolean {\n
|
|
1
|
+
{"version":3,"file":"consent.mjs","names":[],"sources":["../../src/internal/consent.ts"],"sourcesContent":["import type { EventType } from \"@interfere/types/sdk/envelope\";\nimport {\n type ConsentCategory,\n type ConsentState,\n type GateableCategory,\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\n\nexport const DEFAULT_CONSENT = {\n analytics: true,\n replay: true,\n} as const satisfies ConsentState;\n\nconst PLUGIN_CONSENT_BY_KEY = Object.fromEntries(\n PLUGIN_MANIFEST.map((plugin) => [plugin.name, plugin.consentCategory])\n) as Record<PluginKey, ConsentCategory>;\n\nconst EVENT_CONSENT_BY_TYPE = Object.fromEntries(\n PLUGIN_MANIFEST.flatMap((plugin) =>\n plugin.events.map((event) => [event.name, plugin.consentCategory] as const)\n )\n) as Record<EventType, ConsentCategory>;\n\nconst GATEABLE_CATEGORIES = Object.keys(DEFAULT_CONSENT) as GateableCategory[];\n\nexport function getPluginConsentCategory(key: PluginKey): ConsentCategory {\n return PLUGIN_CONSENT_BY_KEY[key];\n}\n\nexport function isConsentAllowed(\n category: ConsentCategory,\n consentState: ConsentState | null\n): boolean {\n return (\n category === \"necessary\" ||\n consentState === null ||\n consentState[category] === true\n );\n}\n\nexport function shouldCaptureEvent(\n type: EventType,\n consentState: ConsentState | null\n): boolean {\n return isConsentAllowed(EVENT_CONSENT_BY_TYPE[type], consentState);\n}\n\nexport function resolveGrantedConsent(consent?: ConsentState): ConsentState {\n return consent ?? { ...DEFAULT_CONSENT };\n}\n\nexport function hasConsentChanged(\n current: ConsentState | null,\n next: ConsentState | null\n): boolean {\n for (const category of GATEABLE_CATEGORIES) {\n if (current?.[category] !== next?.[category]) {\n return true;\n }\n }\n return false;\n}\n"],"mappings":"mEASA,MAAa,gBAAkB,CAC7B,UAAW,GACX,OAAQ,EACV,EAEM,sBAAwB,OAAO,YACnC,gBAAgB,IAAK,QAAW,CAAC,OAAO,KAAM,OAAO,eAAe,CAAC,CACvE,EAEM,sBAAwB,OAAO,YACnC,gBAAgB,QAAS,QACvB,OAAO,OAAO,IAAK,OAAU,CAAC,MAAM,KAAM,OAAO,eAAe,CAAU,CAC5E,CACF,EAEM,oBAAsB,OAAO,KAAK,eAAe,EAEvD,SAAgB,yBAAyB,IAAiC,CACxE,OAAO,sBAAsB,IAC/B,CAEA,SAAgB,iBACd,SACA,aACS,CACT,OACE,WAAa,aACb,eAAiB,MACjB,aAAa,YAAc,EAE/B,CAEA,SAAgB,mBACd,KACA,aACS,CACT,OAAO,iBAAiB,sBAAsB,MAAO,YAAY,CACnE,CAEA,SAAgB,sBAAsB,QAAsC,CAC1E,OAAO,SAAW,CAAE,GAAG,eAAgB,CACzC,CAEA,SAAgB,kBACd,QACA,KACS,CACT,IAAK,IAAM,YAAY,oBACrB,GAAI,UAAU,YAAc,OAAO,UACjC,MAAO,GAGX,MAAO,EACT"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/internal/console-patch.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Single point of `console.{debug,log,info,warn,error}` patching. The
|
|
4
|
+
* errors plugin and the logs plugin both want to observe console calls;
|
|
5
|
+
* before this consolidation, each patched independently and the
|
|
6
|
+
* resulting wrap-order coupling made teardown leaky.
|
|
7
|
+
*
|
|
8
|
+
* Now: a single set of patched methods is installed on first
|
|
9
|
+
* subscription, fans out to every subscriber, and is uninstalled on the
|
|
10
|
+
* last unsubscription. Handlers never wrap each other; they observe.
|
|
11
|
+
*/
|
|
12
|
+
declare const CONSOLE_LEVELS: readonly ["debug", "log", "info", "warn", "error"];
|
|
13
|
+
type ConsoleLevel = (typeof CONSOLE_LEVELS)[number];
|
|
14
|
+
type ConsoleHandler = (level: ConsoleLevel, args: unknown[]) => void;
|
|
15
|
+
declare function onConsoleCall(handler: ConsoleHandler): () => void;
|
|
16
|
+
/** Test hook — drops every handler and restores originals. */
|
|
17
|
+
declare function _resetConsolePatchForTests(): void;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { CONSOLE_LEVELS, ConsoleHandler, ConsoleLevel, _resetConsolePatchForTests, onConsoleCall };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console-patch.d.mts","names":[],"sources":["../../src/internal/console-patch.ts"],"mappings":";;AAUA;;;;AAMU;AAEV;;;;cARa,cAAA;AAAA,KAQD,YAAA,WAAuB,cAAc;AAAA,KAErC,cAAA,IAAkB,KAAA,EAAO,YAAY,EAAE,IAAA;AAAA,iBAiDnC,aAAA,CAAc,OAAuB,EAAd,cAAc;;iBAYrC,0BAAA,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const CONSOLE_LEVELS=[`debug`,`log`,`info`,`warn`,`error`],handlers=new Set,originals={};let installed=!1;function install(){if(installed)return;installed=!0;let target=globalThis.console;for(let level of CONSOLE_LEVELS){let orig=target[level];typeof orig==`function`&&(originals[level]=orig,target[level]=(...args)=>{orig.apply(globalThis.console,args);for(let handler of handlers)handler(level,args)})}}function uninstall(){if(!installed)return;installed=!1;let target=globalThis.console;for(let level of CONSOLE_LEVELS){let orig=originals[level];orig&&(target[level]=orig,delete originals[level])}}function onConsoleCall(handler){return install(),handlers.add(handler),()=>{handlers.delete(handler),handlers.size===0&&uninstall()}}function _resetConsolePatchForTests(){handlers.clear(),uninstall()}export{CONSOLE_LEVELS,_resetConsolePatchForTests,onConsoleCall};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console-patch.mjs","names":[],"sources":["../../src/internal/console-patch.ts"],"sourcesContent":["/**\n * Single point of `console.{debug,log,info,warn,error}` patching. The\n * errors plugin and the logs plugin both want to observe console calls;\n * before this consolidation, each patched independently and the\n * resulting wrap-order coupling made teardown leaky.\n *\n * Now: a single set of patched methods is installed on first\n * subscription, fans out to every subscriber, and is uninstalled on the\n * last unsubscription. Handlers never wrap each other; they observe.\n */\nexport const CONSOLE_LEVELS = [\n \"debug\",\n \"log\",\n \"info\",\n \"warn\",\n \"error\",\n] as const;\n\nexport type ConsoleLevel = (typeof CONSOLE_LEVELS)[number];\n\nexport type ConsoleHandler = (level: ConsoleLevel, args: unknown[]) => void;\n\nconst handlers = new Set<ConsoleHandler>();\nconst originals: Partial<Record<ConsoleLevel, (...args: unknown[]) => void>> =\n {};\nlet installed = false;\n\nfunction install(): void {\n if (installed) {\n return;\n }\n installed = true;\n const target = globalThis.console as unknown as Record<\n ConsoleLevel,\n (...args: unknown[]) => void\n >;\n for (const level of CONSOLE_LEVELS) {\n const orig = target[level];\n if (typeof orig !== \"function\") {\n continue;\n }\n originals[level] = orig;\n target[level] = (...args: unknown[]) => {\n orig.apply(globalThis.console, args);\n for (const handler of handlers) {\n handler(level, args);\n }\n };\n }\n}\n\nfunction uninstall(): void {\n if (!installed) {\n return;\n }\n installed = false;\n const target = globalThis.console as unknown as Record<\n ConsoleLevel,\n (...args: unknown[]) => void\n >;\n for (const level of CONSOLE_LEVELS) {\n const orig = originals[level];\n if (orig) {\n target[level] = orig;\n delete originals[level];\n }\n }\n}\n\nexport function onConsoleCall(handler: ConsoleHandler): () => void {\n install();\n handlers.add(handler);\n return () => {\n handlers.delete(handler);\n if (handlers.size === 0) {\n uninstall();\n }\n };\n}\n\n/** Test hook — drops every handler and restores originals. */\nexport function _resetConsolePatchForTests(): void {\n handlers.clear();\n uninstall();\n}\n"],"mappings":"AAUA,MAAa,eAAiB,CAC5B,QACA,MACA,OACA,OACA,OACF,EAMM,SAAW,IAAI,IACf,UACJ,CAAC,EACH,IAAI,UAAY,GAEhB,SAAS,SAAgB,CACvB,GAAI,UACF,OAEF,UAAY,GACZ,IAAM,OAAS,WAAW,QAI1B,IAAK,IAAM,SAAS,eAAgB,CAClC,IAAM,KAAO,OAAO,OAChB,OAAO,MAAS,aAGpB,UAAU,OAAS,KACnB,OAAO,QAAU,GAAG,OAAoB,CACtC,KAAK,MAAM,WAAW,QAAS,IAAI,EACnC,IAAK,IAAM,WAAW,SACpB,QAAQ,MAAO,IAAI,CAEvB,EACF,CACF,CAEA,SAAS,WAAkB,CACzB,GAAI,CAAC,UACH,OAEF,UAAY,GACZ,IAAM,OAAS,WAAW,QAI1B,IAAK,IAAM,SAAS,eAAgB,CAClC,IAAM,KAAO,UAAU,OACnB,OACF,OAAO,OAAS,KAChB,OAAO,UAAU,OAErB,CACF,CAEA,SAAgB,cAAc,QAAqC,CAGjE,OAFA,QAAQ,EACR,SAAS,IAAI,OAAO,MACP,CACX,SAAS,OAAO,OAAO,EACnB,SAAS,OAAS,GACpB,UAAU,CAEd,CACF,CAGA,SAAgB,4BAAmC,CACjD,SAAS,MAAM,EACf,UAAU,CACZ"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/internal/dom/actionable.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Single source of truth for "what counts as a clickable element" across
|
|
4
|
+
* the SDK. Used by both the `pages` plugin (which captures `ui_event`
|
|
5
|
+
* envelopes) and the OTel `UserInteractionInstrumentation` enrichment
|
|
6
|
+
* hook (which gates span creation + stamps target attrs). Before this
|
|
7
|
+
* was extracted, the two sites diverged and produced inconsistent
|
|
8
|
+
* notions of "actionable."
|
|
9
|
+
*
|
|
10
|
+
* `data-track-click` is the customer escape hatch — any element opted
|
|
11
|
+
* in via that attribute is treated as actionable regardless of tag/role.
|
|
12
|
+
*/
|
|
13
|
+
declare function isActionable(el: Element): boolean;
|
|
14
|
+
declare function closestActionable(target: EventTarget | null): Element | null;
|
|
15
|
+
interface ActionableDescriptor {
|
|
16
|
+
ariaLabel?: string | undefined;
|
|
17
|
+
href?: string | undefined;
|
|
18
|
+
id?: string | undefined;
|
|
19
|
+
name?: string | undefined;
|
|
20
|
+
role?: string | undefined;
|
|
21
|
+
tag: string;
|
|
22
|
+
text?: string | undefined;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
declare function describeActionable(el: Element): ActionableDescriptor;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { ActionableDescriptor, closestActionable, describeActionable, isActionable };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actionable.d.mts","names":[],"sources":["../../../src/internal/dom/actionable.ts"],"mappings":";;AAkCA;;;;AAAwC;AAWxC;;;;;iBAXgB,YAAA,CAAa,EAAW,EAAP,OAAO;AAAA,iBAWxB,iBAAA,CAAkB,MAAA,EAAQ,WAAA,UAAqB,OAAO;AAAA,UAcrD,oBAAA;EACf,SAAA;EACA,IAAA;EACA,EAAA;EACA,IAAA;EACA,IAAA;EACA,GAAA;EACA,IAAA;EAAA,CACC,GAAA;AAAA;AAAA,iBAGa,kBAAA,CAAmB,EAAA,EAAI,OAAA,GAAU,oBAAoB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const ACTIONABLE_TAGS=new Set([`a`,`button`,`input`,`select`,`textarea`,`summary`,`label`]),ACTIONABLE_ROLES=new Set([`button`,`link`,`menuitem`,`tab`,`checkbox`,`radio`,`switch`]);function isActionable(el){if(ACTIONABLE_TAGS.has(el.tagName.toLowerCase()))return!0;let role=el.getAttribute(`role`);return role&&ACTIONABLE_ROLES.has(role)?!0:el.hasAttribute(`data-track-click`)}function closestActionable(target){if(!(target instanceof Element))return null;let el=target;for(;el;){if(isActionable(el))return el;el=el.parentElement}return null}function describeActionable(el){let desc={tag:el.tagName.toLowerCase()};el.id&&(desc.id=el.id);let role=el.getAttribute(`role`);role&&(desc.role=role);let name=el.getAttribute(`name`);name&&(desc.name=name);let ariaLabel=el.getAttribute(`aria-label`);ariaLabel&&(desc.ariaLabel=ariaLabel),el instanceof HTMLAnchorElement&&(desc.href=el.href);let text=el.textContent?.trim().slice(0,120);return text&&(desc.text=text),desc}export{closestActionable,describeActionable,isActionable};
|