@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/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
@@ -1,3 +1,3 @@
1
- export declare const SDK_VERSION = "0.1.0";
1
+ export declare const SDK_VERSION = "0.3.0";
2
2
  /** The audited engine version every package pins exactly (research/05-decisions.md D3). */
3
3
  export declare const ENGINE_VERSION = "10.76.2";
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.1.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.1.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)",