@interfere/react 9.0.2 → 10.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/api.d.mts +25 -0
- package/dist/api.d.mts.map +1 -0
- package/dist/api.mjs +68 -0
- package/dist/api.mjs.map +1 -0
- package/dist/error-boundary.d.mts +11 -4
- package/dist/error-boundary.d.mts.map +1 -1
- package/dist/error-boundary.mjs +6 -3
- 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 +59 -0
- package/dist/internal/browser-context.mjs.map +1 -0
- package/dist/internal/capture-boundary.d.mts +5 -1
- package/dist/internal/capture-boundary.d.mts.map +1 -1
- package/dist/internal/capture-boundary.mjs +9 -5
- 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 +20 -16
- package/dist/internal/capture.mjs.map +1 -1
- package/dist/internal/config.d.mts +20 -4
- package/dist/internal/config.d.mts.map +1 -1
- package/dist/internal/config.mjs +12 -12
- package/dist/internal/config.mjs.map +1 -1
- package/dist/internal/consent.d.mts.map +1 -1
- package/dist/internal/consent.mjs +3 -1
- 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 +62 -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 +62 -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 +31 -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 +322 -0
- package/dist/internal/kernel.mjs.map +1 -0
- package/dist/internal/otel/exporter.d.mts +93 -0
- package/dist/internal/otel/exporter.d.mts.map +1 -0
- package/dist/internal/otel/exporter.mjs +212 -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 +6 -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 +150 -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 +36 -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 +40 -0
- package/dist/internal/otel/propagation.mjs.map +1 -0
- package/dist/internal/otel/provider.d.mts +107 -0
- package/dist/internal/otel/provider.d.mts.map +1 -0
- package/dist/internal/otel/provider.mjs +151 -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 +162 -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 +33 -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 -7
- package/dist/internal/plugin-runtime.mjs.map +1 -1
- package/dist/internal/react-context.d.mts +45 -0
- package/dist/internal/react-context.d.mts.map +1 -0
- package/dist/internal/react-context.mjs +34 -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 +30 -3
- 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 +4 -2
- 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 +73 -0
- package/dist/internal/wrapper-singleton.mjs.map +1 -0
- package/dist/package.mjs +1 -1
- package/dist/plugins/errors.d.mts.map +1 -1
- package/dist/plugins/errors.mjs +18 -25
- 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 +2 -11
- 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/logs.d.mts +13 -0
- package/dist/plugins/logs.d.mts.map +1 -0
- package/dist/plugins/logs.mjs +53 -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 +12 -10
- package/dist/plugins/rage-clicks.mjs.map +1 -1
- package/dist/plugins/replay.d.mts.map +1 -1
- package/dist/plugins/replay.mjs +58 -19
- 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 +13 -14
- 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 +15 -7
- 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 +122 -104
- 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 +70 -46
- 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 +33 -29
- 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.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 +14 -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 +12 -0
- package/dist/util/global.mjs.map +1 -0
- package/dist/util/log.d.mts.map +1 -1
- package/dist/util/log.mjs +8 -1
- 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 +16 -0
- package/dist/util/stringify.mjs.map +1 -0
- package/package.json +73 -20
- 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,KAAA"}
|
package/dist/api.mjs
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { activeKernel } from "./internal/kernel-registry.mjs";
|
|
2
|
+
import { MECHANISM_TYPE, toError, toException } from "@interfere/types/sdk/errors";
|
|
3
|
+
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
4
|
+
//#region src/api.ts
|
|
5
|
+
const TRACER_NAME = "@interfere/react";
|
|
6
|
+
function tracer() {
|
|
7
|
+
return trace.getTracer(TRACER_NAME);
|
|
8
|
+
}
|
|
9
|
+
function isPromise(value) {
|
|
10
|
+
return !!value && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Wraps `fn` in an OTel span. The span becomes the active context for the
|
|
14
|
+
* duration of the call (sync) or the awaited promise (async). Errors thrown
|
|
15
|
+
* by `fn` are recorded on the span before re-throwing — caller still owns
|
|
16
|
+
* the error.
|
|
17
|
+
*
|
|
18
|
+
* Works pre-init: `trace.getTracer()` returns a no-op tracer that still
|
|
19
|
+
* runs `fn` and propagates its return value, so call sites don't need
|
|
20
|
+
* defensive checks.
|
|
21
|
+
*/
|
|
22
|
+
function span(name, fn, options) {
|
|
23
|
+
return tracer().startActiveSpan(name, options ?? {}, (s) => {
|
|
24
|
+
try {
|
|
25
|
+
const result = fn(s);
|
|
26
|
+
if (isPromise(result)) return result.then((value) => {
|
|
27
|
+
s.end();
|
|
28
|
+
return value;
|
|
29
|
+
}, (err) => {
|
|
30
|
+
const e = toError(err);
|
|
31
|
+
s.recordException(e);
|
|
32
|
+
s.setStatus({
|
|
33
|
+
code: SpanStatusCode.ERROR,
|
|
34
|
+
message: e.message
|
|
35
|
+
});
|
|
36
|
+
s.end();
|
|
37
|
+
throw err;
|
|
38
|
+
});
|
|
39
|
+
s.end();
|
|
40
|
+
return result;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
const e = toError(err);
|
|
43
|
+
s.recordException(e);
|
|
44
|
+
s.setStatus({
|
|
45
|
+
code: SpanStatusCode.ERROR,
|
|
46
|
+
message: e.message
|
|
47
|
+
});
|
|
48
|
+
s.end();
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Captures an error. Records on the active OTel span (when present) AND on
|
|
55
|
+
* the legacy envelope path. Accepts any thrown value; non-Error inputs that
|
|
56
|
+
* carry structure (objects without a recoverable nested Error) are preserved
|
|
57
|
+
* via `toException` so the original shape survives to the wire.
|
|
58
|
+
*
|
|
59
|
+
* Safe to call before init — drops silently if no kernel is registered.
|
|
60
|
+
*/
|
|
61
|
+
function capture(error) {
|
|
62
|
+
activeKernel()?.recordException(toException(error), { mechanism: {
|
|
63
|
+
type: MECHANISM_TYPE.manual.capture,
|
|
64
|
+
handled: true
|
|
65
|
+
} });
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
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":";;;;AAeA,MAAM,cAAc;AAEpB,SAAS,SAAS;CAChB,OAAO,MAAM,UAAU,YAAY;;AAGrC,SAAS,UAAa,OAAqC;CACzD,OACE,CAAC,CAAC,UACD,OAAO,UAAU,YAAY,OAAO,UAAU,eAC/C,OAAQ,MAA6B,SAAS;;;;;;;;;;;;AAclD,SAAgB,KACd,MACA,IACA,SACG;CACH,OAAO,QAAQ,CAAC,gBAAgB,MAAM,WAAW,EAAE,GAAG,MAAS;EAC7D,IAAI;GACF,MAAM,SAAS,GAAG,EAAE;GACpB,IAAI,UAAa,OAAO,EACtB,OAAO,OAAO,MACX,UAAU;IACT,EAAE,KAAK;IACP,OAAO;OAER,QAAiB;IAChB,MAAM,IAAI,QAAQ,IAAI;IACtB,EAAE,gBAAgB,EAAE;IACpB,EAAE,UAAU;KAAE,MAAM,eAAe;KAAO,SAAS,EAAE;KAAS,CAAC;IAC/D,EAAE,KAAK;IACP,MAAM;KAET;GAEH,EAAE,KAAK;GACP,OAAO;WACA,KAAK;GACZ,MAAM,IAAI,QAAQ,IAAI;GACtB,EAAE,gBAAgB,EAAE;GACpB,EAAE,UAAU;IAAE,MAAM,eAAe;IAAO,SAAS,EAAE;IAAS,CAAC;GAC/D,EAAE,KAAK;GACP,MAAM;;GAER;;;;;;;;;;AAWJ,SAAgB,QAAQ,OAAsB;CAC5C,cAAc,EAAE,gBAAgB,YAAY,MAAM,EAAE,EAClD,WAAW;EAAE,MAAM,eAAe,OAAO;EAAS,SAAS;EAAM,EAClE,CAAC"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InterfereContext, InterfereContextValue } from "./internal/react-context.mjs";
|
|
2
|
+
import * as _$react from "react";
|
|
3
|
+
import { Component, ContextType, ErrorInfo, ReactNode } from "react";
|
|
2
4
|
|
|
3
5
|
//#region src/error-boundary.d.ts
|
|
4
6
|
interface ErrorBoundaryProps {
|
|
@@ -11,15 +13,20 @@ interface ErrorBoundaryState {
|
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* Catches render-phase React errors, reports them to the SDK, and renders a
|
|
14
|
-
* fallback.
|
|
15
|
-
*
|
|
16
|
+
* fallback. Reads the kernel from `InterfereContext`; mounting outside the
|
|
17
|
+
* provider is supported — capture is silently skipped, fallback + `onError`
|
|
18
|
+
* still fire.
|
|
16
19
|
*
|
|
17
20
|
* Class component required — React has no hook-based error boundary API.
|
|
18
21
|
*/
|
|
19
22
|
declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
23
|
+
static contextType: _$react.Context<InterfereContextValue | null>;
|
|
24
|
+
context: ContextType<typeof InterfereContext>;
|
|
20
25
|
state: ErrorBoundaryState;
|
|
21
26
|
static getDerivedStateFromError(error: Error): ErrorBoundaryState;
|
|
22
|
-
componentDidCatch(error: Error
|
|
27
|
+
componentDidCatch(error: Error & {
|
|
28
|
+
digest?: string;
|
|
29
|
+
}, info: ErrorInfo): void;
|
|
23
30
|
private readonly reset;
|
|
24
31
|
render(): ReactNode;
|
|
25
32
|
}
|
|
@@ -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,KAAA;AAAA;;;;;;;;;cAWI,aAAA,SAAsB,SAAA,CACjC,kBAAA,EACA,kBAAA;EAAA,OAEgB,WAAA,EAAW,OAAA,CAAA,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,22 +1,25 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { captureReactError } from "./internal/capture.mjs";
|
|
3
|
+
import { InterfereContext } from "./internal/react-context.mjs";
|
|
3
4
|
import { MECHANISM_TYPE } from "@interfere/types/sdk/errors";
|
|
4
5
|
import { Component } from "react";
|
|
5
6
|
//#region src/error-boundary.tsx
|
|
6
7
|
/**
|
|
7
8
|
* Catches render-phase React errors, reports them to the SDK, and renders a
|
|
8
|
-
* fallback.
|
|
9
|
-
*
|
|
9
|
+
* fallback. Reads the kernel from `InterfereContext`; mounting outside the
|
|
10
|
+
* provider is supported — capture is silently skipped, fallback + `onError`
|
|
11
|
+
* still fire.
|
|
10
12
|
*
|
|
11
13
|
* Class component required — React has no hook-based error boundary API.
|
|
12
14
|
*/
|
|
13
15
|
var ErrorBoundary = class extends Component {
|
|
16
|
+
static contextType = InterfereContext;
|
|
14
17
|
state = { error: null };
|
|
15
18
|
static getDerivedStateFromError(error) {
|
|
16
19
|
return { error };
|
|
17
20
|
}
|
|
18
21
|
componentDidCatch(error, info) {
|
|
19
|
-
captureReactError(error, info.componentStack, {
|
|
22
|
+
captureReactError(this.context?.kernel ?? null, error, info.componentStack, {
|
|
20
23
|
type: MECHANISM_TYPE.react.errorBoundary,
|
|
21
24
|
handled: true
|
|
22
25
|
});
|
|
@@ -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":";;;;;;;;;;;;;;AAgCA,IAAa,gBAAb,cAAmC,UAGjC;CACA,OAAgB,cAAc;CAG9B,QAAqC,EAAE,OAAO,MAAM;CAEpD,OAAO,yBAAyB,OAAkC;EAChE,OAAO,EAAE,OAAO;;CAGlB,kBACE,OACA,MACA;EACA,kBACE,KAAK,SAAS,UAAU,MACxB,OACA,KAAK,gBACL;GACE,MAAM,eAAe,MAAM;GAC3B,SAAS;GACV,CACF;EAED,KAAK,MAAM,UAAU,OAAO,KAAK;;CAGnC,cAA+B;EAC7B,KAAK,SAAS,EAAE,OAAO,MAAM,CAAC;;CAGhC,SAAkB;EAChB,MAAM,EAAE,UAAU,KAAK;EAEvB,IAAI,OAAO;GACT,MAAM,EAAE,aAAa,KAAK;GAC1B,IAAI,OAAO,aAAa,YACtB,OAAO,SAAS,OAAO,KAAK,MAAM;GAEpC,OAAO,YAAY;;EAGrB,OAAO,KAAK,MAAM"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-context.d.mts","names":[],"sources":["../../src/internal/browser-context.ts"],"mappings":";;;iBA0GsB,cAAA,CAAA,GAAkB,OAAA,CAAQ,cAAA"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { UAParser } from "@ua-parser-js/pro-enterprise";
|
|
2
|
+
import { InApps } from "@ua-parser-js/pro-enterprise/extensions";
|
|
3
|
+
import { isFrozenUA } from "@ua-parser-js/pro-enterprise/helpers";
|
|
4
|
+
//#region src/internal/browser-context.ts
|
|
5
|
+
const NOT_A_BRAND_REGEX = /not.a.brand/i;
|
|
6
|
+
const HIGH_ENTROPY_HINTS = [
|
|
7
|
+
"bitness",
|
|
8
|
+
"platformVersion",
|
|
9
|
+
"fullVersionList"
|
|
10
|
+
];
|
|
11
|
+
async function getDeviceMetadata() {
|
|
12
|
+
if (typeof navigator === "undefined") return null;
|
|
13
|
+
const uaData = navigator.userAgentData;
|
|
14
|
+
const [parsed, hints] = await Promise.all([UAParser(navigator.userAgent, InApps).withClientHints(), uaData?.getHighEntropyValues ? uaData.getHighEntropyValues(HIGH_ENTROPY_HINTS).catch(() => ({})) : Promise.resolve({})]);
|
|
15
|
+
if (!parsed) return null;
|
|
16
|
+
const fullVersion = hints.fullVersionList?.find((b) => !NOT_A_BRAND_REGEX.test(b.brand))?.version;
|
|
17
|
+
return {
|
|
18
|
+
...parsed,
|
|
19
|
+
browser: {
|
|
20
|
+
...parsed.browser,
|
|
21
|
+
...fullVersion ? { fullVersion } : {}
|
|
22
|
+
},
|
|
23
|
+
cpu: {
|
|
24
|
+
...parsed.cpu,
|
|
25
|
+
...hints.bitness ? { bitness: hints.bitness } : {}
|
|
26
|
+
},
|
|
27
|
+
os: {
|
|
28
|
+
...parsed.os,
|
|
29
|
+
...hints.platformVersion ? { versionFull: hints.platformVersion } : {}
|
|
30
|
+
},
|
|
31
|
+
frozenUa: isFrozenUA(navigator.userAgent)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function getBrowserMetadata() {
|
|
35
|
+
if ([
|
|
36
|
+
typeof navigator,
|
|
37
|
+
typeof screen,
|
|
38
|
+
typeof globalThis
|
|
39
|
+
].some((x) => x === "undefined")) return null;
|
|
40
|
+
const { language } = navigator;
|
|
41
|
+
return {
|
|
42
|
+
language,
|
|
43
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
44
|
+
display: { screen: {
|
|
45
|
+
height: screen.availHeight,
|
|
46
|
+
width: screen.availWidth,
|
|
47
|
+
orientation: screen.orientation?.type
|
|
48
|
+
} }
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async function collectContext() {
|
|
52
|
+
return {
|
|
53
|
+
runtime: "browser",
|
|
54
|
+
browser: getBrowserMetadata(),
|
|
55
|
+
device: await getDeviceMetadata()
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
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":";;;;AA0BA,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB;CAAC;CAAW;CAAmB;CAAkB;AAE5E,eAAe,oBAAoD;CACjE,IAAI,OAAO,cAAc,aACvB,OAAO;CAGT,MAAM,SAAU,UACb;CAYH,MAAM,CAAC,QAAQ,SAAS,MAAM,QAAQ,IAAI,CACxC,SAAS,UAAU,WAAW,OAAO,CAAC,iBAAiB,EACvD,QAAQ,uBACJ,OACG,qBAAqB,mBAAmB,CACxC,aAAgC,EAAE,EAAE,GACvC,QAAQ,QAA2B,EAAE,CAAC,CAC3C,CAAC;CAEF,IAAI,CAAC,QACH,OAAO;CAGT,MAAM,cAAc,MAAM,iBAAiB,MACxC,MAAM,CAAC,kBAAkB,KAAK,EAAE,MAAM,CACxC,EAAE;CAEH,OAAO;EACL,GAAG;EACH,SAAS;GACP,GAAG,OAAO;GACV,GAAI,cAAc,EAAE,aAAa,GAAG,EAAE;GACvC;EACD,KAAK;GACH,GAAG,OAAO;GACV,GAAI,MAAM,UAAU,EAAE,SAAS,MAAM,SAAS,GAAG,EAAE;GACpD;EACD,IAAI;GACF,GAAG,OAAO;GACV,GAAI,MAAM,kBAAkB,EAAE,aAAa,MAAM,iBAAiB,GAAG,EAAE;GACxE;EACD,UAAU,WAAW,UAAU,UAAU;EAC1C;;AAGH,SAAS,qBAA6C;CACpD,IACE;EAAC,OAAO;EAAW,OAAO;EAAQ,OAAO;EAAW,CAAC,MAClD,MAAM,MAAM,YACd,EAED,OAAO;CAGT,MAAM,EAAE,aAAa;CAErB,OAAO;EACL;EACA,UAAU,KAAK,gBAAgB,CAAC,iBAAiB,CAAC;EAClD,SAAS,EACP,QAAQ;GACN,QAAQ,OAAO;GACf,OAAO,OAAO;GACd,aAAa,OAAO,aAAa;GAClC,EACF;EACF;;AAGH,eAAsB,iBAA0C;CAC9D,OAAO;EACL,SAAS;EACT,SAAS,oBAAoB;EAC7B,QAAQ,MAAM,mBAAmB;EAClC"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InterfereContext, InterfereContextValue } from "./react-context.mjs";
|
|
2
|
+
import * as _$react from "react";
|
|
3
|
+
import { Component, ContextType, ReactNode } from "react";
|
|
2
4
|
|
|
3
5
|
//#region src/internal/capture-boundary.d.ts
|
|
4
6
|
interface CaptureBoundaryProps {
|
|
@@ -31,6 +33,8 @@ interface CaptureBoundaryState {
|
|
|
31
33
|
* captured the error but did not render a fallback — propagation continues.
|
|
32
34
|
*/
|
|
33
35
|
declare class CaptureBoundary extends Component<CaptureBoundaryProps, CaptureBoundaryState> {
|
|
36
|
+
static contextType: _$react.Context<InterfereContextValue | null>;
|
|
37
|
+
context: ContextType<typeof InterfereContext>;
|
|
34
38
|
state: CaptureBoundaryState;
|
|
35
39
|
static getDerivedStateFromError(error: Error): CaptureBoundaryState;
|
|
36
40
|
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,SAAA;AAAA;AAAA,UAGF,oBAAA;EACR,KAAA,EAAO,KAAA;AAAA;;;AAJY;;;;;AA8BrB;;;;;;;;;;;;;;;;cAAa,eAAA,SAAwB,SAAA,CACnC,oBAAA,EACA,oBAAA;EAAA,OAEgB,WAAA,EAAW,OAAA,CAAA,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,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { captureReactError } from "./capture.mjs";
|
|
3
|
+
import { InterfereContext } from "./react-context.mjs";
|
|
3
4
|
import { MECHANISM_TYPE } from "@interfere/types/sdk/errors";
|
|
4
5
|
import { Component } from "react";
|
|
5
6
|
//#region src/internal/capture-boundary.tsx
|
|
@@ -27,16 +28,19 @@ import { Component } from "react";
|
|
|
27
28
|
* captured the error but did not render a fallback — propagation continues.
|
|
28
29
|
*/
|
|
29
30
|
var CaptureBoundary = class extends Component {
|
|
31
|
+
static contextType = InterfereContext;
|
|
30
32
|
state = { error: null };
|
|
31
33
|
static getDerivedStateFromError(error) {
|
|
32
|
-
captureReactError(error, null, {
|
|
33
|
-
type: MECHANISM_TYPE.react.captureBoundary,
|
|
34
|
-
handled: false
|
|
35
|
-
});
|
|
36
34
|
return { error };
|
|
37
35
|
}
|
|
38
36
|
render() {
|
|
39
|
-
if (this.state.error)
|
|
37
|
+
if (this.state.error) {
|
|
38
|
+
captureReactError(this.context?.kernel ?? null, this.state.error, null, {
|
|
39
|
+
type: MECHANISM_TYPE.react.captureBoundary,
|
|
40
|
+
handled: false
|
|
41
|
+
});
|
|
42
|
+
throw this.state.error;
|
|
43
|
+
}
|
|
40
44
|
return this.props.children;
|
|
41
45
|
}
|
|
42
46
|
};
|
|
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,IAAa,kBAAb,cAAqC,UAGnC;CACA,OAAgB,cAAc;CAG9B,QAAuC,EAAE,OAAO,MAAM;CAEtD,OAAO,yBAAyB,OAAoC;EAClE,OAAO,EAAE,OAAO;;CAGlB,SAA6B;EAC3B,IAAI,KAAK,MAAM,OAAO;GAMpB,kBAAkB,KAAK,SAAS,UAAU,MAAM,KAAK,MAAM,OAAO,MAAM;IACtE,MAAM,eAAe,MAAM;IAC3B,SAAS;IACV,CAAC;GACF,MAAM,KAAK,MAAM;;EAEnB,OAAO,KAAK,MAAM"}
|
|
@@ -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,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getClient } from "./client.mjs";
|
|
3
|
-
import { parseReactComponentStack, shouldDropBrowserExtensionNoise, toExceptions } from "@interfere/types/sdk/errors";
|
|
1
|
+
import { parseReactComponentStack } from "@interfere/types/sdk/errors";
|
|
4
2
|
//#region src/internal/capture.ts
|
|
5
3
|
/**
|
|
6
|
-
*
|
|
7
|
-
* reported by React as additional frames.
|
|
4
|
+
* Routes a React error through `kernel.recordException()`, attaching the
|
|
5
|
+
* component stack reported by React as additional frames. Dedup of the
|
|
6
|
+
* same `Error` instance caught by both the boundary and `window.onerror`
|
|
7
|
+
* happens inside `recordException`.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* `error.digest` is React's identifier for an RSC server-side throw that
|
|
10
|
+
* was sanitised and rebuilt on the client — promote it so enrichment can
|
|
11
|
+
* fingerprint the redacted client capture into the same problem as the
|
|
12
|
+
* unredacted server capture.
|
|
13
|
+
*
|
|
14
|
+
* Accepts a nullable kernel — error boundaries can mount outside the
|
|
15
|
+
* `<InterfereProvider>` tree, in which case we silently skip capture and
|
|
16
|
+
* let the host render its fallback / call its `onError` callback.
|
|
11
17
|
*/
|
|
12
|
-
function captureReactError(error, componentStack, mechanism) {
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
getClient().capture("error", { exceptions });
|
|
20
|
-
} catch {}
|
|
18
|
+
function captureReactError(kernel, error, componentStack, mechanism) {
|
|
19
|
+
if (!kernel) return;
|
|
20
|
+
kernel.recordException(error, {
|
|
21
|
+
mechanism,
|
|
22
|
+
...componentStack ? { appendFrames: parseReactComponentStack(componentStack) } : {},
|
|
23
|
+
...error.digest ? { errorDigest: error.digest } : {}
|
|
24
|
+
});
|
|
21
25
|
}
|
|
22
26
|
//#endregion
|
|
23
27
|
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":";;;;;;;;;;;;;;;;;AAoBA,SAAgB,kBACd,QACA,OACA,gBACA,WACM;CACN,IAAI,CAAC,QACH;CAEF,OAAO,gBAAgB,OAAO;EAC5B;EACA,GAAI,iBACA,EAAE,cAAc,yBAAyB,eAAe,EAAE,GAC1D,EAAE;EACN,GAAI,MAAM,SAAS,EAAE,aAAa,MAAM,QAAQ,GAAG,EAAE;EACtD,CAAC"}
|
|
@@ -1,10 +1,26 @@
|
|
|
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
|
+
}
|
|
4
9
|
declare function resolveTargets(): {
|
|
10
|
+
/**
|
|
11
|
+
* Base URL for OTLP exports — the trace/metric exporters append the
|
|
12
|
+
* sink path themselves. Avoids the kernel having to regex-strip
|
|
13
|
+
* `/v2/sink` off the configured URL to derive a base.
|
|
14
|
+
*/
|
|
15
|
+
collectorBaseUrl: string;
|
|
5
16
|
config: IngestTarget;
|
|
6
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Identity headers shared across every collector-bound request
|
|
19
|
+
* (OTLP exporters, replay upload, session sync). One source of
|
|
20
|
+
* truth for auth + force-enable.
|
|
21
|
+
*/
|
|
22
|
+
ingest: AuthHeaders;
|
|
7
23
|
session: IngestTarget;
|
|
8
24
|
};
|
|
9
25
|
//#endregion
|
|
10
|
-
export { resolveTargets };
|
|
26
|
+
export { AuthHeaders, IngestTarget, resolveTargets };
|
|
@@ -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":";UAKiB,YAAA;EACf,OAAA,EAAS,OAAA;EACT,GAAA;AAAA;AAAA,UAGe,WAAA;EACf,OAAA,EAAS,OAAA;AAAA;AAAA,iBAsBK,cAAA,CAAA;EA1BX;;AAGL;;;EA6BE,gBAAA;EACA,MAAA,EAAQ,YAAA;EAPM;;;;;EAad,MAAA,EAAQ,WAAA;EACR,OAAA,EAAS,YAAA;AAAA"}
|
package/dist/internal/config.mjs
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
+
import { getGlobal } from "../util/global.mjs";
|
|
1
2
|
import { API_PATHS, API_URL } from "@interfere/constants/api";
|
|
3
|
+
import { resolveRoutePrefix } from "@interfere/constants/route-prefix";
|
|
2
4
|
//#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
5
|
function resolvePublicKey() {
|
|
7
|
-
const injected =
|
|
6
|
+
const injected = getGlobal("__INTERFERE_PUBLIC_KEY__");
|
|
8
7
|
if (injected) return injected;
|
|
9
8
|
if (typeof process !== "undefined") return process.env["INTERFERE_PUBLIC_KEY"] ?? void 0;
|
|
10
9
|
}
|
|
10
|
+
function isForceEnableActive() {
|
|
11
|
+
return !!getGlobal("__INTERFERE_FORCE_ENABLE__");
|
|
12
|
+
}
|
|
11
13
|
function resolveTargets() {
|
|
12
14
|
const publicKey = resolvePublicKey();
|
|
13
15
|
const headers = new Headers({ "content-type": "application/json" });
|
|
14
16
|
if (publicKey) headers.set("x-interfere-pub-token", publicKey);
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
+
if (isForceEnableActive()) headers.set("x-interfere-force-enable", "1");
|
|
18
|
+
const baseUrl = publicKey ? API_URL : resolveRoutePrefix();
|
|
17
19
|
return {
|
|
20
|
+
collectorBaseUrl: baseUrl,
|
|
18
21
|
config: {
|
|
19
|
-
url: `${baseUrl}${API_PATHS.CONFIG
|
|
20
|
-
headers
|
|
21
|
-
},
|
|
22
|
-
ingest: {
|
|
23
|
-
url: `${baseUrl}${API_PATHS.INGEST}`,
|
|
22
|
+
url: `${baseUrl}${API_PATHS.CONFIG}`,
|
|
24
23
|
headers
|
|
25
24
|
},
|
|
25
|
+
ingest: { headers },
|
|
26
26
|
session: {
|
|
27
|
-
url: `${baseUrl}${
|
|
27
|
+
url: `${baseUrl}${API_PATHS.SESSION}`,
|
|
28
28
|
headers
|
|
29
29
|
}
|
|
30
30
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.mjs","names":[],"sources":["../../src/internal/config.ts"],"sourcesContent":["import { API_PATHS, API_URL } from \"@interfere/constants/api\";\
|
|
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\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 isForceEnableActive(): boolean {\n return !!getGlobal<boolean>(\"__INTERFERE_FORCE_ENABLE__\");\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 if (publicKey) {\n headers.set(\"x-interfere-pub-token\", publicKey);\n }\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 = publicKey ? API_URL : resolveRoutePrefix();\n\n return {\n collectorBaseUrl: baseUrl,\n config: {\n url: `${baseUrl}${API_PATHS.CONFIG}`,\n headers,\n },\n ingest: { headers },\n session: {\n url: `${baseUrl}${API_PATHS.SESSION}`,\n headers,\n },\n };\n}\n"],"mappings":";;;;AAcA,SAAS,mBAAuC;CAE9C,MAAM,WAAW,UAAkB,2BAA2B;CAC9D,IAAI,UACF,OAAO;CAIT,IAAI,OAAO,YAAY,aACrB,OAAO,QAAQ,IAAI,2BAA2B,KAAA;;AAMlD,SAAS,sBAA+B;CACtC,OAAO,CAAC,CAAC,UAAmB,6BAA6B;;AAG3D,SAAgB,iBAed;CACA,MAAM,YAAY,kBAAkB;CACpC,MAAM,UAAU,IAAI,QAAQ,EAAE,gBAAgB,oBAAoB,CAAC;CACnE,IAAI,WACF,QAAQ,IAAI,yBAAyB,UAAU;CAMjD,IAAI,qBAAqB,EACvB,QAAQ,IAAI,4BAA4B,IAAI;CAG9C,MAAM,UAAU,YAAY,UAAU,oBAAoB;CAE1D,OAAO;EACL,kBAAkB;EAClB,QAAQ;GACN,KAAK,GAAG,UAAU,UAAU;GAC5B;GACD;EACD,QAAQ,EAAE,SAAS;EACnB,SAAS;GACP,KAAK,GAAG,UAAU,UAAU;GAC5B;GACD;EACF"}
|
|
@@ -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,eAAA;AAAA,iBAI1C,gBAAA,CACd,QAAA,EAAU,eAAA,EACV,YAAA,EAAc,YAAA;AAAA,iBASA,kBAAA,CACd,IAAA,EAAM,SAAA,EACN,YAAA,EAAc,YAAA;AAAA,iBAKA,qBAAA,CAAsB,OAAA,GAAU,YAAA,GAAe,YAAA;AAAA,iBAI/C,iBAAA,CACd,OAAA,EAAS,YAAA,SACT,IAAA,EAAM,YAAA"}
|
|
@@ -6,6 +6,7 @@ const DEFAULT_CONSENT = {
|
|
|
6
6
|
};
|
|
7
7
|
const PLUGIN_CONSENT_BY_KEY = Object.fromEntries(PLUGIN_MANIFEST.map((plugin) => [plugin.name, plugin.consentCategory]));
|
|
8
8
|
const EVENT_CONSENT_BY_TYPE = Object.fromEntries(PLUGIN_MANIFEST.flatMap((plugin) => plugin.events.map((event) => [event.name, plugin.consentCategory])));
|
|
9
|
+
const GATEABLE_CATEGORIES = Object.keys(DEFAULT_CONSENT);
|
|
9
10
|
function getPluginConsentCategory(key) {
|
|
10
11
|
return PLUGIN_CONSENT_BY_KEY[key];
|
|
11
12
|
}
|
|
@@ -19,7 +20,8 @@ function resolveGrantedConsent(consent) {
|
|
|
19
20
|
return consent ?? { ...DEFAULT_CONSENT };
|
|
20
21
|
}
|
|
21
22
|
function hasConsentChanged(current, next) {
|
|
22
|
-
|
|
23
|
+
for (const category of GATEABLE_CATEGORIES) if (current?.[category] !== next?.[category]) return true;
|
|
24
|
+
return false;
|
|
23
25
|
}
|
|
24
26
|
//#endregion
|
|
25
27
|
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":";;AASA,MAAa,kBAAkB;CAC7B,WAAW;CACX,QAAQ;CACT;AAED,MAAM,wBAAwB,OAAO,YACnC,gBAAgB,KAAK,WAAW,CAAC,OAAO,MAAM,OAAO,gBAAgB,CAAC,CACvE;AAED,MAAM,wBAAwB,OAAO,YACnC,gBAAgB,SAAS,WACvB,OAAO,OAAO,KAAK,UAAU,CAAC,MAAM,MAAM,OAAO,gBAAgB,CAAU,CAC5E,CACF;AAED,MAAM,sBAAsB,OAAO,KAAK,gBAAgB;AAExD,SAAgB,yBAAyB,KAAiC;CACxE,OAAO,sBAAsB;;AAG/B,SAAgB,iBACd,UACA,cACS;CACT,OACE,aAAa,eACb,iBAAiB,QACjB,aAAa,cAAc;;AAI/B,SAAgB,mBACd,MACA,cACS;CACT,OAAO,iBAAiB,sBAAsB,OAAO,aAAa;;AAGpE,SAAgB,sBAAsB,SAAsC;CAC1E,OAAO,WAAW,EAAE,GAAG,iBAAiB;;AAG1C,SAAgB,kBACd,SACA,MACS;CACT,KAAK,MAAM,YAAY,qBACrB,IAAI,UAAU,cAAc,OAAO,WACjC,OAAO;CAGX,OAAO"}
|