@belvador/core 0.1.0 → 0.3.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/dist/aborts.d.ts +12 -0
- package/dist/aborts.js +74 -0
- package/dist/constants.d.ts +15 -0
- package/dist/constants.js +18 -0
- package/dist/diagnostics.d.ts +60 -0
- package/dist/diagnostics.js +139 -0
- package/dist/facade.d.ts +3 -0
- package/dist/facade.js +9 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +9 -2
- package/dist/monitor.d.ts +89 -0
- package/dist/monitor.js +330 -0
- package/dist/options.d.ts +26 -0
- package/dist/options.js +57 -8
- package/dist/privacy.d.ts +1 -1
- package/dist/privacy.js +25 -5
- package/dist/trace.d.ts +32 -0
- package/dist/trace.js +184 -0
- package/dist/transport.d.ts +9 -0
- package/dist/transport.js +106 -3
- package/dist/verification.d.ts +51 -0
- package/dist/verification.js +87 -0
- package/dist/verify.d.ts +47 -0
- package/dist/verify.js +180 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/verify.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// verifyIntegration (build integration-health-1.0 spec 02 §3): proves that one
|
|
2
|
+
// runtime of this app reaches the right Belvador project. Only runs when the app
|
|
3
|
+
// calls it; ≤ 2 events and ≤ 10 read-backs per call; always resolves, never throws.
|
|
4
|
+
import { captureException as engineCaptureException, flush as engineFlush, getClient } from "@sentry/core";
|
|
5
|
+
import { VERIFY_DOCS_URL, diagnosticDocsUrl, reportDiagnostic, } from "./diagnostics.js";
|
|
6
|
+
import { VERIFY_LIMITS } from "./constants.js";
|
|
7
|
+
import { checkDsn } from "./dsn.js";
|
|
8
|
+
import { getDiagnostics, isActive } from "./facade.js";
|
|
9
|
+
import { BelvadorVerificationError, newVerificationId, verificationOutcome, } from "./verification.js";
|
|
10
|
+
export { VERIFY_LIMITS };
|
|
11
|
+
const running = new Map();
|
|
12
|
+
function clampTimeout(ms) {
|
|
13
|
+
return typeof ms === "number" && Number.isFinite(ms)
|
|
14
|
+
? Math.min(VERIFY_LIMITS.timeoutMaxMs, Math.max(1000, ms))
|
|
15
|
+
: VERIFY_LIMITS.timeoutDefaultMs;
|
|
16
|
+
}
|
|
17
|
+
function line(r) {
|
|
18
|
+
const parts = r.checks
|
|
19
|
+
.filter((c) => c.check !== "init" || c.status !== "confirmed")
|
|
20
|
+
.map((c) => `${c.check} ${c.status}${c.code ? ` (${c.code})` : ""}`);
|
|
21
|
+
return `[belvador] verify ${r.surface}: ${r.outcome} — ${parts.join(", ")} — ${r.verificationId} — ${r.docsUrl}`;
|
|
22
|
+
}
|
|
23
|
+
function print(r) {
|
|
24
|
+
try {
|
|
25
|
+
(r.outcome === "verified" ? console.info : console.warn)(line(r));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
/* no console */
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Runs the verification for one runtime. Concurrent calls share one run. */
|
|
32
|
+
export function runVerification(runtime, options = {}) {
|
|
33
|
+
const current = running.get(runtime.surface);
|
|
34
|
+
if (current)
|
|
35
|
+
return current;
|
|
36
|
+
const p = execute(runtime, options)
|
|
37
|
+
.catch(() => fallback(runtime.surface))
|
|
38
|
+
.then((r) => {
|
|
39
|
+
print(r);
|
|
40
|
+
return r;
|
|
41
|
+
})
|
|
42
|
+
.finally(() => running.delete(runtime.surface));
|
|
43
|
+
running.set(runtime.surface, p);
|
|
44
|
+
return p;
|
|
45
|
+
}
|
|
46
|
+
function fallback(surface) {
|
|
47
|
+
return {
|
|
48
|
+
verificationId: "",
|
|
49
|
+
surface,
|
|
50
|
+
sdk: getDiagnostics().sdk,
|
|
51
|
+
project: null,
|
|
52
|
+
checks: [{ check: "init", status: "failed", code: "BLV_NOT_INITIALIZED" }],
|
|
53
|
+
outcome: "problem",
|
|
54
|
+
docsUrl: VERIFY_DOCS_URL,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function execute(runtime, options) {
|
|
58
|
+
const now = runtime.now ?? Date.now;
|
|
59
|
+
const started = now();
|
|
60
|
+
const deadline = started + clampTimeout(options.timeoutMs);
|
|
61
|
+
const sleep = runtime.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
62
|
+
const id = newVerificationId();
|
|
63
|
+
const snapshot = getDiagnostics();
|
|
64
|
+
const dsnValue = getClient()?.getOptions()?.dsn;
|
|
65
|
+
const dsn = checkDsn(dsnValue);
|
|
66
|
+
const result = {
|
|
67
|
+
verificationId: id,
|
|
68
|
+
surface: runtime.surface,
|
|
69
|
+
sdk: snapshot.sdk,
|
|
70
|
+
project: dsn?.projectId ?? snapshot.dsn?.project ?? null,
|
|
71
|
+
checks: [],
|
|
72
|
+
outcome: "problem",
|
|
73
|
+
docsUrl: VERIFY_DOCS_URL,
|
|
74
|
+
};
|
|
75
|
+
// 1. init
|
|
76
|
+
if (!isActive() || !dsn) {
|
|
77
|
+
const code = ["BLV_DSN_MISSING", "BLV_DSN_INVALID", "BLV_INIT_CONFLICT"].find((c) => (snapshot.counts[c] ?? 0) > 0);
|
|
78
|
+
result.checks.push({ check: "init", status: "failed", code: code ?? "BLV_NOT_INITIALIZED" });
|
|
79
|
+
result.docsUrl = diagnosticDocsUrl(code ?? "BLV_NOT_INITIALIZED");
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
result.checks.push({ check: "init", status: "confirmed" });
|
|
83
|
+
// 2. hook
|
|
84
|
+
const hookOk = runtime.hook().ok;
|
|
85
|
+
if (!hookOk)
|
|
86
|
+
reportDiagnostic("BLV_HOOK_MISSING");
|
|
87
|
+
result.checks.push(hookOk ? { check: "hook", status: "confirmed" } : { check: "hook", status: "missing", code: "BLV_HOOK_MISSING" });
|
|
88
|
+
// 3. delivery (+ 4. uncaught in the browser)
|
|
89
|
+
const wantUncaught = runtime.surface === "browser" && options.uncaught !== false;
|
|
90
|
+
engineCaptureException(new BelvadorVerificationError(id, {
|
|
91
|
+
v: 1,
|
|
92
|
+
surface: runtime.surface,
|
|
93
|
+
check: "delivery",
|
|
94
|
+
...(runtime.surface === "server"
|
|
95
|
+
? { hook: hookOk ? "ok" : "missing" }
|
|
96
|
+
: { uncaught_expected: wantUncaught }),
|
|
97
|
+
}), { level: "info" });
|
|
98
|
+
if (wantUncaught) {
|
|
99
|
+
// The page's own timer, so the throw reaches window.onerror like any app error.
|
|
100
|
+
const w = globalThis.window;
|
|
101
|
+
const schedule = w?.setTimeout ? w.setTimeout.bind(w) : setTimeout;
|
|
102
|
+
schedule(() => {
|
|
103
|
+
throw new BelvadorVerificationError(id, { v: 1, surface: "browser", check: "uncaught" });
|
|
104
|
+
}, 0);
|
|
105
|
+
// Let the global handler see it before flushing.
|
|
106
|
+
await sleep(50);
|
|
107
|
+
}
|
|
108
|
+
await engineFlush(Math.max(1000, Math.min(5000, deadline - now()))).catch(() => false);
|
|
109
|
+
const local = (check) => {
|
|
110
|
+
const o = verificationOutcome(id, check);
|
|
111
|
+
if (!o)
|
|
112
|
+
return { status: "failed", code: "BLV_DROPPED_BY_APP" };
|
|
113
|
+
if (o.kind === "dropped")
|
|
114
|
+
return { status: "failed", code: "BLV_DROPPED_BY_APP" };
|
|
115
|
+
if (o.kind === "failed")
|
|
116
|
+
return { status: "failed", code: o.code };
|
|
117
|
+
return { status: "accepted" };
|
|
118
|
+
};
|
|
119
|
+
const delivery = local("delivery");
|
|
120
|
+
const uncaught = wantUncaught ? local("uncaught") : { status: "skipped" };
|
|
121
|
+
// 5. read back
|
|
122
|
+
let readback = null;
|
|
123
|
+
const sendsOk = delivery.status === "accepted";
|
|
124
|
+
if (options.readback !== false && sendsOk) {
|
|
125
|
+
const doFetch = runtime.fetch ?? ((u, i) => globalThis.fetch(u, i));
|
|
126
|
+
const url = `${dsn.protocol}//${dsn.host}/api/${dsn.projectId}/verification/${id}/?sentry_key=${dsn.publicKey}`;
|
|
127
|
+
for (let i = 0; i < VERIFY_LIMITS.readbackMaxRequests; i++) {
|
|
128
|
+
const wait = VERIFY_LIMITS.readbackDelaysMs[Math.min(i, VERIFY_LIMITS.readbackDelaysMs.length - 1)];
|
|
129
|
+
if (now() + wait > deadline)
|
|
130
|
+
break;
|
|
131
|
+
await sleep(wait);
|
|
132
|
+
try {
|
|
133
|
+
const res = await doFetch(url, { method: "GET", credentials: "omit" });
|
|
134
|
+
if (res.status === 200) {
|
|
135
|
+
const body = (await res.json());
|
|
136
|
+
readback = { node: body.node === "offline" ? "offline" : "connected", checks: Array.isArray(body.checks) ? body.checks : [] };
|
|
137
|
+
const has = (c) => readback.checks.some((x) => x.surface === runtime.surface && x.check === c);
|
|
138
|
+
if (has("delivery") && (!wantUncaught || has("uncaught") || uncaught.status !== "accepted"))
|
|
139
|
+
break;
|
|
140
|
+
if (readback.node === "offline")
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
else if (res.status === 429 || res.status === 401 || res.status === 403) {
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
/* retry within the deadline */
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const confirmed = (check) => readback?.checks.some((x) => x.surface === runtime.surface && x.check === check) ?? false;
|
|
153
|
+
const finalize = (check, s) => {
|
|
154
|
+
if (s.status !== "accepted")
|
|
155
|
+
return s;
|
|
156
|
+
if (confirmed(check))
|
|
157
|
+
return { status: "confirmed" };
|
|
158
|
+
// Read back worked and the node is up, yet nothing arrived: the event is missing.
|
|
159
|
+
if (readback && readback.node === "connected")
|
|
160
|
+
return { status: "missing" };
|
|
161
|
+
return s;
|
|
162
|
+
};
|
|
163
|
+
const d = finalize("delivery", delivery);
|
|
164
|
+
result.checks.push({ check: "delivery", ...d });
|
|
165
|
+
if (runtime.surface === "browser")
|
|
166
|
+
result.checks.push({ check: "uncaught", ...finalize("uncaught", uncaught) });
|
|
167
|
+
if (readback)
|
|
168
|
+
result.node = readback.node;
|
|
169
|
+
const all = result.checks.filter((c) => c.status !== "skipped");
|
|
170
|
+
if (all.some((c) => c.status === "failed" || c.status === "missing"))
|
|
171
|
+
result.outcome = "problem";
|
|
172
|
+
else if (all.every((c) => c.status === "confirmed") && !result.checks.some((c) => c.status === "skipped"))
|
|
173
|
+
result.outcome = "verified";
|
|
174
|
+
else
|
|
175
|
+
result.outcome = "unconfirmed";
|
|
176
|
+
const first = result.checks.find((c) => c.code);
|
|
177
|
+
if (first?.code)
|
|
178
|
+
result.docsUrl = diagnosticDocsUrl(first.code);
|
|
179
|
+
return result;
|
|
180
|
+
}
|
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.3.0";
|
|
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.3.0",
|
|
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)",
|