@obsrviq/tracker 0.7.0 → 0.7.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/index.js +35 -11
- package/dist/index.js.map +1 -1
- package/dist/obsrviq.global.js +37 -37
- package/dist/obsrviq.global.js.map +1 -1
- package/package.json +1 -1
- package/src/network.ts +14 -8
- package/src/trace.ts +42 -12
package/package.json
CHANGED
package/src/network.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { NetworkEvent, NetworkResourceType, NetworkTiming } from '@obsrviq/
|
|
|
2
2
|
import type { InstrumentCtx, Teardown } from './context.js';
|
|
3
3
|
import { maskString } from './mask.js';
|
|
4
4
|
import { tokenBucket } from './ratelimit.js';
|
|
5
|
-
import {
|
|
5
|
+
import { applyFetchTrace, newTrace, traceIdOf, traceTarget } from './trace.js';
|
|
6
6
|
import { uuid } from './util.js';
|
|
7
7
|
|
|
8
8
|
const BODY_CAP = 4 * 1024; // 4 KB body preview cap
|
|
@@ -345,11 +345,10 @@ function wrapFetch(ctx: InstrumentCtx, tr: TimingResolver, emitNet: EmitNet): Te
|
|
|
345
345
|
let traceId: string | undefined;
|
|
346
346
|
if (traceTarget(rawUrl, ctx.config)) {
|
|
347
347
|
try {
|
|
348
|
-
const
|
|
349
|
-
const inj = injectFetchTrace(input, init, tr.header);
|
|
348
|
+
const inj = applyFetchTrace(input, init);
|
|
350
349
|
input = inj.input;
|
|
351
350
|
init = inj.init;
|
|
352
|
-
traceId =
|
|
351
|
+
traceId = inj.traceId || undefined;
|
|
353
352
|
} catch {
|
|
354
353
|
/* never let propagation break the request */
|
|
355
354
|
}
|
|
@@ -485,12 +484,19 @@ function wrapXhr(ctx: InstrumentCtx, tr: TimingResolver, emitNet: EmitNet): Tear
|
|
|
485
484
|
if (ctx.config.captureNetworkBodies) meta.body = stringifyBody(body);
|
|
486
485
|
// Trace-context propagation (first-party only). setRequestHeader is valid
|
|
487
486
|
// here — after open(), before the real send() below. Our own setRequestHeader
|
|
488
|
-
// patch records it into meta.reqHeaders (traceparent is allowlisted)
|
|
487
|
+
// patch records it into meta.reqHeaders (traceparent is allowlisted), so a
|
|
488
|
+
// traceparent the APP already set is visible here: respect it (record its id,
|
|
489
|
+
// don't re-set — XHR COMBINES duplicate headers into an invalid "v1, v2").
|
|
489
490
|
if (traceTarget(meta.rawUrl, ctx.config)) {
|
|
490
491
|
try {
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
492
|
+
const appSet = meta.reqHeaders['traceparent'];
|
|
493
|
+
if (appSet) {
|
|
494
|
+
meta.traceId = traceIdOf(appSet) ?? undefined;
|
|
495
|
+
} else {
|
|
496
|
+
const tr = newTrace();
|
|
497
|
+
this.setRequestHeader('traceparent', tr.header);
|
|
498
|
+
meta.traceId = tr.traceId;
|
|
499
|
+
}
|
|
494
500
|
} catch {
|
|
495
501
|
/* header rejected (e.g. send already dispatched) — skip, never throw */
|
|
496
502
|
}
|
package/src/trace.ts
CHANGED
|
@@ -77,24 +77,54 @@ function hostAllowed(hostname: string, hosts: string[]): boolean {
|
|
|
77
77
|
return false;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
/**
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
/** The 32-hex trace id inside a `traceparent` value, or null if malformed. */
|
|
81
|
+
export function traceIdOf(header: string | null | undefined): string | null {
|
|
82
|
+
if (!header) return null;
|
|
83
|
+
const parts = header.split('-');
|
|
84
|
+
return parts.length >= 2 && /^[0-9a-f]{32}$/.test(parts[1]!) ? parts[1]! : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The traceparent already on this fetch call, honoring fetch's header semantics:
|
|
88
|
+
* init.headers (when present) REPLACE a Request's headers, so they win. */
|
|
89
|
+
function existingFetchTraceparent(input: RequestInfo | URL, init: RequestInit | undefined): string | null {
|
|
90
|
+
const hasInitHeaders = !!init && 'headers' in init && init.headers != null;
|
|
91
|
+
const src = hasInitHeaders
|
|
92
|
+
? (init!.headers as HeadersInit)
|
|
93
|
+
: input instanceof Request
|
|
94
|
+
? input.headers
|
|
95
|
+
: undefined;
|
|
96
|
+
if (!src) return null;
|
|
97
|
+
try {
|
|
98
|
+
return new Headers(src).get('traceparent');
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Ensure a fetch call carries a `traceparent`, and report the trace id that will
|
|
105
|
+
* actually be ON THE WIRE. If the app already set one, we DON'T overwrite it (and
|
|
106
|
+
* return ITS id, so the recorded event matches what the backend sees); otherwise
|
|
107
|
+
* we mint + inject ours. Total + side-effect-free so a caller can fall back to the
|
|
108
|
+
* originals if it throws. */
|
|
109
|
+
export function applyFetchTrace(
|
|
84
110
|
input: RequestInfo | URL,
|
|
85
111
|
init: RequestInit | undefined,
|
|
86
|
-
|
|
87
|
-
|
|
112
|
+
): { input: RequestInfo | URL; init: RequestInit | undefined; traceId: string } {
|
|
113
|
+
const existing = existingFetchTraceparent(input, init);
|
|
114
|
+
if (existing) {
|
|
115
|
+
// Respect the app's own trace context — record its id, change nothing.
|
|
116
|
+
return { input, init, traceId: traceIdOf(existing) ?? '' };
|
|
117
|
+
}
|
|
118
|
+
const tr = newTrace();
|
|
88
119
|
const hasInitHeaders = !!init && 'headers' in init && init.headers != null;
|
|
89
120
|
// fetch(Request) with no override headers → rebuild the Request so we don't drop
|
|
90
|
-
// its own headers. When init.headers IS given, fetch uses THOSE
|
|
91
|
-
// replace the Request's), so we inject there instead.
|
|
121
|
+
// its own headers. When init.headers IS given, fetch uses THOSE, so inject there.
|
|
92
122
|
if (input instanceof Request && !hasInitHeaders) {
|
|
93
123
|
const h = new Headers(input.headers);
|
|
94
|
-
|
|
95
|
-
return { input: new Request(input, { headers: h }), init };
|
|
124
|
+
h.set('traceparent', tr.header);
|
|
125
|
+
return { input: new Request(input, { headers: h }), init, traceId: tr.traceId };
|
|
96
126
|
}
|
|
97
127
|
const h = new Headers((init?.headers as HeadersInit | undefined) ?? undefined);
|
|
98
|
-
|
|
99
|
-
return { input, init: { ...(init || {}), headers: h } };
|
|
128
|
+
h.set('traceparent', tr.header);
|
|
129
|
+
return { input, init: { ...(init || {}), headers: h }, traceId: tr.traceId };
|
|
100
130
|
}
|