@reliantlabs/forge-web-runtime 0.3.2 → 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/LICENSE +202 -0
- package/NOTICE +5 -0
- package/README.md +26 -3
- package/dist/devlog.js +6 -2
- package/dist/devlog.js.map +1 -1
- package/dist/error-boundary.d.ts +1 -1
- package/dist/error-boundary.js +9 -2
- package/dist/error-boundary.js.map +1 -1
- package/dist/error-reporter.d.ts +27 -0
- package/dist/error-reporter.js +55 -0
- package/dist/error-reporter.js.map +1 -0
- package/dist/index.d.ts +15 -2
- package/dist/index.js +15 -2
- package/dist/index.js.map +1 -1
- package/dist/mock-transport.d.ts +8 -1
- package/dist/mock-transport.js +198 -6
- package/dist/mock-transport.js.map +1 -1
- package/dist/money.d.ts +69 -0
- package/dist/money.js +335 -0
- package/dist/money.js.map +1 -0
- package/dist/resource.js +7 -2
- package/dist/resource.js.map +1 -1
- package/dist/telemetry.d.ts +61 -21
- package/dist/telemetry.js +254 -111
- package/dist/telemetry.js.map +1 -1
- package/package.json +15 -47
- package/dist/otel.d.ts +0 -31
- package/dist/otel.js +0 -94
- package/dist/otel.js.map +0 -1
package/dist/telemetry.js
CHANGED
|
@@ -1,128 +1,271 @@
|
|
|
1
|
-
|
|
1
|
+
// Part of @reliantlabs/forge-web-runtime — the web twin of forge/pkg.
|
|
2
|
+
//
|
|
3
|
+
// Browser telemetry: the HyperDX browser SDK, initialised the forge way.
|
|
4
|
+
//
|
|
5
|
+
// Captures errors (uncaught, unhandled rejections, boundary-caught), console
|
|
6
|
+
// output, fetch/XHR spans, document load, long tasks and web vitals, and
|
|
7
|
+
// ships them to ClickStack through a SAME-ORIGIN `/_otel` route.
|
|
8
|
+
//
|
|
9
|
+
// ── Why HyperDX REPLACES the old OTel web wiring, not joins it ───────────
|
|
10
|
+
//
|
|
11
|
+
// @hyperdx/browser wraps @hyperdx/otel-web, which builds its own
|
|
12
|
+
// WebTracerProvider and calls provider.register() — the global tracer
|
|
13
|
+
// provider, context manager and propagator. A second registration (the old
|
|
14
|
+
// `./otel` subpath did exactly that) is rejected by @opentelemetry/api with a
|
|
15
|
+
// "duplicate registration" diagnostic and silently loses: whichever ran
|
|
16
|
+
// second would simply never see a span. Two tracer providers cannot coexist
|
|
17
|
+
// in one page, so there is one, and it is HyperDX's. `./otel` and its eight
|
|
18
|
+
// @opentelemetry/* SDK peer dependencies are gone.
|
|
19
|
+
//
|
|
20
|
+
// traceparent on RPCs is unaffected. traceInterceptor (trace.ts) injects the
|
|
21
|
+
// active span's context into every Connect call, or synthesises a valid
|
|
22
|
+
// traceparent when none is active — with no SDK at all. With the SDK running,
|
|
23
|
+
// the fetch span HyperDX opens for the call carries the same trace id, and
|
|
24
|
+
// `apiOrigins` below makes that hold for a cross-origin API too (which is
|
|
25
|
+
// every dev loop: the API binds its own port).
|
|
26
|
+
//
|
|
27
|
+
// ── The browser holds no secret ──────────────────────────────────────────
|
|
28
|
+
//
|
|
29
|
+
// `url` is `${location.origin}/_otel`. The browser talks only to its own
|
|
30
|
+
// origin; a dev-server proxy (next.config.ts rewrites / Vite server.proxy) or
|
|
31
|
+
// the deployment's ingress forwards `/_otel/v1/{traces,logs}` to the OTLP/HTTP
|
|
32
|
+
// port of a collector, and that hop — never the bundle — owns credentials.
|
|
33
|
+
//
|
|
34
|
+
// The SDK nevertheless wants an `apiKey` and sends it as an `authorization`
|
|
35
|
+
// header. We pass PLACEHOLDER_API_KEY, a constant that grants nothing and is
|
|
36
|
+
// public by construction. A collector that enforces ingestion auth (ClickStack
|
|
37
|
+
// `collectorAuthenticationEnforced`) rejects it with 401, so the proxy in
|
|
38
|
+
// front of such a collector must strip the header or replace it with the real
|
|
39
|
+
// key. The local ClickStack collector does not enforce, and ignores it.
|
|
40
|
+
//
|
|
41
|
+
// ── Replay: shipped, off, masked ─────────────────────────────────────────
|
|
42
|
+
//
|
|
43
|
+
// The SDK bundles an rrweb recorder. It is DISABLED unless `replay: true`.
|
|
44
|
+
// When enabled, masking is forced on — every input and all text — because a
|
|
45
|
+
// replay records the DOM, which for most apps is the most sensitive data they
|
|
46
|
+
// have. There is deliberately no option to turn masking off while replay is
|
|
47
|
+
// on; an app that needs unmasked replay passes its own SDK options.
|
|
48
|
+
//
|
|
49
|
+
// ── Loading ──────────────────────────────────────────────────────────────
|
|
50
|
+
//
|
|
51
|
+
// The SDK is ~600 KB minified (rrweb and the OTel web stack are bundled in
|
|
52
|
+
// it), so it is imported lazily: bundlers split it into its own chunk that
|
|
53
|
+
// loads after the app is interactive and costs the first paint nothing.
|
|
54
|
+
// initBrowserTelemetry is therefore async and idempotent — every caller gets
|
|
55
|
+
// the same promise, which is what survives React StrictMode's double mount and
|
|
56
|
+
// Fast Refresh. It is a no-op without a DOM (SSR, prerender) and without an
|
|
57
|
+
// endpoint, and it never throws.
|
|
58
|
+
import { trace } from "@opentelemetry/api";
|
|
59
|
+
import { setExceptionReporter } from "./error-reporter.js";
|
|
60
|
+
/** Where the browser ships telemetry: this origin's `/_otel`. */
|
|
61
|
+
export const DEFAULT_INGEST_PATH = "/_otel";
|
|
2
62
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
63
|
+
* The apiKey handed to the SDK. Public by construction: it identifies
|
|
64
|
+
* nothing and authorises nothing. See the header — the proxy or collector
|
|
65
|
+
* owns the real credential.
|
|
6
66
|
*/
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
67
|
+
export const PLACEHOLDER_API_KEY = "forge-browser-public";
|
|
68
|
+
let started = null;
|
|
69
|
+
/**
|
|
70
|
+
* initBrowserTelemetry starts the HyperDX browser SDK once per page load and
|
|
71
|
+
* resolves to whether it is running. See the module header for the contract.
|
|
72
|
+
*/
|
|
73
|
+
export function initBrowserTelemetry(config) {
|
|
74
|
+
if (typeof window === "undefined") {
|
|
75
|
+
return Promise.resolve(false);
|
|
76
|
+
}
|
|
77
|
+
if (started) {
|
|
78
|
+
return started;
|
|
79
|
+
}
|
|
80
|
+
const ingest = resolveIngestUrl(config.endpoint, window.location.origin);
|
|
81
|
+
if (!ingest) {
|
|
82
|
+
return Promise.resolve(false);
|
|
83
|
+
}
|
|
84
|
+
started = start(config, ingest);
|
|
85
|
+
return started;
|
|
86
|
+
}
|
|
87
|
+
async function start(config, ingest) {
|
|
88
|
+
try {
|
|
89
|
+
const hdx = resolveClient(await import("@hyperdx/browser"));
|
|
90
|
+
if (!hdx) {
|
|
91
|
+
return false;
|
|
27
92
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
name: e.error?.name ?? "Error",
|
|
34
|
-
message: e.message,
|
|
35
|
-
detail: { filename: e.filename, line: e.lineno, col: e.colno },
|
|
36
|
-
timestamp: Date.now(),
|
|
37
|
-
});
|
|
38
|
-
const onRejection = (e) => dispatch({
|
|
39
|
-
type: "unhandled-rejection",
|
|
40
|
-
name: "UnhandledRejection",
|
|
41
|
-
message: String(e.reason),
|
|
42
|
-
timestamp: Date.now(),
|
|
43
|
-
});
|
|
44
|
-
window.addEventListener("error", onError);
|
|
45
|
-
window.addEventListener("unhandledrejection", onRejection);
|
|
46
|
-
teardowns.push(() => window.removeEventListener("error", onError));
|
|
47
|
-
teardowns.push(() => window.removeEventListener("unhandledrejection", onRejection));
|
|
48
|
-
}
|
|
49
|
-
if (captureWebVitals && typeof PerformanceObserver === "function") {
|
|
50
|
-
let clsValue = 0;
|
|
51
|
-
const observe = (type, cb) => {
|
|
52
|
-
try {
|
|
53
|
-
const obs = new PerformanceObserver((list) => cb(list.getEntries()));
|
|
54
|
-
obs.observe({ type, buffered: true });
|
|
55
|
-
teardowns.push(() => obs.disconnect());
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
// Unsupported entry type in this browser — skip silently.
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
observe("largest-contentful-paint", (entries) => {
|
|
62
|
-
const last = entries.at(-1);
|
|
63
|
-
if (last) {
|
|
64
|
-
dispatch({
|
|
65
|
-
type: "web-vital",
|
|
66
|
-
name: "LCP",
|
|
67
|
-
value: Math.round(last.startTime),
|
|
68
|
-
timestamp: Date.now(),
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
observe("layout-shift", (entries) => {
|
|
73
|
-
for (const entry of entries) {
|
|
74
|
-
if (!entry.hadRecentInput) {
|
|
75
|
-
clsValue += entry.value;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
dispatch({
|
|
79
|
-
type: "web-vital",
|
|
80
|
-
name: "CLS",
|
|
81
|
-
value: Number(clsValue.toFixed(4)),
|
|
82
|
-
timestamp: Date.now(),
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
// FID (First Input Delay): processingStart − startTime of the first
|
|
86
|
-
// input. This is FID, NOT INP — INP needs the full interaction-latency
|
|
87
|
-
// model from the web-vitals library, which this dependency-free module
|
|
88
|
-
// deliberately doesn't pull in. Reach for `web-vitals` if you need INP.
|
|
89
|
-
observe("first-input", (entries) => {
|
|
90
|
-
const first = entries[0];
|
|
91
|
-
if (first) {
|
|
92
|
-
dispatch({
|
|
93
|
-
type: "web-vital",
|
|
94
|
-
name: "FID",
|
|
95
|
-
value: Math.round(first.processingStart - first.startTime),
|
|
96
|
-
timestamp: Date.now(),
|
|
97
|
-
});
|
|
93
|
+
hdx.init(buildSdkOptions(config, ingest, window.location.origin));
|
|
94
|
+
tapSdkInputs();
|
|
95
|
+
setExceptionReporter((error, attributes) => {
|
|
96
|
+
if (alreadyRecorded(error)) {
|
|
97
|
+
return;
|
|
98
98
|
}
|
|
99
|
+
hdx.recordException(error, attributes);
|
|
99
100
|
});
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
// Blocked chunk, ad-blocker, SDK throw: telemetry must never break the app.
|
|
105
|
+
console.debug("[telemetry] disabled:", err);
|
|
106
|
+
return false;
|
|
100
107
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
}
|
|
109
|
+
// ── One failure, one record ──────────────────────────────────────────────
|
|
110
|
+
//
|
|
111
|
+
// The SDK records an Error that reaches window "error", window
|
|
112
|
+
// "unhandledrejection", or console.error. React (18 and 19) console.errors an
|
|
113
|
+
// error a boundary catches BEFORE it calls componentDidCatch, and Next
|
|
114
|
+
// dispatches an uncaught render error to window "error" — so by the time
|
|
115
|
+
// reportException runs, the SDK has usually recorded that very error already.
|
|
116
|
+
// Recording it again would count one failure twice, and the issue layer
|
|
117
|
+
// (which groups by fingerprint) would report two.
|
|
118
|
+
//
|
|
119
|
+
// So the taps below remember which Error OBJECTS the SDK was shown, and the
|
|
120
|
+
// installed recorder skips those. Whatever the SDK did not see — a custom
|
|
121
|
+
// onCaughtError that silences React's logging, an error rethrown as a new
|
|
122
|
+
// object — is still recorded. The console tap is installed after the SDK's own
|
|
123
|
+
// wrapper, so it sits outermost and only observes; every call is forwarded
|
|
124
|
+
// untouched.
|
|
125
|
+
//
|
|
126
|
+
// The cost is the React component stack: the SDK's record of a boundary-caught
|
|
127
|
+
// error comes from console.error(error), which carries the error and its
|
|
128
|
+
// stack but not React's componentStack, and attributes passed to a skipped
|
|
129
|
+
// reportException are dropped with it.
|
|
130
|
+
const seenBySdk = new WeakSet();
|
|
131
|
+
let tapped = false;
|
|
132
|
+
function noteSeen(value) {
|
|
133
|
+
if (typeof value === "object" && value !== null) {
|
|
134
|
+
seenBySdk.add(value);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function tapSdkInputs() {
|
|
138
|
+
if (tapped) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
tapped = true;
|
|
142
|
+
const wrapped = console.error;
|
|
143
|
+
console.error = function (...args) {
|
|
144
|
+
for (const arg of args) {
|
|
145
|
+
if (arg instanceof Error) {
|
|
146
|
+
noteSeen(arg);
|
|
147
|
+
}
|
|
104
148
|
}
|
|
105
|
-
|
|
149
|
+
return wrapped.apply(this, args);
|
|
150
|
+
};
|
|
151
|
+
window.addEventListener("error", (e) => noteSeen(e.error), true);
|
|
152
|
+
window.addEventListener("unhandledrejection", (e) => noteSeen(e.reason), true);
|
|
153
|
+
}
|
|
154
|
+
function alreadyRecorded(error) {
|
|
155
|
+
return typeof error === "object" && error !== null && seenBySdk.has(error);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The options passed to HyperDX.init. Exported for tests and for apps that
|
|
159
|
+
* want to see exactly what is configured — it is pure.
|
|
160
|
+
*/
|
|
161
|
+
export function buildSdkOptions(config, ingestUrl, pageOrigin) {
|
|
162
|
+
const replay = config.replay === true;
|
|
163
|
+
const resourceAttributes = {};
|
|
164
|
+
if (config.serviceVersion) {
|
|
165
|
+
resourceAttributes["service.version"] = config.serviceVersion;
|
|
166
|
+
}
|
|
167
|
+
if (config.serviceNamespace) {
|
|
168
|
+
resourceAttributes["service.namespace"] = config.serviceNamespace;
|
|
169
|
+
}
|
|
170
|
+
if (config.environment) {
|
|
171
|
+
resourceAttributes["deployment.environment.name"] = config.environment;
|
|
172
|
+
resourceAttributes["deployment.environment"] = config.environment;
|
|
173
|
+
}
|
|
174
|
+
const options = {
|
|
175
|
+
apiKey: PLACEHOLDER_API_KEY,
|
|
176
|
+
service: config.serviceName,
|
|
177
|
+
url: ingestUrl,
|
|
178
|
+
tracePropagationTargets: propagationTargets(pageOrigin, config.apiOrigins),
|
|
179
|
+
// The SDK would otherwise trace its own exports, and it must never
|
|
180
|
+
// observe the route it ships through.
|
|
181
|
+
ignoreUrls: [new RegExp(`^${escapeRegExp(ingestUrl)}`)],
|
|
182
|
+
consoleCapture: config.consoleCapture ?? true,
|
|
183
|
+
advancedNetworkCapture: config.advancedNetworkCapture ?? false,
|
|
184
|
+
// Polls for window.Intercom on a timer; nothing here uses Intercom.
|
|
185
|
+
disableIntercom: true,
|
|
186
|
+
disableReplay: !replay,
|
|
187
|
+
otelResourceAttributes: resourceAttributes,
|
|
188
|
+
debug: config.debug ?? false,
|
|
106
189
|
};
|
|
190
|
+
if (replay) {
|
|
191
|
+
// The SDK's own default masks inputs but NOT text; text is where chat
|
|
192
|
+
// content and records live. Force both.
|
|
193
|
+
options.maskAllInputs = true;
|
|
194
|
+
options.maskAllText = true;
|
|
195
|
+
options.recordCanvas = false;
|
|
196
|
+
}
|
|
197
|
+
return options;
|
|
107
198
|
}
|
|
108
|
-
/**
|
|
109
|
-
|
|
199
|
+
/**
|
|
200
|
+
* resolveIngestUrl turns the configured endpoint into the absolute URL the SDK
|
|
201
|
+
* is given, or null when telemetry is off. A path resolves against the page
|
|
202
|
+
* origin, so the default is same-origin by construction.
|
|
203
|
+
*/
|
|
204
|
+
export function resolveIngestUrl(endpoint, pageOrigin) {
|
|
205
|
+
const value = (endpoint ?? "").trim();
|
|
206
|
+
if (value === "" || value.toLowerCase() === "off") {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
110
209
|
try {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
210
|
+
return new URL(value, pageOrigin).toString().replace(/\/+$/, "");
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function propagationTargets(pageOrigin, apiOrigins = []) {
|
|
217
|
+
const origins = new Set([pageOrigin]);
|
|
218
|
+
for (const raw of apiOrigins) {
|
|
219
|
+
if (!raw) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
origins.add(new URL(raw, pageOrigin).origin);
|
|
114
224
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
225
|
+
catch {
|
|
226
|
+
// An unparsable origin is not a target.
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return [...origins].map((o) => new RegExp(`^${escapeRegExp(o)}(?:[/?#]|$)`));
|
|
230
|
+
}
|
|
231
|
+
function escapeRegExp(s) {
|
|
232
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
233
|
+
}
|
|
234
|
+
// @hyperdx/browser is a UMD/CJS bundle whose module.exports IS the client, and
|
|
235
|
+
// its methods use `this`. A bundler hands back a module NAMESPACE that exposes
|
|
236
|
+
// both the client's members at the top level (as read-only getters) and the
|
|
237
|
+
// client itself as `default`. Calling init on the namespace binds `this` to that
|
|
238
|
+
// frozen object and the SDK throws on its first property write, which disabled
|
|
239
|
+
// telemetry silently. So descend to the deepest `default` that is still a
|
|
240
|
+
// client, the real instance, and never settle for the namespace in front of it.
|
|
241
|
+
function resolveClient(mod) {
|
|
242
|
+
let cur = mod;
|
|
243
|
+
for (let depth = 0; depth < 4; depth++) {
|
|
244
|
+
const next = peek(cur, "default");
|
|
245
|
+
if (!isClient(next)) {
|
|
246
|
+
break;
|
|
122
247
|
}
|
|
248
|
+
cur = next;
|
|
249
|
+
}
|
|
250
|
+
return isClient(cur) ? cur : null;
|
|
251
|
+
}
|
|
252
|
+
function isClient(v) {
|
|
253
|
+
return (typeof peek(v, "init") === "function" &&
|
|
254
|
+
typeof peek(v, "recordException") === "function");
|
|
255
|
+
}
|
|
256
|
+
// A module namespace object may throw on an absent key (strict ESM
|
|
257
|
+
// interop shims and test doubles do), and "absent" is an answer here.
|
|
258
|
+
function peek(obj, key) {
|
|
259
|
+
try {
|
|
260
|
+
return obj[key];
|
|
123
261
|
}
|
|
124
262
|
catch {
|
|
125
|
-
|
|
263
|
+
return undefined;
|
|
126
264
|
}
|
|
127
265
|
}
|
|
266
|
+
/** The tracer to open manual spans on. Resolves HyperDX's provider once it is running. */
|
|
267
|
+
export function getTracer(name = "default") {
|
|
268
|
+
return trace.getTracer(name);
|
|
269
|
+
}
|
|
270
|
+
export { context, propagation, trace } from "@opentelemetry/api";
|
|
128
271
|
//# sourceMappingURL=telemetry.js.map
|
package/dist/telemetry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"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","sourcesContent":["// Part of @reliantlabs/forge-web-runtime — the web twin of forge/pkg.\n//\n// Lightweight client RUM: unhandled errors + core Web Vitals, captured with\n// native browser APIs (no extra dependency). Export is OPT-IN: by default\n// events go to an in-process sink (console.debug in dev). Set `endpoint` to\n// beacon them to a browser-reachable OTLP/HTTP collector (needs CORS on that\n// collector — stand it up before relying on export). This is the config seam;\n// distributed tracing itself (the traceparent on every RPC) is always on and\n// lives in trace.ts — it does NOT depend on this module.\nexport interface TelemetryEvent {\n type: \"error\" | \"unhandled-rejection\" | \"web-vital\";\n name: string;\n value?: number;\n message?: string;\n detail?: Record<string, unknown>;\n timestamp: number;\n}\n\nexport interface TelemetryConfig {\n serviceName?: string;\n /** OTLP/HTTP-ish collector URL. Unset = no network export (default). */\n endpoint?: string;\n /** In-process sink for every event. Default: console.debug in dev. */\n onEvent?: (event: TelemetryEvent) => void;\n /** Capture window errors + unhandled rejections. Default true. */\n captureErrors?: boolean;\n /** Capture LCP / CLS / FID via PerformanceObserver. Default true. */\n captureWebVitals?: boolean;\n}\n\ninterface LayoutShiftEntry extends PerformanceEntry {\n value: number;\n hadRecentInput: boolean;\n}\n\ninterface FirstInputEntry extends PerformanceEntry {\n processingStart: number;\n}\n\n// `process` is a bundler-injected global (Next replaces the literal\n// `process.env.NODE_ENV` at build time), not a browser one. Declaring it\n// locally keeps this package free of a @types/node dependency while leaving\n// the expression textually intact so that replacement still happens.\ndeclare const process: { env?: Record<string, string | undefined> } | undefined;\n\nlet started = false;\n\n/**\n * initClientTelemetry wires the capture listeners and returns a teardown\n * function. Idempotent and SSR-safe (no-op without a DOM). Call once from the\n * app shell (e.g. inside Providers' mount effect).\n */\nexport function initClientTelemetry(config: TelemetryConfig = {}): () => void {\n if (started || typeof window === \"undefined\") {\n return () => {};\n }\n started = true;\n\n const serviceName = config.serviceName ?? \"frontend\";\n const captureErrors = config.captureErrors ?? true;\n const captureWebVitals = config.captureWebVitals ?? true;\n\n const dispatch = (event: TelemetryEvent) => {\n if (config.onEvent) {\n config.onEvent(event);\n } else if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV === \"development\"\n ) {\n // The `typeof` guard keeps this framework-agnostic — Vite SPAs and\n // plain browsers have no `process` at all.\n console.debug(\"[telemetry]\", event.type, event.name, event.value ?? \"\");\n }\n if (config.endpoint) {\n exportEvent(config.endpoint, serviceName, event);\n }\n };\n\n const teardowns: Array<() => void> = [];\n\n if (captureErrors) {\n const onError = (e: ErrorEvent) =>\n dispatch({\n type: \"error\",\n name: e.error?.name ?? \"Error\",\n message: e.message,\n detail: { filename: e.filename, line: e.lineno, col: e.colno },\n timestamp: Date.now(),\n });\n const onRejection = (e: PromiseRejectionEvent) =>\n dispatch({\n type: \"unhandled-rejection\",\n name: \"UnhandledRejection\",\n message: String(e.reason),\n timestamp: Date.now(),\n });\n window.addEventListener(\"error\", onError);\n window.addEventListener(\"unhandledrejection\", onRejection);\n teardowns.push(() => window.removeEventListener(\"error\", onError));\n teardowns.push(() =>\n window.removeEventListener(\"unhandledrejection\", onRejection),\n );\n }\n\n if (captureWebVitals && typeof PerformanceObserver === \"function\") {\n let clsValue = 0;\n const observe = (\n type: string,\n cb: (entries: PerformanceEntryList) => void,\n ) => {\n try {\n const obs = new PerformanceObserver((list) => cb(list.getEntries()));\n obs.observe({ type, buffered: true });\n teardowns.push(() => obs.disconnect());\n } catch {\n // Unsupported entry type in this browser — skip silently.\n }\n };\n\n observe(\"largest-contentful-paint\", (entries) => {\n const last = entries.at(-1);\n if (last) {\n dispatch({\n type: \"web-vital\",\n name: \"LCP\",\n value: Math.round(last.startTime),\n timestamp: Date.now(),\n });\n }\n });\n\n observe(\"layout-shift\", (entries) => {\n for (const entry of entries as LayoutShiftEntry[]) {\n if (!entry.hadRecentInput) {\n clsValue += entry.value;\n }\n }\n dispatch({\n type: \"web-vital\",\n name: \"CLS\",\n value: Number(clsValue.toFixed(4)),\n timestamp: Date.now(),\n });\n });\n\n // FID (First Input Delay): processingStart − startTime of the first\n // input. This is FID, NOT INP — INP needs the full interaction-latency\n // model from the web-vitals library, which this dependency-free module\n // deliberately doesn't pull in. Reach for `web-vitals` if you need INP.\n observe(\"first-input\", (entries) => {\n const first = entries[0] as FirstInputEntry | undefined;\n if (first) {\n dispatch({\n type: \"web-vital\",\n name: \"FID\",\n value: Math.round(first.processingStart - first.startTime),\n timestamp: Date.now(),\n });\n }\n });\n }\n\n return () => {\n for (const t of teardowns) {\n t();\n }\n started = false;\n };\n}\n\n/** Best-effort beacon of one event. Never throws into the caller. */\nfunction exportEvent(\n endpoint: string,\n serviceName: string,\n event: TelemetryEvent,\n): void {\n try {\n const body = JSON.stringify({ service: serviceName, ...event });\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(endpoint, body);\n } else {\n void fetch(endpoint, {\n method: \"POST\",\n body,\n keepalive: true,\n headers: { \"Content-Type\": \"application/json\" },\n }).catch(() => {});\n }\n } catch {\n // Telemetry must never break the app.\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"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","sourcesContent":["// Part of @reliantlabs/forge-web-runtime — the web twin of forge/pkg.\n//\n// Browser telemetry: the HyperDX browser SDK, initialised the forge way.\n//\n// Captures errors (uncaught, unhandled rejections, boundary-caught), console\n// output, fetch/XHR spans, document load, long tasks and web vitals, and\n// ships them to ClickStack through a SAME-ORIGIN `/_otel` route.\n//\n// ── Why HyperDX REPLACES the old OTel web wiring, not joins it ───────────\n//\n// @hyperdx/browser wraps @hyperdx/otel-web, which builds its own\n// WebTracerProvider and calls provider.register() — the global tracer\n// provider, context manager and propagator. A second registration (the old\n// `./otel` subpath did exactly that) is rejected by @opentelemetry/api with a\n// \"duplicate registration\" diagnostic and silently loses: whichever ran\n// second would simply never see a span. Two tracer providers cannot coexist\n// in one page, so there is one, and it is HyperDX's. `./otel` and its eight\n// @opentelemetry/* SDK peer dependencies are gone.\n//\n// traceparent on RPCs is unaffected. traceInterceptor (trace.ts) injects the\n// active span's context into every Connect call, or synthesises a valid\n// traceparent when none is active — with no SDK at all. With the SDK running,\n// the fetch span HyperDX opens for the call carries the same trace id, and\n// `apiOrigins` below makes that hold for a cross-origin API too (which is\n// every dev loop: the API binds its own port).\n//\n// ── The browser holds no secret ──────────────────────────────────────────\n//\n// `url` is `${location.origin}/_otel`. The browser talks only to its own\n// origin; a dev-server proxy (next.config.ts rewrites / Vite server.proxy) or\n// the deployment's ingress forwards `/_otel/v1/{traces,logs}` to the OTLP/HTTP\n// port of a collector, and that hop — never the bundle — owns credentials.\n//\n// The SDK nevertheless wants an `apiKey` and sends it as an `authorization`\n// header. We pass PLACEHOLDER_API_KEY, a constant that grants nothing and is\n// public by construction. A collector that enforces ingestion auth (ClickStack\n// `collectorAuthenticationEnforced`) rejects it with 401, so the proxy in\n// front of such a collector must strip the header or replace it with the real\n// key. The local ClickStack collector does not enforce, and ignores it.\n//\n// ── Replay: shipped, off, masked ─────────────────────────────────────────\n//\n// The SDK bundles an rrweb recorder. It is DISABLED unless `replay: true`.\n// When enabled, masking is forced on — every input and all text — because a\n// replay records the DOM, which for most apps is the most sensitive data they\n// have. There is deliberately no option to turn masking off while replay is\n// on; an app that needs unmasked replay passes its own SDK options.\n//\n// ── Loading ──────────────────────────────────────────────────────────────\n//\n// The SDK is ~600 KB minified (rrweb and the OTel web stack are bundled in\n// it), so it is imported lazily: bundlers split it into its own chunk that\n// loads after the app is interactive and costs the first paint nothing.\n// initBrowserTelemetry is therefore async and idempotent — every caller gets\n// the same promise, which is what survives React StrictMode's double mount and\n// Fast Refresh. It is a no-op without a DOM (SSR, prerender) and without an\n// endpoint, and it never throws.\nimport { trace } from \"@opentelemetry/api\";\n\nimport { setExceptionReporter } from \"./error-reporter.js\";\n\n/** Where the browser ships telemetry: this origin's `/_otel`. */\nexport const DEFAULT_INGEST_PATH = \"/_otel\";\n\n/**\n * The apiKey handed to the SDK. Public by construction: it identifies\n * nothing and authorises nothing. See the header — the proxy or collector\n * owns the real credential.\n */\nexport const PLACEHOLDER_API_KEY = \"forge-browser-public\";\n\nexport interface BrowserTelemetryConfig {\n /** `service.name` on everything this page emits. */\n serviceName: string;\n /** `service.version`. Omit for none. */\n serviceVersion?: string;\n /**\n * The forge environment name. Emitted as BOTH `deployment.environment.name`\n * (current semconv) and `deployment.environment` (the legacy key HyperDX's\n * default sources still key on).\n */\n environment?: string;\n /** `service.namespace`, conventionally the project name. */\n serviceNamespace?: string;\n /**\n * Ingest base: a path on this origin (`/_otel`) or, as an escape hatch, an\n * absolute URL. Unset, empty, or `off` disables telemetry — the app runs\n * untraced rather than throwing, which is what makes it safe to call\n * unconditionally from the app shell.\n */\n endpoint?: string;\n /**\n * Origins (or URLs — only the origin is used) of the app's own backend.\n * Requests to them always get a `traceparent` from the SDK's fetch/XHR\n * spans. This page's own origin is always included.\n */\n apiOrigins?: Array<string | undefined>;\n /** Mirror console.* into telemetry. Default true. */\n consoleCapture?: boolean;\n /**\n * Capture full request/response headers AND BODIES on fetch/XHR spans.\n * Default false, and it should stay false: bodies are user data.\n */\n advancedNetworkCapture?: boolean;\n /** Session replay (rrweb). Default false. When true, all text and inputs are masked. */\n replay?: boolean;\n /** Verbose SDK diagnostics to the console. Default false. */\n debug?: boolean;\n}\n\n/** The slice of the SDK this module calls. Typed here, not imported, so the module loads without it. */\ninterface HyperDXClient {\n init(config: Record<string, unknown>): void;\n recordException(error: unknown, attributes?: Record<string, unknown>): void;\n}\n\nlet started: Promise<boolean> | null = null;\n\n/**\n * initBrowserTelemetry starts the HyperDX browser SDK once per page load and\n * resolves to whether it is running. See the module header for the contract.\n */\nexport function initBrowserTelemetry(\n config: BrowserTelemetryConfig,\n): Promise<boolean> {\n if (typeof window === \"undefined\") {\n return Promise.resolve(false);\n }\n if (started) {\n return started;\n }\n const ingest = resolveIngestUrl(config.endpoint, window.location.origin);\n if (!ingest) {\n return Promise.resolve(false);\n }\n started = start(config, ingest);\n return started;\n}\n\nasync function start(\n config: BrowserTelemetryConfig,\n ingest: string,\n): Promise<boolean> {\n try {\n const hdx = resolveClient(await import(\"@hyperdx/browser\"));\n if (!hdx) {\n return false;\n }\n hdx.init(buildSdkOptions(config, ingest, window.location.origin));\n tapSdkInputs();\n setExceptionReporter((error, attributes) => {\n if (alreadyRecorded(error)) {\n return;\n }\n hdx.recordException(error, attributes);\n });\n return true;\n } catch (err) {\n // Blocked chunk, ad-blocker, SDK throw: telemetry must never break the app.\n console.debug(\"[telemetry] disabled:\", err);\n return false;\n }\n}\n\n// ── One failure, one record ──────────────────────────────────────────────\n//\n// The SDK records an Error that reaches window \"error\", window\n// \"unhandledrejection\", or console.error. React (18 and 19) console.errors an\n// error a boundary catches BEFORE it calls componentDidCatch, and Next\n// dispatches an uncaught render error to window \"error\" — so by the time\n// reportException runs, the SDK has usually recorded that very error already.\n// Recording it again would count one failure twice, and the issue layer\n// (which groups by fingerprint) would report two.\n//\n// So the taps below remember which Error OBJECTS the SDK was shown, and the\n// installed recorder skips those. Whatever the SDK did not see — a custom\n// onCaughtError that silences React's logging, an error rethrown as a new\n// object — is still recorded. The console tap is installed after the SDK's own\n// wrapper, so it sits outermost and only observes; every call is forwarded\n// untouched.\n//\n// The cost is the React component stack: the SDK's record of a boundary-caught\n// error comes from console.error(error), which carries the error and its\n// stack but not React's componentStack, and attributes passed to a skipped\n// reportException are dropped with it.\nconst seenBySdk = new WeakSet<object>();\nlet tapped = false;\n\nfunction noteSeen(value: unknown): void {\n if (typeof value === \"object\" && value !== null) {\n seenBySdk.add(value);\n }\n}\n\nfunction tapSdkInputs(): void {\n if (tapped) {\n return;\n }\n tapped = true;\n const wrapped = console.error;\n console.error = function (this: unknown, ...args: unknown[]) {\n for (const arg of args) {\n if (arg instanceof Error) {\n noteSeen(arg);\n }\n }\n return wrapped.apply(this, args);\n };\n window.addEventListener(\"error\", (e) => noteSeen(e.error), true);\n window.addEventListener(\n \"unhandledrejection\",\n (e) => noteSeen(e.reason),\n true,\n );\n}\n\nfunction alreadyRecorded(error: unknown): boolean {\n return typeof error === \"object\" && error !== null && seenBySdk.has(error);\n}\n\n/**\n * The options passed to HyperDX.init. Exported for tests and for apps that\n * want to see exactly what is configured — it is pure.\n */\nexport function buildSdkOptions(\n config: BrowserTelemetryConfig,\n ingestUrl: string,\n pageOrigin: string,\n): Record<string, unknown> {\n const replay = config.replay === true;\n const resourceAttributes: Record<string, string> = {};\n if (config.serviceVersion) {\n resourceAttributes[\"service.version\"] = config.serviceVersion;\n }\n if (config.serviceNamespace) {\n resourceAttributes[\"service.namespace\"] = config.serviceNamespace;\n }\n if (config.environment) {\n resourceAttributes[\"deployment.environment.name\"] = config.environment;\n resourceAttributes[\"deployment.environment\"] = config.environment;\n }\n\n const options: Record<string, unknown> = {\n apiKey: PLACEHOLDER_API_KEY,\n service: config.serviceName,\n url: ingestUrl,\n tracePropagationTargets: propagationTargets(pageOrigin, config.apiOrigins),\n // The SDK would otherwise trace its own exports, and it must never\n // observe the route it ships through.\n ignoreUrls: [new RegExp(`^${escapeRegExp(ingestUrl)}`)],\n consoleCapture: config.consoleCapture ?? true,\n advancedNetworkCapture: config.advancedNetworkCapture ?? false,\n // Polls for window.Intercom on a timer; nothing here uses Intercom.\n disableIntercom: true,\n disableReplay: !replay,\n otelResourceAttributes: resourceAttributes,\n debug: config.debug ?? false,\n };\n if (replay) {\n // The SDK's own default masks inputs but NOT text; text is where chat\n // content and records live. Force both.\n options.maskAllInputs = true;\n options.maskAllText = true;\n options.recordCanvas = false;\n }\n return options;\n}\n\n/**\n * resolveIngestUrl turns the configured endpoint into the absolute URL the SDK\n * is given, or null when telemetry is off. A path resolves against the page\n * origin, so the default is same-origin by construction.\n */\nexport function resolveIngestUrl(\n endpoint: string | undefined,\n pageOrigin: string,\n): string | null {\n const value = (endpoint ?? \"\").trim();\n if (value === \"\" || value.toLowerCase() === \"off\") {\n return null;\n }\n try {\n return new URL(value, pageOrigin).toString().replace(/\\/+$/, \"\");\n } catch {\n return null;\n }\n}\n\nfunction propagationTargets(\n pageOrigin: string,\n apiOrigins: Array<string | undefined> = [],\n): RegExp[] {\n const origins = new Set<string>([pageOrigin]);\n for (const raw of apiOrigins) {\n if (!raw) {\n continue;\n }\n try {\n origins.add(new URL(raw, pageOrigin).origin);\n } catch {\n // An unparsable origin is not a target.\n }\n }\n return [...origins].map((o) => new RegExp(`^${escapeRegExp(o)}(?:[/?#]|$)`));\n}\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n// @hyperdx/browser is a UMD/CJS bundle whose module.exports IS the client, and\n// its methods use `this`. A bundler hands back a module NAMESPACE that exposes\n// both the client's members at the top level (as read-only getters) and the\n// client itself as `default`. Calling init on the namespace binds `this` to that\n// frozen object and the SDK throws on its first property write, which disabled\n// telemetry silently. So descend to the deepest `default` that is still a\n// client, the real instance, and never settle for the namespace in front of it.\nfunction resolveClient(mod: unknown): HyperDXClient | null {\n let cur: unknown = mod;\n for (let depth = 0; depth < 4; depth++) {\n const next = peek(cur, \"default\");\n if (!isClient(next)) {\n break;\n }\n cur = next;\n }\n return isClient(cur) ? cur : null;\n}\n\nfunction isClient(v: unknown): v is HyperDXClient {\n return (\n typeof peek(v, \"init\") === \"function\" &&\n typeof peek(v, \"recordException\") === \"function\"\n );\n}\n\n// A module namespace object may throw on an absent key (strict ESM\n// interop shims and test doubles do), and \"absent\" is an answer here.\nfunction peek(obj: unknown, key: string): unknown {\n try {\n return (obj as Record<string, unknown>)[key];\n } catch {\n return undefined;\n }\n}\n\n/** The tracer to open manual spans on. Resolves HyperDX's provider once it is running. */\nexport function getTracer(name = \"default\") {\n return trace.getTracer(name);\n}\n\nexport { context, propagation, trace } from \"@opentelemetry/api\";\n"]}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reliantlabs/forge-web-runtime",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The forge frontend runtime: Connect interceptor stack, session, error boundary, toast queue, <Resource> container, W3C trace propagation and
|
|
5
|
-
"license": "
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "The forge frontend runtime: Connect interceptor stack, session, error boundary, toast queue, <Resource> container, W3C trace propagation and the HyperDX browser SDK wiring (errors, console, network, traces; replay off by default). The web twin of forge/pkg.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
6
|
"//repository note": "REQUIRED for `npm publish --provenance`, not decoration. npm cross-checks this against the repository in the signed provenance statement and rejects the publish with a 422 if they disagree — an absent field reads as \"\" and fails that comparison. `directory` names the subdirectory this package lives in, so the provenance points at the real source. Omitting this is what made the first 0.3.1 publish fail AFTER the tarball had been signed and written to the sigstore transparency log.",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"files": [
|
|
23
23
|
"dist",
|
|
24
24
|
"interceptors",
|
|
25
|
-
"README.md"
|
|
25
|
+
"README.md",
|
|
26
|
+
"NOTICE"
|
|
26
27
|
],
|
|
27
28
|
"main": "./dist/index.js",
|
|
28
29
|
"types": "./dist/index.d.ts",
|
|
@@ -39,14 +40,14 @@
|
|
|
39
40
|
"types": "./dist/mock-transport.d.ts",
|
|
40
41
|
"default": "./dist/mock-transport.js"
|
|
41
42
|
},
|
|
42
|
-
"./otel": {
|
|
43
|
-
"types": "./dist/otel.d.ts",
|
|
44
|
-
"default": "./dist/otel.js"
|
|
45
|
-
},
|
|
46
43
|
"./service-hooks": {
|
|
47
44
|
"types": "./dist/service-hooks.d.ts",
|
|
48
45
|
"default": "./dist/service-hooks.js"
|
|
49
46
|
},
|
|
47
|
+
"./telemetry": {
|
|
48
|
+
"types": "./dist/telemetry.d.ts",
|
|
49
|
+
"default": "./dist/telemetry.js"
|
|
50
|
+
},
|
|
50
51
|
"./package.json": "./package.json"
|
|
51
52
|
},
|
|
52
53
|
"scripts": {
|
|
@@ -57,19 +58,12 @@
|
|
|
57
58
|
"pretest": "node scripts/build.mjs",
|
|
58
59
|
"test": "vitest run"
|
|
59
60
|
},
|
|
60
|
-
"// peerDependencies note": "@opentelemetry/api is REQUIRED (trace.ts propagates traceparent on every RPC — always on, no collector).
|
|
61
|
+
"// peerDependencies note": "@opentelemetry/api is REQUIRED (trace.ts propagates traceparent on every RPC — always on, no collector). @hyperdx/browser is OPTIONAL and exact-pinned: it is imported only (and lazily) by the './telemetry' subpath, which the Next.js and Vite scaffolds reach. It carries the whole OpenTelemetry web SDK and the rrweb recorder as its own dependencies, so a frontend that never imports './telemetry' (React Native) installs none of it and is not told it is missing a package it will never resolve.",
|
|
61
62
|
"peerDependencies": {
|
|
62
63
|
"@bufbuild/protobuf": "^2.5.0",
|
|
63
64
|
"@connectrpc/connect": "^2.0.0",
|
|
65
|
+
"@hyperdx/browser": "0.26.0",
|
|
64
66
|
"@opentelemetry/api": "^1.9.0",
|
|
65
|
-
"@opentelemetry/auto-instrumentations-web": "^0.67.0",
|
|
66
|
-
"@opentelemetry/core": "^2.8.0",
|
|
67
|
-
"@opentelemetry/exporter-trace-otlp-http": "^0.222.0",
|
|
68
|
-
"@opentelemetry/instrumentation": "^0.222.0",
|
|
69
|
-
"@opentelemetry/resources": "^2.8.0",
|
|
70
|
-
"@opentelemetry/sdk-trace-base": "^2.8.0",
|
|
71
|
-
"@opentelemetry/sdk-trace-web": "^2.8.0",
|
|
72
|
-
"@opentelemetry/semantic-conventions": "^1.28.0",
|
|
73
67
|
"@tanstack/react-query": "^5.59.0",
|
|
74
68
|
"react": "^18.3.0 || ^19.0.0"
|
|
75
69
|
},
|
|
@@ -77,48 +71,22 @@
|
|
|
77
71
|
"@tanstack/react-query": {
|
|
78
72
|
"optional": true
|
|
79
73
|
},
|
|
80
|
-
"@
|
|
81
|
-
"optional": true
|
|
82
|
-
},
|
|
83
|
-
"@opentelemetry/core": {
|
|
84
|
-
"optional": true
|
|
85
|
-
},
|
|
86
|
-
"@opentelemetry/exporter-trace-otlp-http": {
|
|
87
|
-
"optional": true
|
|
88
|
-
},
|
|
89
|
-
"@opentelemetry/instrumentation": {
|
|
90
|
-
"optional": true
|
|
91
|
-
},
|
|
92
|
-
"@opentelemetry/resources": {
|
|
93
|
-
"optional": true
|
|
94
|
-
},
|
|
95
|
-
"@opentelemetry/sdk-trace-base": {
|
|
96
|
-
"optional": true
|
|
97
|
-
},
|
|
98
|
-
"@opentelemetry/sdk-trace-web": {
|
|
99
|
-
"optional": true
|
|
100
|
-
},
|
|
101
|
-
"@opentelemetry/semantic-conventions": {
|
|
74
|
+
"@hyperdx/browser": {
|
|
102
75
|
"optional": true
|
|
103
76
|
}
|
|
104
77
|
},
|
|
105
78
|
"devDependencies": {
|
|
106
79
|
"@bufbuild/protobuf": "^2.5.0",
|
|
107
80
|
"@connectrpc/connect": "^2.0.0",
|
|
81
|
+
"@hyperdx/browser": "0.26.0",
|
|
108
82
|
"@opentelemetry/api": "^1.9.0",
|
|
109
|
-
"@opentelemetry/auto-instrumentations-web": "^0.67.0",
|
|
110
|
-
"@opentelemetry/core": "^2.8.0",
|
|
111
|
-
"@opentelemetry/exporter-trace-otlp-http": "^0.222.0",
|
|
112
|
-
"@opentelemetry/instrumentation": "^0.222.0",
|
|
113
|
-
"@opentelemetry/resources": "^2.8.0",
|
|
114
|
-
"@opentelemetry/sdk-trace-base": "^2.8.0",
|
|
115
|
-
"@opentelemetry/sdk-trace-web": "^2.8.0",
|
|
116
|
-
"@opentelemetry/semantic-conventions": "^1.28.0",
|
|
117
83
|
"@tanstack/react-query": "^5.59.0",
|
|
118
84
|
"@types/node": "^22.14.0",
|
|
119
85
|
"@types/react": "^19.1.0",
|
|
120
86
|
"@types/react-18": "npm:@types/react@~18.3.0",
|
|
87
|
+
"@types/react-dom": "^19.3.0",
|
|
121
88
|
"react": "^19.1.0",
|
|
89
|
+
"react-dom": "^19.3.0",
|
|
122
90
|
"typescript": "^5.8.0",
|
|
123
91
|
"vitest": "^4.1.11"
|
|
124
92
|
},
|
package/dist/otel.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { context, propagation, trace } from "@opentelemetry/api";
|
|
2
|
-
export interface BrowserTracingConfig {
|
|
3
|
-
/**
|
|
4
|
-
* Collector base URL. Spans are POSTed to `${endpoint}/v1/traces`. Unset or
|
|
5
|
-
* empty is a no-op — the app runs untraced rather than throwing, which is
|
|
6
|
-
* what makes this safe to call unconditionally from the app shell.
|
|
7
|
-
*/
|
|
8
|
-
endpoint?: string;
|
|
9
|
-
/** `service.name` on every span. */
|
|
10
|
-
serviceName: string;
|
|
11
|
-
/** `service.version` on every span. Defaults to "0.0.0". */
|
|
12
|
-
serviceVersion?: string;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* initBrowserTracing registers the browser tracer provider and the fetch /
|
|
16
|
-
* XHR / document-load auto-instrumentations.
|
|
17
|
-
*
|
|
18
|
-
* No-ops without an `endpoint` (nothing to export to) and without a DOM
|
|
19
|
-
* (SSR / prerender). Idempotent: a second call while a provider is already
|
|
20
|
-
* registered returns without double-registering, which is what survives
|
|
21
|
-
* React StrictMode's double-mount and Fast Refresh.
|
|
22
|
-
*
|
|
23
|
-
* The env vars that carry the endpoint stay in APPLICATION code
|
|
24
|
-
* (`NEXT_PUBLIC_OTEL_ENDPOINT` and friends) — a bundler replaces those
|
|
25
|
-
* literals at build time, and it can only do that where they are written
|
|
26
|
-
* out verbatim.
|
|
27
|
-
*/
|
|
28
|
-
export declare function initBrowserTracing(config: BrowserTracingConfig): void;
|
|
29
|
-
/** The tracer to open manual spans on. */
|
|
30
|
-
export declare function getTracer(name?: string): import("@opentelemetry/api").Tracer;
|
|
31
|
-
export { context, propagation, trace };
|