@belvador/core 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/options.d.ts +8 -0
- package/dist/options.js +8 -1
- package/dist/otel.d.ts +31 -0
- package/dist/otel.js +44 -0
- package/dist/privacy.js +42 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/diagnostics.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const DIAGNOSTIC_CODES: readonly ["BLV_DSN_MISSING", "BLV_DSN_INVALID", "BLV_INIT_CONFLICT", "BLV_NOT_INITIALIZED", "BLV_SEND_UNAUTHORIZED", "BLV_SEND_RATE_LIMITED", "BLV_SEND_REJECTED", "BLV_SEND_NETWORK", "BLV_SEND_SERVER", "BLV_QUEUE_FULL", "BLV_EVENT_TOO_LARGE", "BLV_HOOK_MISSING", "BLV_DROPPED_BY_APP", "BLV_MONITOR_INVALID_SLUG", "BLV_MONITOR_INVALID_REASON", "BLV_MONITOR_CONFIG_INVALID", "BLV_CHECK_IN_DROPPED", "BLV_TRACE_TARGET_INVALID"];
|
|
1
|
+
export declare const DIAGNOSTIC_CODES: readonly ["BLV_DSN_MISSING", "BLV_DSN_INVALID", "BLV_INIT_CONFLICT", "BLV_NOT_INITIALIZED", "BLV_SEND_UNAUTHORIZED", "BLV_SEND_RATE_LIMITED", "BLV_SEND_REJECTED", "BLV_SEND_NETWORK", "BLV_SEND_SERVER", "BLV_QUEUE_FULL", "BLV_EVENT_TOO_LARGE", "BLV_HOOK_MISSING", "BLV_DROPPED_BY_APP", "BLV_MONITOR_INVALID_SLUG", "BLV_MONITOR_INVALID_REASON", "BLV_MONITOR_CONFIG_INVALID", "BLV_CHECK_IN_DROPPED", "BLV_TRACE_TARGET_INVALID", "BLV_OTEL_INVALID", "BLV_TRACE_OWNER_OTEL"];
|
|
2
2
|
export type DiagnosticCode = (typeof DIAGNOSTIC_CODES)[number];
|
|
3
3
|
/** Public page explaining every code and the verification (one anchor per code). */
|
|
4
4
|
export declare const VERIFY_DOCS_URL = "https://belvador.dev/en/docs/sdk/verify";
|
package/dist/diagnostics.js
CHANGED
|
@@ -21,6 +21,8 @@ export const DIAGNOSTIC_CODES = [
|
|
|
21
21
|
"BLV_MONITOR_CONFIG_INVALID",
|
|
22
22
|
"BLV_CHECK_IN_DROPPED",
|
|
23
23
|
"BLV_TRACE_TARGET_INVALID",
|
|
24
|
+
"BLV_OTEL_INVALID",
|
|
25
|
+
"BLV_TRACE_OWNER_OTEL",
|
|
24
26
|
];
|
|
25
27
|
/** Public page explaining every code and the verification (one anchor per code). */
|
|
26
28
|
export const VERIFY_DOCS_URL = "https://belvador.dev/en/docs/sdk/verify";
|
|
@@ -45,6 +47,8 @@ const MESSAGES = {
|
|
|
45
47
|
BLV_MONITOR_CONFIG_INVALID: "a monitor schedule or setting is not valid — the check-in was sent without it",
|
|
46
48
|
BLV_CHECK_IN_DROPPED: "a check-in was dropped (too many waiting, rate limited or not sent)",
|
|
47
49
|
BLV_TRACE_TARGET_INVALID: "a tracePropagation target is not a string or RegExp — it was ignored",
|
|
50
|
+
BLV_OTEL_INVALID: "otel.api is not @opentelemetry/api — ignored",
|
|
51
|
+
BLV_TRACE_OWNER_OTEL: "otel is set, so tracePropagation is ignored",
|
|
48
52
|
};
|
|
49
53
|
const config = {
|
|
50
54
|
onDiagnostic: undefined,
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { SDK_VERSION, ENGINE_VERSION } from "./version.js";
|
|
|
2
2
|
export { checkDsn, type BelvadorDsn } from "./dsn.js";
|
|
3
3
|
export { isAbortError, isClientAbort, isViewTransitionSkip, noteClientAbort } from "./aborts.js";
|
|
4
4
|
export { INTERVAL_UNITS, MONITOR_CONFIG_LIMITS, MONITOR_CONTEXT, MONITOR_SLUG_RE, RUN_CONTEXT, RUN_REASON_RE, buildCheckIn, captureCheckIn, heartbeat, monitorConfigOf, runIdOf, withMonitor, type CheckInInput, type IntervalUnit, type MonitorConfigInput, type MonitorSchedule, type RunHandle, type WithMonitorOptions, } from "./monitor.js";
|
|
5
|
+
export { OTEL_OPTION, otelApiOf, otelBridgeIntegration, otelTraceContext, type OtelApiLike, type OtelOptions, } from "./otel.js";
|
|
5
6
|
export { TRACE_OPTION, installTracePropagation, sentryTraceFromTraceparent, traceConfigOf, urlMatches, withIncomingTraceparent, type TraceConfig, type TracePropagationOptions, } from "./trace.js";
|
|
6
7
|
export { FILTERED, SENSITIVE_KEY_PARTS, isSensitiveKey, scrubBreadcrumb, scrubEvent, scrubParams, scrubText, scrubValue, } from "./privacy.js";
|
|
7
8
|
export { MAX_QUEUED_CHECK_INS, SEND_LIMITS, SENT_ITEM_TYPES, filterEnvelope, makeBelvadorTransport, sendWithRetry, type FetchLike, type SendPolicy, type SendState, } from "./transport.js";
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export { SDK_VERSION, ENGINE_VERSION } from "./version.js";
|
|
|
5
5
|
export { checkDsn } from "./dsn.js";
|
|
6
6
|
export { isAbortError, isClientAbort, isViewTransitionSkip, noteClientAbort } from "./aborts.js";
|
|
7
7
|
export { INTERVAL_UNITS, MONITOR_CONFIG_LIMITS, MONITOR_CONTEXT, MONITOR_SLUG_RE, RUN_CONTEXT, RUN_REASON_RE, buildCheckIn, captureCheckIn, heartbeat, monitorConfigOf, runIdOf, withMonitor, } from "./monitor.js";
|
|
8
|
+
export { OTEL_OPTION, otelApiOf, otelBridgeIntegration, otelTraceContext, } from "./otel.js";
|
|
8
9
|
export { TRACE_OPTION, installTracePropagation, sentryTraceFromTraceparent, traceConfigOf, urlMatches, withIncomingTraceparent, } from "./trace.js";
|
|
9
10
|
export { FILTERED, SENSITIVE_KEY_PARTS, isSensitiveKey, scrubBreadcrumb, scrubEvent, scrubParams, scrubText, scrubValue, } from "./privacy.js";
|
|
10
11
|
export { MAX_QUEUED_CHECK_INS, SEND_LIMITS, SENT_ITEM_TYPES, filterEnvelope, makeBelvadorTransport, sendWithRetry, } from "./transport.js";
|
package/dist/options.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Breadcrumb, BreadcrumbHint, ErrorEvent, EventHint } from "@sentry/core";
|
|
2
2
|
import { type Diagnostic } from "./diagnostics.js";
|
|
3
|
+
import { type OtelOptions } from "./otel.js";
|
|
3
4
|
import { type TracePropagationOptions } from "./trace.js";
|
|
4
5
|
import { type SendPolicy } from "./transport.js";
|
|
5
6
|
export type BelvadorEvent = ErrorEvent;
|
|
@@ -39,6 +40,12 @@ export type BelvadorOptions = {
|
|
|
39
40
|
* (the receiving API must allow them in CORS). No spans or transactions are sent.
|
|
40
41
|
*/
|
|
41
42
|
tracePropagation?: TracePropagationOptions;
|
|
43
|
+
/**
|
|
44
|
+
* The app already runs OpenTelemetry: pass `{ api }` from `@opentelemetry/api` and errors
|
|
45
|
+
* captured inside an active span carry that span's trace and span ids. OpenTelemetry then
|
|
46
|
+
* owns propagation, so `tracePropagation` is ignored. Belvador creates no spans.
|
|
47
|
+
*/
|
|
48
|
+
otel?: OtelOptions;
|
|
42
49
|
/** Send attachments added with scope.addAttachment (each ≤ 5 MB). Default false. */
|
|
43
50
|
attachments?: boolean;
|
|
44
51
|
/** Runs before Belvador's own scrubbing; return null to drop the event. */
|
|
@@ -86,6 +93,7 @@ export declare function buildEngineOptions(opts: BelvadorOptions, runtime: Runti
|
|
|
86
93
|
transport: (options: import("@sentry/core").BaseTransportOptions) => import("@sentry/core").Transport;
|
|
87
94
|
_belvador: boolean;
|
|
88
95
|
_belvadorTrace: import("./trace.js").TraceConfig | null;
|
|
96
|
+
_belvadorOtel: import("./otel.js").OtelApiLike | null;
|
|
89
97
|
_metadata: {
|
|
90
98
|
sdk: {
|
|
91
99
|
name: string;
|
package/dist/options.js
CHANGED
|
@@ -3,6 +3,7 @@ import { configureDiagnostics, reportDiagnostic } from "./diagnostics.js";
|
|
|
3
3
|
import { checkDsn } from "./dsn.js";
|
|
4
4
|
import { scrubBreadcrumb, scrubEvent } from "./privacy.js";
|
|
5
5
|
import { VERIFICATION_CONTEXT, VERIFICATION_TAG, noteVerificationOutcome, verificationOf, } from "./verification.js";
|
|
6
|
+
import { OTEL_OPTION, otelApiOf } from "./otel.js";
|
|
6
7
|
import { TRACE_OPTION, traceConfigOf } from "./trace.js";
|
|
7
8
|
import { makeBelvadorTransport } from "./transport.js";
|
|
8
9
|
import { ENGINE_VERSION, SDK_VERSION } from "./version.js";
|
|
@@ -116,6 +117,10 @@ export function buildEngineOptions(opts, runtime, send) {
|
|
|
116
117
|
return null;
|
|
117
118
|
}
|
|
118
119
|
const attachments = opts.attachments === true;
|
|
120
|
+
const otel = otelApiOf(opts.otel);
|
|
121
|
+
// One owner of propagation: with OpenTelemetry present, Belvador adds no headers of its own.
|
|
122
|
+
if (otel && opts.tracePropagation)
|
|
123
|
+
reportDiagnostic("BLV_TRACE_OWNER_OTEL");
|
|
119
124
|
return {
|
|
120
125
|
dsn: opts.dsn,
|
|
121
126
|
release: typeof opts.release === "string" && opts.release ? opts.release : undefined,
|
|
@@ -140,7 +145,9 @@ export function buildEngineOptions(opts, runtime, send) {
|
|
|
140
145
|
transport: makeBelvadorTransport({ ...send, attachments, debug: opts.debug === true }),
|
|
141
146
|
[BELVADOR_MARKER]: true,
|
|
142
147
|
// Checked trace propagation of this client (null = off); the engine itself never adds headers.
|
|
143
|
-
[TRACE_OPTION]: traceConfigOf(opts.tracePropagation),
|
|
148
|
+
[TRACE_OPTION]: otel ? null : traceConfigOf(opts.tracePropagation),
|
|
149
|
+
// The checked `@opentelemetry/api` of this client (null = no bridge); runtimes add the integration.
|
|
150
|
+
[OTEL_OPTION]: otel,
|
|
144
151
|
_metadata: {
|
|
145
152
|
sdk: {
|
|
146
153
|
name: sdkName,
|
package/dist/otel.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Integration } from "@sentry/core";
|
|
2
|
+
/** The part of `@opentelemetry/api` the bridge reads (structural, any 1.x version fits). */
|
|
3
|
+
export type OtelApiLike = {
|
|
4
|
+
trace: {
|
|
5
|
+
getActiveSpan(): {
|
|
6
|
+
spanContext(): {
|
|
7
|
+
traceId: string;
|
|
8
|
+
spanId: string;
|
|
9
|
+
};
|
|
10
|
+
} | undefined;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export type OtelOptions = {
|
|
14
|
+
/** `import * as api from "@opentelemetry/api"` — the same copy your OpenTelemetry SDK uses. */
|
|
15
|
+
api: OtelApiLike;
|
|
16
|
+
};
|
|
17
|
+
/** Engine-options key carrying the checked API of one client (null = bridge off). */
|
|
18
|
+
export declare const OTEL_OPTION = "_belvadorOtel";
|
|
19
|
+
/** The API when it looks like `@opentelemetry/api`, else null (a wrong value is reported). */
|
|
20
|
+
export declare function otelApiOf(opts: OtelOptions | undefined): OtelApiLike | null;
|
|
21
|
+
/** Ids of the active OpenTelemetry span, or null (none, invalid, all-zero, or the API threw). */
|
|
22
|
+
export declare function otelTraceContext(api: OtelApiLike): {
|
|
23
|
+
trace_id: string;
|
|
24
|
+
span_id: string;
|
|
25
|
+
} | null;
|
|
26
|
+
/**
|
|
27
|
+
* Stamps `contexts.trace` from the active span when the error is captured. `preprocessEvent`
|
|
28
|
+
* runs synchronously inside the capture call, while the app's span is still current; the
|
|
29
|
+
* engine keeps an event's own trace context over the scope's.
|
|
30
|
+
*/
|
|
31
|
+
export declare function otelBridgeIntegration(api: OtelApiLike): Integration;
|
package/dist/otel.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { reportDiagnostic } from "./diagnostics.js";
|
|
2
|
+
/** Engine-options key carrying the checked API of one client (null = bridge off). */
|
|
3
|
+
export const OTEL_OPTION = "_belvadorOtel";
|
|
4
|
+
/** The API when it looks like `@opentelemetry/api`, else null (a wrong value is reported). */
|
|
5
|
+
export function otelApiOf(opts) {
|
|
6
|
+
if (!opts)
|
|
7
|
+
return null;
|
|
8
|
+
const api = opts.api;
|
|
9
|
+
if (typeof api?.trace?.getActiveSpan === "function")
|
|
10
|
+
return api;
|
|
11
|
+
reportDiagnostic("BLV_OTEL_INVALID");
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
// 32 + 16 lowercase hex, neither all zeros (W3C Trace Context invalid ids).
|
|
15
|
+
const IDS = /^(?!0{32})[0-9a-f]{32}-(?!0{16})[0-9a-f]{16}$/;
|
|
16
|
+
/** Ids of the active OpenTelemetry span, or null (none, invalid, all-zero, or the API threw). */
|
|
17
|
+
export function otelTraceContext(api) {
|
|
18
|
+
try {
|
|
19
|
+
const c = api.trace.getActiveSpan()?.spanContext();
|
|
20
|
+
const ids = c ? `${c.traceId}-${c.spanId}`.toLowerCase() : "";
|
|
21
|
+
return IDS.test(ids) ? { trace_id: ids.slice(0, 32), span_id: ids.slice(33) } : null;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Stamps `contexts.trace` from the active span when the error is captured. `preprocessEvent`
|
|
29
|
+
* runs synchronously inside the capture call, while the app's span is still current; the
|
|
30
|
+
* engine keeps an event's own trace context over the scope's.
|
|
31
|
+
*/
|
|
32
|
+
export function otelBridgeIntegration(api) {
|
|
33
|
+
return {
|
|
34
|
+
name: "BelvadorOtelBridge",
|
|
35
|
+
preprocessEvent(event) {
|
|
36
|
+
const ids = !event.type && otelTraceContext(api); // errors only
|
|
37
|
+
if (!ids)
|
|
38
|
+
return;
|
|
39
|
+
const trace = { ...event.contexts?.trace, ...ids };
|
|
40
|
+
delete trace.parent_span_id; // the scope's parent is not this span's parent
|
|
41
|
+
event.contexts = { ...event.contexts, trace };
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
package/dist/privacy.js
CHANGED
|
@@ -83,7 +83,25 @@ const PROVIDER_KEY = /\b[sr]k_(?:live|test)_[A-Za-z0-9]{8,}\b/g;
|
|
|
83
83
|
const JWT = /\beyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}/g;
|
|
84
84
|
const CARD = /\b(?:\d[ -]?){12,18}\d\b/g;
|
|
85
85
|
const EMAIL = /\b[A-Za-z0-9._%+-]{1,64}@([A-Za-z0-9-]{1,63}(?:\.[A-Za-z0-9-]{1,63})*\.[A-Za-z]{2,24})\b/g;
|
|
86
|
+
/** The same address percent-encoded in a URL (`linh%40example.com`). */
|
|
87
|
+
const EMAIL_ENCODED = /\b(?:[A-Za-z0-9._+-]|%[0-9A-Fa-f]{2}){1,64}%40([A-Za-z0-9-]{1,63}(?:\.[A-Za-z0-9-]{1,63})*\.[A-Za-z]{2,24})\b/gi;
|
|
86
88
|
const USERINFO = /\b([a-z][a-z0-9+.-]{1,15}:\/\/)[^\s/?#@]+@/gi;
|
|
89
|
+
/** A dotted IPv4 in free text, not inside a longer dotted number or a word (`v1.2.3.4`). */
|
|
90
|
+
const IPV4_TEXT = /(^|[^0-9A-Za-z.])(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?![0-9A-Za-z]|\.\d)/g;
|
|
91
|
+
/**
|
|
92
|
+
* Private, CGNAT, loopback, link-local, unspecified and multicast/reserved addresses name
|
|
93
|
+
* infrastructure (`ECONNREFUSED 10.0.3.17:5432`) and stay readable; any other IPv4 may be a client.
|
|
94
|
+
*/
|
|
95
|
+
function isInfrastructureV4(a, b) {
|
|
96
|
+
return (a === 0 ||
|
|
97
|
+
a === 10 ||
|
|
98
|
+
a === 127 ||
|
|
99
|
+
a >= 224 ||
|
|
100
|
+
(a === 100 && b >= 64 && b <= 127) ||
|
|
101
|
+
(a === 169 && b === 254) ||
|
|
102
|
+
(a === 172 && b >= 16 && b <= 31) ||
|
|
103
|
+
(a === 192 && b === 168));
|
|
104
|
+
}
|
|
87
105
|
/** name=value inside URLs, query strings and log lines. */
|
|
88
106
|
const PARAM = /([A-Za-z0-9_.~%[\]-]{1,128})=([^&#\s"'<>,;]*)/g;
|
|
89
107
|
/** `Basic <base64>` credentials in headers and log lines. */
|
|
@@ -154,7 +172,9 @@ export function scrubText(text) {
|
|
|
154
172
|
.replace(PROVIDER_KEY, FILTERED)
|
|
155
173
|
.replace(JWT, FILTERED)
|
|
156
174
|
.replace(CARD, (m) => (luhn(m.replace(/[ -]/g, "")) ? FILTERED : m))
|
|
157
|
-
.replace(EMAIL, (_m, domain) => `${FILTERED}@${domain}`)
|
|
175
|
+
.replace(EMAIL, (_m, domain) => `${FILTERED}@${domain}`)
|
|
176
|
+
.replace(EMAIL_ENCODED, (_m, domain) => `${FILTERED}%40${domain}`)
|
|
177
|
+
.replace(IPV4_TEXT, (all, lead, a, b, c) => isInfrastructureV4(Number(a), Number(b)) ? all : `${lead}${a}.${b}.${c}.0`);
|
|
158
178
|
}
|
|
159
179
|
function readKey(obj, key) {
|
|
160
180
|
try {
|
|
@@ -368,6 +388,25 @@ export function scrubBreadcrumb(crumb) {
|
|
|
368
388
|
return null;
|
|
369
389
|
}
|
|
370
390
|
}
|
|
391
|
+
// W3C trace/span ids are random hex, not personal data; an all-digit span id can look like a
|
|
392
|
+
// card number, so exact-format ids under `contexts.trace` survive (parity with the node).
|
|
393
|
+
const HEX = /^[0-9a-f]+$/;
|
|
394
|
+
function scrubContexts(contexts) {
|
|
395
|
+
const out = scrubValue(contexts, 0);
|
|
396
|
+
try {
|
|
397
|
+
const t = contexts.trace;
|
|
398
|
+
if (t && out?.trace && typeof out.trace === "object")
|
|
399
|
+
for (const k of ["trace_id", "span_id", "parent_span_id"]) {
|
|
400
|
+
const id = t[k];
|
|
401
|
+
if (typeof id === "string" && id.length === (k === "trace_id" ? 32 : 16) && HEX.test(id))
|
|
402
|
+
out.trace[k] = id;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
/* a throwing getter: keep the scrubbed copy */
|
|
407
|
+
}
|
|
408
|
+
return out;
|
|
409
|
+
}
|
|
371
410
|
/**
|
|
372
411
|
* Whole-event scrub, run after the app's own `beforeSend` and before any byte
|
|
373
412
|
* leaves the app. Returns a new event, or null (drop) when scrubbing fails —
|
|
@@ -390,6 +429,8 @@ export function scrubEvent(event) {
|
|
|
390
429
|
next = scrubUser(v);
|
|
391
430
|
else if (key === "exception")
|
|
392
431
|
next = scrubException(v);
|
|
432
|
+
else if (key === "contexts")
|
|
433
|
+
next = scrubContexts(v);
|
|
393
434
|
else if (key === "breadcrumbs")
|
|
394
435
|
next = Array.isArray(v)
|
|
395
436
|
? v.flatMap((b) => {
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// One version for every @belvador/* package (lockstep). Changed only by
|
|
2
2
|
// `bun scripts/set-version.ts`; tests compare it with every package.json and
|
|
3
3
|
// with src/content/sdk-matrix.ts in the web app.
|
|
4
|
-
export const SDK_VERSION = "0.
|
|
4
|
+
export const SDK_VERSION = "0.4.1";
|
|
5
5
|
/** The audited engine version every package pins exactly (research/05-decisions.md D3). */
|
|
6
6
|
export const ENGINE_VERSION = "10.76.2";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@belvador/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Internal building blocks of the Belvador SDK (privacy, sending policy). Install @belvador/browser, @belvador/react, @belvador/cloudflare or @belvador/tanstack-start instead.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Belvador (https://belvador.dev)",
|