@observtech/rum 0.1.31 → 0.1.33
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.d.ts +27 -0
- package/dist/index.js +205 -11
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
package/dist/index.d.ts
CHANGED
|
@@ -69,6 +69,33 @@ interface ObservInitOptions {
|
|
|
69
69
|
* omit to mask all input values. See {@link PrivacyOptions}.
|
|
70
70
|
*/
|
|
71
71
|
privacy?: PrivacyOptions;
|
|
72
|
+
/**
|
|
73
|
+
* `service.name` stamped on every signal's resource. Without it spans/logs/
|
|
74
|
+
* metrics land under `unknown_service`. Set it per app (e.g. `cotevins-front`).
|
|
75
|
+
*/
|
|
76
|
+
serviceName?: string;
|
|
77
|
+
/** `service.version` resource attribute (e.g. the app build / release). */
|
|
78
|
+
serviceVersion?: string;
|
|
79
|
+
/** `deployment.environment.name` resource attribute (e.g. `prod`, `dev`). */
|
|
80
|
+
environment?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Disable the metrics pipeline (Core Web Vitals histograms + nav/error
|
|
83
|
+
* counters over OTLP/HTTP to `{endpoint}/v1/metrics`). Default: metrics ON.
|
|
84
|
+
*/
|
|
85
|
+
disableMetrics?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Propagate `session.id` to backends via the W3C `baggage` header on
|
|
88
|
+
* fetch/XHR, so server spans can stamp the session identity. The allowlist is
|
|
89
|
+
* {@link propagateTraceHeaderCorsUrls} (same backends that receive
|
|
90
|
+
* `traceparent`). Default: off. Same-origin requests are always eligible.
|
|
91
|
+
*/
|
|
92
|
+
propagateBaggage?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Head-based trace sampling ratio in `[0, 1)` (e.g. `0.2` keeps 20% of root
|
|
95
|
+
* traces) via a `ParentBasedSampler`. Omit / `>= 1` / invalid ⇒ sample all
|
|
96
|
+
* (AlwaysOn). Use under heavy traffic to bound RUM volume.
|
|
97
|
+
*/
|
|
98
|
+
sampleRate?: number;
|
|
72
99
|
}
|
|
73
100
|
/** Public SDK handle returned/exposed by the package entry point. */
|
|
74
101
|
interface ObservSdk {
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,84 @@
|
|
|
1
|
-
import { ATTR_EXCEPTION_MESSAGE, ATTR_EXCEPTION_TYPE, ATTR_EXCEPTION_STACKTRACE } from '@opentelemetry/semantic-conventions';
|
|
1
|
+
import { ATTR_EXCEPTION_MESSAGE, ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, ATTR_EXCEPTION_TYPE, ATTR_EXCEPTION_STACKTRACE } from '@opentelemetry/semantic-conventions';
|
|
2
2
|
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
|
|
3
3
|
import { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
|
|
4
|
+
import { resourceFromAttributes } from '@opentelemetry/resources';
|
|
4
5
|
import { ATTR_SESSION_ID } from '@opentelemetry/semantic-conventions/incubating';
|
|
5
|
-
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
6
6
|
import { trace, metrics } from '@opentelemetry/api';
|
|
7
|
+
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
|
|
8
|
+
import { PeriodicExportingMetricReader, MeterProvider } from '@opentelemetry/sdk-metrics';
|
|
9
|
+
import { onLCP, onINP, onFCP, onTTFB, onCLS } from 'web-vitals';
|
|
10
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
7
11
|
import { logs } from '@opentelemetry/api-logs';
|
|
12
|
+
import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load';
|
|
8
13
|
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
|
|
9
14
|
import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request';
|
|
15
|
+
import { ParentBasedSampler, TraceIdRatioBasedSampler, AlwaysOnSampler } from '@opentelemetry/sdk-trace-base';
|
|
10
16
|
import { WebTracerProvider, BatchSpanProcessor } from '@opentelemetry/sdk-trace-web';
|
|
11
17
|
import { record } from '@rrweb/record';
|
|
12
18
|
|
|
13
|
-
// src/
|
|
19
|
+
// src/baggage.ts
|
|
20
|
+
function matches(url, matchers) {
|
|
21
|
+
return matchers.some((m) => typeof m === "string" ? url.startsWith(m) : m.test(url));
|
|
22
|
+
}
|
|
23
|
+
function resolveUrl(input) {
|
|
24
|
+
if (typeof input === "string") return input;
|
|
25
|
+
if (input instanceof URL) return input.href;
|
|
26
|
+
return input.url;
|
|
27
|
+
}
|
|
28
|
+
function withSessionBaggage(existing, sessionId) {
|
|
29
|
+
const entry = `session.id=${encodeURIComponent(sessionId)}`;
|
|
30
|
+
if (!existing) return entry;
|
|
31
|
+
const hasSession = existing.split(",").some((p) => p.trim().startsWith("session.id="));
|
|
32
|
+
return hasSession ? existing : `${existing},${entry}`;
|
|
33
|
+
}
|
|
34
|
+
function installBaggagePropagation(getId, backendUrls, ignoreUrls = []) {
|
|
35
|
+
if (typeof window === "undefined" || typeof window.fetch !== "function" || backendUrls.length === 0) {
|
|
36
|
+
return () => {
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const originalFetch = window.fetch.bind(window);
|
|
40
|
+
window.fetch = function patchedFetch(input, init2) {
|
|
41
|
+
let sessionId = "";
|
|
42
|
+
let url = "";
|
|
43
|
+
try {
|
|
44
|
+
sessionId = getId();
|
|
45
|
+
url = resolveUrl(input);
|
|
46
|
+
} catch {
|
|
47
|
+
return originalFetch(input, init2);
|
|
48
|
+
}
|
|
49
|
+
if (!sessionId || !matches(url, backendUrls) || matches(url, ignoreUrls)) {
|
|
50
|
+
return originalFetch(input, init2);
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const headers = new Headers(
|
|
54
|
+
init2?.headers ?? (input instanceof Request ? input.headers : void 0)
|
|
55
|
+
);
|
|
56
|
+
headers.set("baggage", withSessionBaggage(headers.get("baggage"), sessionId));
|
|
57
|
+
return originalFetch(input, { ...init2, headers });
|
|
58
|
+
} catch {
|
|
59
|
+
return originalFetch(input, init2);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
return () => {
|
|
63
|
+
window.fetch = originalFetch;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function buildResource(options) {
|
|
67
|
+
const attrs = {
|
|
68
|
+
"telemetry.sdk.language": "webjs"
|
|
69
|
+
};
|
|
70
|
+
if (options.serviceName) attrs[ATTR_SERVICE_NAME] = options.serviceName;
|
|
71
|
+
if (options.serviceVersion) attrs[ATTR_SERVICE_VERSION] = options.serviceVersion;
|
|
72
|
+
if (options.environment) attrs["deployment.environment.name"] = options.environment;
|
|
73
|
+
try {
|
|
74
|
+
if (typeof navigator !== "undefined") {
|
|
75
|
+
if (navigator.language) attrs["browser.language"] = navigator.language;
|
|
76
|
+
attrs["browser.mobile"] = /Mobi|Android/i.test(navigator.userAgent || "");
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
80
|
+
return resourceFromAttributes(attrs);
|
|
81
|
+
}
|
|
14
82
|
var SESSION_ID_ATTRIBUTE = ATTR_SESSION_ID;
|
|
15
83
|
|
|
16
84
|
// src/session.ts
|
|
@@ -107,7 +175,10 @@ function setupOtelLogs(options) {
|
|
|
107
175
|
const headers = {};
|
|
108
176
|
if (options.key) headers["x-observ-key"] = options.key;
|
|
109
177
|
const exporter = new OTLPLogExporter({ url: buildLogsUrl(options.endpoint), headers });
|
|
110
|
-
const p = new LoggerProvider({
|
|
178
|
+
const p = new LoggerProvider({
|
|
179
|
+
resource: buildResource(options),
|
|
180
|
+
processors: [new BatchLogRecordProcessor(exporter)]
|
|
181
|
+
});
|
|
111
182
|
try {
|
|
112
183
|
const logger = p.getLogger("@observtech/rum");
|
|
113
184
|
provider = p;
|
|
@@ -228,6 +299,85 @@ function startJsErrorCapture(options) {
|
|
|
228
299
|
active = handle;
|
|
229
300
|
return handle;
|
|
230
301
|
}
|
|
302
|
+
var METRIC_EXPORT_INTERVAL_MS = 3e4;
|
|
303
|
+
var VITAL_HISTOGRAMS = {
|
|
304
|
+
LCP: { name: "web_vitals.lcp", unit: "ms", description: "Largest Contentful Paint" },
|
|
305
|
+
INP: { name: "web_vitals.inp", unit: "ms", description: "Interaction to Next Paint" },
|
|
306
|
+
FCP: { name: "web_vitals.fcp", unit: "ms", description: "First Contentful Paint" },
|
|
307
|
+
TTFB: { name: "web_vitals.ttfb", unit: "ms", description: "Time To First Byte" },
|
|
308
|
+
CLS: { name: "web_vitals.cls", unit: "1", description: "Cumulative Layout Shift" }
|
|
309
|
+
};
|
|
310
|
+
function buildMetricsUrl(endpoint) {
|
|
311
|
+
try {
|
|
312
|
+
const url = new URL(endpoint);
|
|
313
|
+
url.pathname = `${url.pathname.replace(/\/+$/, "")}/v1/metrics`;
|
|
314
|
+
url.search = "";
|
|
315
|
+
url.hash = "";
|
|
316
|
+
return url.toString();
|
|
317
|
+
} catch {
|
|
318
|
+
return `${endpoint.replace(/\/+$/, "")}/v1/metrics`;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
var provider2 = null;
|
|
322
|
+
function setupOtelMetrics(options) {
|
|
323
|
+
if (provider2) return { meter: provider2.getMeter("@observtech/rum"), stop: shutdownOtelMetrics };
|
|
324
|
+
const headers = {};
|
|
325
|
+
if (options.key) headers["x-observ-key"] = options.key;
|
|
326
|
+
const reader = new PeriodicExportingMetricReader({
|
|
327
|
+
exporter: new OTLPMetricExporter({ url: buildMetricsUrl(options.endpoint), headers }),
|
|
328
|
+
exportIntervalMillis: METRIC_EXPORT_INTERVAL_MS
|
|
329
|
+
});
|
|
330
|
+
const p = new MeterProvider({ resource: buildResource(options), readers: [reader] });
|
|
331
|
+
const meter = p.getMeter("@observtech/rum");
|
|
332
|
+
const histograms = Object.fromEntries(
|
|
333
|
+
Object.entries(VITAL_HISTOGRAMS).map(([key, h]) => [
|
|
334
|
+
key,
|
|
335
|
+
meter.createHistogram(h.name, { unit: h.unit, description: h.description })
|
|
336
|
+
])
|
|
337
|
+
);
|
|
338
|
+
let logger = null;
|
|
339
|
+
try {
|
|
340
|
+
logger = setupOtelLogs(options);
|
|
341
|
+
} catch {
|
|
342
|
+
}
|
|
343
|
+
const record2 = (m) => {
|
|
344
|
+
try {
|
|
345
|
+
const path = typeof location !== "undefined" ? location.pathname : "";
|
|
346
|
+
const dims = {
|
|
347
|
+
"web_vital.rating": m.rating,
|
|
348
|
+
"web_vital.navigation_type": m.navigationType,
|
|
349
|
+
"url.path": path
|
|
350
|
+
};
|
|
351
|
+
histograms[m.name]?.record(m.value, dims);
|
|
352
|
+
if (logger) {
|
|
353
|
+
emitSessionEvent(logger, "observ.session.web_vital", {
|
|
354
|
+
"web_vital.name": m.name,
|
|
355
|
+
"web_vital.value": String(m.value),
|
|
356
|
+
"web_vital.id": m.id,
|
|
357
|
+
"web_vital.rating": m.rating,
|
|
358
|
+
"web_vital.navigation_type": m.navigationType,
|
|
359
|
+
"url.path": path
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
} catch {
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
try {
|
|
366
|
+
onLCP(record2);
|
|
367
|
+
onINP(record2);
|
|
368
|
+
onFCP(record2);
|
|
369
|
+
onTTFB(record2);
|
|
370
|
+
onCLS(record2);
|
|
371
|
+
} catch {
|
|
372
|
+
}
|
|
373
|
+
provider2 = p;
|
|
374
|
+
return { meter, stop: shutdownOtelMetrics };
|
|
375
|
+
}
|
|
376
|
+
async function shutdownOtelMetrics() {
|
|
377
|
+
const p = provider2;
|
|
378
|
+
provider2 = null;
|
|
379
|
+
await p?.shutdown();
|
|
380
|
+
}
|
|
231
381
|
|
|
232
382
|
// node_modules/@opentelemetry/instrumentation/build/esm/autoLoaderUtils.js
|
|
233
383
|
function enableInstrumentations(instrumentations, tracerProvider, meterProvider, loggerProvider) {
|
|
@@ -262,6 +412,17 @@ function registerInstrumentations(options) {
|
|
|
262
412
|
disableInstrumentations(instrumentations);
|
|
263
413
|
};
|
|
264
414
|
}
|
|
415
|
+
function buildSampler(options) {
|
|
416
|
+
const rate = options.sampleRate;
|
|
417
|
+
if (typeof rate === "number" && Number.isFinite(rate) && rate >= 0 && rate < 1) {
|
|
418
|
+
return new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(rate) });
|
|
419
|
+
}
|
|
420
|
+
return new AlwaysOnSampler();
|
|
421
|
+
}
|
|
422
|
+
function telemetryIgnoreUrls(endpoint) {
|
|
423
|
+
const escaped = endpoint.replace(/\/+$/, "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
424
|
+
return [new RegExp(`${escaped}/v1/`)];
|
|
425
|
+
}
|
|
265
426
|
function buildTracesUrl(endpoint) {
|
|
266
427
|
try {
|
|
267
428
|
const url = new URL(endpoint);
|
|
@@ -289,11 +450,11 @@ var SessionAttributeSpanProcessor = class {
|
|
|
289
450
|
return Promise.resolve();
|
|
290
451
|
}
|
|
291
452
|
};
|
|
292
|
-
var
|
|
453
|
+
var provider3 = null;
|
|
293
454
|
var disableInstrumentations2 = null;
|
|
294
455
|
var activeConfig = null;
|
|
295
456
|
function setupOtelRum(options) {
|
|
296
|
-
if (
|
|
457
|
+
if (provider3) {
|
|
297
458
|
if (activeConfig && (activeConfig.endpoint !== options.endpoint || activeConfig.key !== options.key)) {
|
|
298
459
|
console.warn(
|
|
299
460
|
"[observ] OTel RUM already initialized; ignoring divergent endpoint/key on this init() (first call wins)."
|
|
@@ -311,7 +472,10 @@ function setupOtelRum(options) {
|
|
|
311
472
|
url: buildTracesUrl(options.endpoint),
|
|
312
473
|
headers
|
|
313
474
|
});
|
|
475
|
+
const ignoreUrls = telemetryIgnoreUrls(options.endpoint);
|
|
314
476
|
const p = new WebTracerProvider({
|
|
477
|
+
resource: buildResource(options),
|
|
478
|
+
sampler: buildSampler(options),
|
|
315
479
|
spanProcessors: [new SessionAttributeSpanProcessor(), new BatchSpanProcessor(exporter)]
|
|
316
480
|
});
|
|
317
481
|
try {
|
|
@@ -319,11 +483,15 @@ function setupOtelRum(options) {
|
|
|
319
483
|
disableInstrumentations2 = registerInstrumentations({
|
|
320
484
|
tracerProvider: p,
|
|
321
485
|
instrumentations: [
|
|
486
|
+
// Page-load timing: documentLoad / documentFetch / resourceFetch spans.
|
|
487
|
+
new DocumentLoadInstrumentation(),
|
|
322
488
|
new FetchInstrumentation({
|
|
323
|
-
propagateTraceHeaderCorsUrls: options.propagateTraceHeaderCorsUrls
|
|
489
|
+
propagateTraceHeaderCorsUrls: options.propagateTraceHeaderCorsUrls,
|
|
490
|
+
ignoreUrls
|
|
324
491
|
}),
|
|
325
492
|
new XMLHttpRequestInstrumentation({
|
|
326
|
-
propagateTraceHeaderCorsUrls: options.propagateTraceHeaderCorsUrls
|
|
493
|
+
propagateTraceHeaderCorsUrls: options.propagateTraceHeaderCorsUrls,
|
|
494
|
+
ignoreUrls
|
|
327
495
|
})
|
|
328
496
|
]
|
|
329
497
|
});
|
|
@@ -333,16 +501,16 @@ function setupOtelRum(options) {
|
|
|
333
501
|
void p.shutdown();
|
|
334
502
|
throw err;
|
|
335
503
|
}
|
|
336
|
-
|
|
504
|
+
provider3 = p;
|
|
337
505
|
activeConfig = { endpoint: options.endpoint, key: options.key };
|
|
338
506
|
}
|
|
339
507
|
async function shutdownOtelRum() {
|
|
340
|
-
const p =
|
|
508
|
+
const p = provider3;
|
|
341
509
|
try {
|
|
342
510
|
disableInstrumentations2?.();
|
|
343
511
|
} finally {
|
|
344
512
|
disableInstrumentations2 = null;
|
|
345
|
-
|
|
513
|
+
provider3 = null;
|
|
346
514
|
activeConfig = null;
|
|
347
515
|
await p?.shutdown();
|
|
348
516
|
}
|
|
@@ -622,6 +790,8 @@ function startSemanticEvents(options) {
|
|
|
622
790
|
var replayRecorder = null;
|
|
623
791
|
var semanticEvents = null;
|
|
624
792
|
var jsErrors = null;
|
|
793
|
+
var metrics2 = null;
|
|
794
|
+
var baggageUninstall = null;
|
|
625
795
|
function init(options) {
|
|
626
796
|
try {
|
|
627
797
|
ensureSession();
|
|
@@ -635,6 +805,18 @@ function init(options) {
|
|
|
635
805
|
if (!jsErrors) {
|
|
636
806
|
jsErrors = startJsErrorCapture(options);
|
|
637
807
|
}
|
|
808
|
+
if (!options.disableMetrics && !metrics2) {
|
|
809
|
+
metrics2 = setupOtelMetrics(options);
|
|
810
|
+
}
|
|
811
|
+
if (options.propagateBaggage && !baggageUninstall) {
|
|
812
|
+
const backends = [...options.propagateTraceHeaderCorsUrls ?? []];
|
|
813
|
+
if (typeof location !== "undefined") backends.push(location.origin);
|
|
814
|
+
baggageUninstall = installBaggagePropagation(
|
|
815
|
+
getSessionId,
|
|
816
|
+
backends,
|
|
817
|
+
telemetryIgnoreUrls(options.endpoint)
|
|
818
|
+
);
|
|
819
|
+
}
|
|
638
820
|
} catch (err) {
|
|
639
821
|
console.warn("[observ] RUM setup failed; tracing/replay/events degraded.", err);
|
|
640
822
|
}
|
|
@@ -643,9 +825,17 @@ async function shutdown() {
|
|
|
643
825
|
const recorder = replayRecorder;
|
|
644
826
|
const events = semanticEvents;
|
|
645
827
|
const errors = jsErrors;
|
|
828
|
+
const metricsHandle = metrics2;
|
|
829
|
+
const uninstallBaggage = baggageUninstall;
|
|
646
830
|
replayRecorder = null;
|
|
647
831
|
semanticEvents = null;
|
|
648
832
|
jsErrors = null;
|
|
833
|
+
metrics2 = null;
|
|
834
|
+
baggageUninstall = null;
|
|
835
|
+
try {
|
|
836
|
+
uninstallBaggage?.();
|
|
837
|
+
} catch {
|
|
838
|
+
}
|
|
649
839
|
try {
|
|
650
840
|
await recorder?.stop();
|
|
651
841
|
} catch {
|
|
@@ -658,6 +848,10 @@ async function shutdown() {
|
|
|
658
848
|
errors?.stop();
|
|
659
849
|
} catch {
|
|
660
850
|
}
|
|
851
|
+
try {
|
|
852
|
+
await metricsHandle?.stop();
|
|
853
|
+
} catch {
|
|
854
|
+
}
|
|
661
855
|
try {
|
|
662
856
|
await shutdownOtelLogs();
|
|
663
857
|
} catch {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/constants.ts","../src/session.ts","../src/otel-logs.ts","../src/js-errors.ts","../node_modules/@opentelemetry/instrumentation/src/autoLoaderUtils.ts","../node_modules/@opentelemetry/instrumentation/src/autoLoader.ts","../src/otel-rum.ts","../src/privacy.ts","../src/transport.ts","../src/rrweb-record.ts","../src/selector.ts","../src/semantic-events.ts","../src/index.ts"],"names":["provider","disableInstrumentations","current","active","NOOP"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Cross-cutting constants for the Observ RUM SDK.\n *\n * Kept in a leaf module (no SDK imports beyond semconv constants) so both the\n * public entry point (`index.ts`) and internal modules (`otel-rum.ts`) can share\n * them without a circular import.\n */\nimport { ATTR_SESSION_ID } from '@opentelemetry/semantic-conventions/incubating'\n\n/**\n * Attribute key used everywhere to correlate signals of a single session.\n * Sourced from the pinned `@opentelemetry/semantic-conventions` (incubating\n * `ATTR_SESSION_ID`) rather than a hand-typed literal, so the key tracks the\n * standard. Enforcement rule (architecture): the OTel session attribute is\n * EXACTLY `session.id` — never `sessionId` nor `session_id`. Single source of\n * truth so every signal (spans, semantic events, rrweb) stamps the identical key.\n * A test asserts `=== 'session.id'` to guard that invariant across semconv bumps.\n */\nexport const SESSION_ID_ATTRIBUTE: 'session.id' = ATTR_SESSION_ID\n","/**\n * Session lifecycle for the Observ RUM SDK (FR-1).\n *\n * Produces a single, stable `session.id` per visit and exposes it INTERNALLY as\n * the one source of truth that later modules (`rrweb-record`, `semantic-events`,\n * `otel-rum`) stamp onto every signal. Not part of the public `observ` surface.\n *\n * Authoritative rules (architecture D5):\n * - `session.id` is a UUID v4, generated in the browser,\n * - persisted in `sessionStorage` (per-tab — two tabs are two visits, by design),\n * - rotated after 30 min of inactivity.\n *\n * Rotation is LAZY: there is no background timer — the window is evaluated on the\n * next `ensureSession`/`getSessionId`/`touchSession` call, and a fresh id is\n * minted then if the window has elapsed.\n *\n * This module performs NO network I/O. It must never throw: if `sessionStorage`\n * or `crypto` are unavailable (private mode, disabled storage, SSR / insecure\n * context), it degrades to an in-memory session.id. NOTE: in that degraded mode\n * persistence is lost across full page loads, so each navigation starts a new\n * session (the per-visit guarantee holds only while `sessionStorage` works).\n */\n\n/** Single `sessionStorage` key holding the serialized session state. */\nexport const SESSION_STORAGE_KEY = 'observ.rum.session'\n\n/**\n * Inactivity window after which a new `session.id` is minted (architecture D5,\n * resolves PRD Open Q2). 30 minutes, in milliseconds.\n */\nexport const SESSION_INACTIVITY_TIMEOUT_MS = 30 * 60 * 1000\n\n/** Shape persisted under {@link SESSION_STORAGE_KEY}. Timestamps are epoch ms. */\ninterface PersistedSession {\n id: string\n startedAt: number\n lastActivityAt: number\n}\n\n/**\n * In-memory memoization. Within a single page it is the source of truth (one\n * `init` per SPA load); it is also the sole fallback when storage is unusable.\n */\nlet current: PersistedSession | null = null\n\n/** Build a UUID v4, preferring crypto; falls back gracefully (documented). */\nfunction generateSessionId(): string {\n const c: Crypto | undefined = globalThis.crypto\n // `crypto.randomUUID` requires a secure context (https or localhost). Detection\n // tests presence, not callability — guard the call so a throwing implementation\n // falls through instead of escaping (the module must never throw).\n if (c && typeof c.randomUUID === 'function') {\n try {\n return c.randomUUID()\n } catch {\n // fall through to getRandomValues\n }\n }\n if (c && typeof c.getRandomValues === 'function') {\n const bytes = c.getRandomValues(new Uint8Array(16))\n bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x40 // version 4\n bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80 // variant 10\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`\n }\n // Last-resort, non-crypto fallback (SSR / no Web Crypto). Not cryptographically\n // strong — acceptable only because no real entropy source exists here.\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (ch) => {\n const r = (Math.random() * 16) | 0\n const v = ch === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\n/** Read from `sessionStorage`, swallowing any access error. */\nfunction safeRead(): string | null {\n try {\n return globalThis.sessionStorage?.getItem(SESSION_STORAGE_KEY) ?? null\n } catch {\n return null\n }\n}\n\n/** Write to `sessionStorage`, swallowing any access/quota error. */\nfunction safeWrite(value: string): void {\n try {\n globalThis.sessionStorage?.setItem(SESSION_STORAGE_KEY, value)\n } catch {\n // In-memory `current` stays authoritative; nothing else to do.\n }\n}\n\n/** Validate the parsed storage payload before trusting it. */\nfunction isPersistedSession(value: unknown): value is PersistedSession {\n if (typeof value !== 'object' || value === null) return false\n const v = value as Record<string, unknown>\n return (\n typeof v.id === 'string' &&\n v.id.length > 0 &&\n typeof v.startedAt === 'number' &&\n Number.isFinite(v.startedAt) &&\n typeof v.lastActivityAt === 'number' &&\n Number.isFinite(v.lastActivityAt)\n )\n}\n\n/** Resolve the current session, from memory first, then storage. */\nfunction load(): PersistedSession | null {\n if (current) return current\n const raw = safeRead()\n if (raw === null) return null\n try {\n const parsed: unknown = JSON.parse(raw)\n return isPersistedSession(parsed) ? parsed : null\n } catch {\n return null // corrupt JSON → treat as absent\n }\n}\n\n/** Memoize in memory and best-effort persist to storage. */\nfunction save(session: PersistedSession): void {\n current = session\n safeWrite(JSON.stringify(session))\n}\n\n/**\n * Ensure a `session.id` exists for this visit and return it.\n *\n * Reuses the stored id when within the inactivity window (refreshing\n * `lastActivityAt`); otherwise mints a fresh session. `now` is injectable for\n * deterministic tests.\n */\nexport function ensureSession(now: number = Date.now()): string {\n const existing = load()\n if (existing) {\n // Clamp to 0 so a backward clock jump (now < lastActivityAt, e.g. NTP/DST)\n // can't yield a negative \"elapsed\" that silently extends the session; the\n // stale future timestamp is then corrected to `now` below.\n const elapsed = Math.max(0, now - existing.lastActivityAt)\n if (elapsed <= SESSION_INACTIVITY_TIMEOUT_MS) {\n save({ ...existing, lastActivityAt: now })\n return existing.id\n }\n }\n const fresh: PersistedSession = { id: generateSessionId(), startedAt: now, lastActivityAt: now }\n save(fresh)\n return fresh.id\n}\n\n/**\n * Return the `session.id` for the current visit.\n *\n * Delegates to {@link ensureSession} so every read enforces the inactivity\n * timeout (rotating to a fresh id once the window has elapsed) — a memoized\n * read must not be able to hand back a session that should have rotated. As a\n * consequence a read also refreshes `lastActivityAt`, i.e. reading the id counts\n * as activity.\n */\nexport function getSessionId(now: number = Date.now()): string {\n return ensureSession(now)\n}\n\n/**\n * Push the inactivity window forward on real activity (rotating if the session\n * has already expired). Wiring to actual interactions arrives in stories 2.x;\n * this is the hook + the write.\n */\nexport function touchSession(now: number = Date.now()): void {\n ensureSession(now)\n}\n\n/**\n * @internal Test-only. Clears the in-memory memoization to simulate a fresh\n * page load / module re-import (storage is left untouched).\n */\nexport function resetSessionState(): void {\n current = null\n}\n","/**\n * OpenTelemetry Logs pipeline for the Observ RUM SDK (story 2.3).\n *\n * Semantic events (`observ.session.*`) are emitted as OTel **log records** and\n * exported over OTLP/HTTP to `{endpoint}/v1/logs` (received by the backend since\n * story 1.4; the session-assembly writer taps them in 1.6). This is the mirror\n * of the trace pipeline in `otel-rum.ts`, on a dedicated, separate provider —\n * the light \"logs\" channel, never the heavy `/v1/replay` channel.\n *\n * `session.id` is stamped once, at emit time, by {@link emitSessionEvent} — the\n * single point through which every semantic event flows (so a separate\n * `LogRecordProcessor` would be redundant; this also keeps the SDK's\n * sdk-logs API surface minimal).\n */\nimport type { Logger } from '@opentelemetry/api-logs'\nimport { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'\nimport { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs'\n\nimport { SESSION_ID_ATTRIBUTE } from './constants.js'\nimport { getSessionId } from './session.js'\nimport type { ObservInitOptions } from './types.js'\n\n/**\n * Join an endpoint base URL with the OTLP/HTTP logs path. Mirror of\n * {@link import('./otel-rum.js').buildTracesUrl}: preserves a path prefix, never\n * doubles a trailing slash, drops query/fragment; relative/empty → same-origin.\n */\nexport function buildLogsUrl(endpoint: string): string {\n try {\n const url = new URL(endpoint)\n url.pathname = `${url.pathname.replace(/\\/+$/, '')}/v1/logs`\n url.search = ''\n url.hash = ''\n return url.toString()\n } catch {\n return `${endpoint.replace(/\\/+$/, '')}/v1/logs`\n }\n}\n\nlet provider: LoggerProvider | null = null\nlet activeLogger: Logger | null = null\n\n/**\n * Wire up the OTel Logs provider and return a `Logger`. Idempotent: a second\n * call returns the already-active logger (first init wins, mirrors\n * `setupOtelRum`). The `x-observ-key` header is omitted when the key is empty\n * (the trace setup already warns about a missing key).\n */\nexport function setupOtelLogs(options: ObservInitOptions): Logger {\n if (activeLogger) return activeLogger\n\n const headers: Record<string, string> = {}\n if (options.key) headers['x-observ-key'] = options.key\n\n const exporter = new OTLPLogExporter({ url: buildLogsUrl(options.endpoint), headers })\n const p = new LoggerProvider({ processors: [new BatchLogRecordProcessor(exporter)] })\n\n try {\n const logger = p.getLogger('@observtech/rum')\n // Commit the module state ONLY once everything succeeded — otherwise a\n // partial init would leak the provider's flush timer and latch the guard.\n provider = p\n activeLogger = logger\n return logger\n } catch (err) {\n // Roll back a partial init (mirrors otel-rum.ts): shut the provider down so\n // its BatchLogRecordProcessor timer can't outlive the failure, and leave the\n // guards null so a later setup can retry.\n void p.shutdown()\n throw err\n }\n}\n\n/** Inactivity refresh throttle for the cached session id (story 1.2 — avoid a\n * synchronous sessionStorage write on every event). */\nconst SESSION_ID_REFRESH_MS = 1_000\nlet cachedSessionId = ''\nlet cachedSessionIdAt = 0\n\n/**\n * Resolve the visit `session.id`, refreshing it (and the session's inactivity\n * window) at most once per second. A click burst therefore costs ≤ 1\n * `getSessionId` (→ ≤ 1 sessionStorage write) per second instead of one per\n * event, while still counting interaction as session activity.\n */\nfunction currentSessionId(): string {\n const now = Date.now()\n if (!cachedSessionId || now - cachedSessionIdAt >= SESSION_ID_REFRESH_MS) {\n cachedSessionId = getSessionId()\n cachedSessionIdAt = now\n }\n return cachedSessionId\n}\n\n/**\n * Emit one semantic event as an OTel log record: `event.name` in the\n * `observ.session.*` namespace + the exact `session.id` correlation attribute\n * (throttled refresh, so an event read also counts as session activity, 1.2) +\n * the caller's attributes. Best-effort: never throws to the host page.\n */\nexport function emitSessionEvent(\n logger: Logger,\n eventName: string,\n attrs: Record<string, string> = {},\n): void {\n try {\n logger.emit({\n attributes: {\n 'event.name': eventName,\n [SESSION_ID_ATTRIBUTE]: currentSessionId(),\n ...attrs,\n },\n })\n } catch (err) {\n console.warn('[observ] semantic event emit failed', err)\n }\n}\n\n/**\n * @internal Flush + shut the logs provider down and reset the idempotence guard\n * so a later setup can re-init. Called by the SDK teardown.\n */\nexport async function shutdownOtelLogs(): Promise<void> {\n const p = provider\n provider = null\n activeLogger = null\n await p?.shutdown()\n}\n\n/** @internal Test-only: the active logger, or `null` when logs are not set up. */\nexport function getActiveLogger(): Logger | null {\n return activeLogger\n}\n","/**\n * JS error capture for the Observ RUM SDK (FR-9, story 2.4).\n *\n * Captures uncaught errors (`window` `error`) and unhandled promise rejections\n * (`unhandledrejection`) and emits them as OTel log records\n * (`observ.session.js_error`) on the SAME logs channel as the semantic events\n * (story 2.3): standard exception semconv attributes + `session.id`. The stack\n * trace is carried as a log/event attribute — never a metric (architecture\n * enforcement).\n *\n * Invariant (1.1/1.2/1.3): never break the host page. Handlers run when the\n * browser is already handling an error, so each is `try/catch`-guarded and we\n * NEVER `preventDefault()` — the site's own error flow (console, other handlers)\n * is left untouched; we only observe.\n */\nimport type { Logger } from '@opentelemetry/api-logs'\nimport {\n ATTR_EXCEPTION_MESSAGE,\n ATTR_EXCEPTION_STACKTRACE,\n ATTR_EXCEPTION_TYPE,\n} from '@opentelemetry/semantic-conventions'\n\nimport { emitSessionEvent, setupOtelLogs } from './otel-logs.js'\nimport type { ObservInitOptions } from './types.js'\n\n/** Max `js_error` events per rolling window — bounds an error storm (AC#5)\n * without going permanently blind: the window resets, so capture self-heals. */\nexport const MAX_JS_ERRORS = 50\n/** Rolling window (ms) for the {@link MAX_JS_ERRORS} rate limit. */\nexport const RATE_WINDOW_MS = 60_000\n/** Truncation bounds so one error can't produce a huge log record. */\nconst MAX_MESSAGE = 1_024\nconst MAX_STACK = 4_096\n\n/** Best-effort string for a rejection reason: Error→message handled by the\n * caller; objects → JSON (so `{code:500}` isn't flattened to \"[object Object]\");\n * everything else → `String`. */\nfunction reasonToMessage(reason: unknown): string {\n if (typeof reason === 'string') return reason\n if (typeof reason === 'object' && reason !== null) {\n try {\n return JSON.stringify(reason) ?? String(reason)\n } catch {\n return String(reason)\n }\n }\n return String(reason)\n}\n\n/** Public handle for the running JS-error capture. */\nexport interface JsErrorHandle {\n /** Remove the global error listeners. Idempotent. The shared logs provider is\n * shut down by `index.shutdown()`, NOT here (two taps share it). */\n stop(): void\n}\n\nconst NOOP: JsErrorHandle = { stop: () => {} }\n\n/** Module-level guard so the capture is self-idempotent. */\nlet active: JsErrorHandle | null = null\n\nfunction truncate(s: string, max: number): string {\n return s.length > max ? s.slice(0, max) : s\n}\n\n/**\n * Start capturing uncaught JS errors + unhandled rejections. Returns a handle\n * whose `stop()` removes the listeners. Never throws; no-op in a non-DOM env.\n * Not gated by `disableReplay` — errors are useful even without replay.\n */\nexport function startJsErrorCapture(options: ObservInitOptions): JsErrorHandle {\n if (active) return active\n if (typeof window === 'undefined') return NOOP\n\n let logger: Logger\n try {\n logger = setupOtelLogs(options) // shared, idempotent — same logger as 2.3\n } catch (err) {\n console.warn('[observ] js-errors: logs setup failed', err)\n return NOOP\n }\n\n // Sliding-window rate limit: at most MAX_JS_ERRORS per RATE_WINDOW_MS. Resets\n // each window so a burst at boot doesn't blind capture for the rest of a long\n // SPA session.\n let windowStart = 0\n let inWindow = 0\n\n const emit = (type: string | undefined, message: string, stack: string | undefined): void => {\n const now = Date.now()\n if (now - windowStart >= RATE_WINDOW_MS) {\n windowStart = now\n inWindow = 0\n }\n if (inWindow >= MAX_JS_ERRORS) return // anti-flood cap (AC#5)\n inWindow++\n const attrs: Record<string, string> = {\n [ATTR_EXCEPTION_MESSAGE]: truncate(message, MAX_MESSAGE),\n }\n if (type) attrs[ATTR_EXCEPTION_TYPE] = type\n if (stack) attrs[ATTR_EXCEPTION_STACKTRACE] = truncate(stack, MAX_STACK)\n emitSessionEvent(logger, 'observ.session.js_error', attrs)\n }\n\n const onError = (e: ErrorEvent): void => {\n try {\n // `e.error` is the Error in modern browsers; cross-origin \"Script error.\"\n // has `e.error === null`, so fall back to `e.message` with no stack.\n const err = e.error as Error | null | undefined\n emit(err?.name, err?.message ?? e.message ?? 'unknown error', err?.stack ?? undefined)\n } catch {\n /* never let the error handler break the host page */\n }\n }\n\n const onRejection = (e: PromiseRejectionEvent): void => {\n try {\n const reason: unknown = e.reason\n if (reason instanceof Error) {\n emit(reason.name, reason.message, reason.stack ?? undefined)\n } else {\n emit('UnhandledRejection', reasonToMessage(reason), undefined)\n }\n } catch {\n /* swallow — observe, never interfere */\n }\n }\n\n // Do NOT use `window.onerror =` (would clobber a host handler). Never call\n // preventDefault — we observe without suppressing the site's error flow.\n window.addEventListener('error', onError)\n window.addEventListener('unhandledrejection', onRejection)\n\n const handle: JsErrorHandle = {\n stop(): void {\n window.removeEventListener('error', onError)\n window.removeEventListener('unhandledrejection', onRejection)\n active = null\n },\n }\n active = handle\n return handle\n}\n","/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { TracerProvider, MeterProvider } from '@opentelemetry/api';\nimport type { Instrumentation } from './types';\nimport type { LoggerProvider } from '@opentelemetry/api-logs';\n\n/**\n * Enable instrumentations\n * @param instrumentations\n * @param tracerProvider\n * @param meterProvider\n */\nexport function enableInstrumentations(\n instrumentations: Instrumentation[],\n tracerProvider?: TracerProvider,\n meterProvider?: MeterProvider,\n loggerProvider?: LoggerProvider\n): void {\n for (let i = 0, j = instrumentations.length; i < j; i++) {\n const instrumentation = instrumentations[i];\n if (tracerProvider) {\n instrumentation.setTracerProvider(tracerProvider);\n }\n if (meterProvider) {\n instrumentation.setMeterProvider(meterProvider);\n }\n if (loggerProvider && instrumentation.setLoggerProvider) {\n instrumentation.setLoggerProvider(loggerProvider);\n }\n // instrumentations have been already enabled during creation\n // so enable only if user prevented that by setting enabled to false\n // this is to prevent double enabling but when calling register all\n // instrumentations should be now enabled\n if (!instrumentation.getConfig().enabled) {\n instrumentation.enable();\n }\n }\n}\n\n/**\n * Disable instrumentations\n * @param instrumentations\n */\nexport function disableInstrumentations(\n instrumentations: Instrumentation[]\n): void {\n instrumentations.forEach(instrumentation => instrumentation.disable());\n}\n","/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { trace, metrics } from '@opentelemetry/api';\nimport { logs } from '@opentelemetry/api-logs';\nimport {\n disableInstrumentations,\n enableInstrumentations,\n} from './autoLoaderUtils';\nimport type { AutoLoaderOptions } from './types_internal';\n\n/**\n * It will register instrumentations and plugins\n * @param options\n * @return returns function to unload instrumentation and plugins that were\n * registered\n */\nexport function registerInstrumentations(\n options: AutoLoaderOptions\n): () => void {\n const tracerProvider = options.tracerProvider || trace.getTracerProvider();\n const meterProvider = options.meterProvider || metrics.getMeterProvider();\n const loggerProvider = options.loggerProvider || logs.getLoggerProvider();\n const instrumentations = options.instrumentations?.flat() ?? [];\n\n enableInstrumentations(\n instrumentations,\n tracerProvider,\n meterProvider,\n loggerProvider\n );\n\n return () => {\n disableInstrumentations(instrumentations);\n };\n}\n","/**\n * OpenTelemetry RUM wiring for the Observ SDK (FR-4, story 1.3).\n *\n * Sets up a {@link WebTracerProvider} that:\n * - creates `http.client` spans for browser `fetch`/XHR (auto-instrumentation),\n * - stamps every span with the visit's `session.id` (via {@link SessionAttributeSpanProcessor}),\n * - injects the W3C `traceparent` header on outgoing requests (default\n * trace-context propagator; cross-origin gated by `propagateTraceHeaderCorsUrls`),\n * - exports spans over OTLP/HTTP to `{endpoint}/v1/traces` (BatchSpanProcessor).\n *\n * Like the rest of the SDK, this must never throw out to the host page: the\n * caller ({@link import('./index.js')}) wraps {@link setupOtelRum} defensively.\n *\n * NOTE: until the backend OTLP/HTTP receiver lands (story 1.4), exports will fail\n * at the network layer (CORS / 404). That is expected — span creation, attribute\n * stamping and header propagation are fully exercised regardless.\n */\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport { registerInstrumentations } from '@opentelemetry/instrumentation'\nimport { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'\nimport { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'\nimport {\n BatchSpanProcessor,\n WebTracerProvider,\n type ReadableSpan,\n type Span,\n type SpanProcessor,\n} from '@opentelemetry/sdk-trace-web'\n\nimport { SESSION_ID_ATTRIBUTE } from './constants.js'\nimport { getSessionId } from './session.js'\nimport type { ObservInitOptions } from './types.js'\n\n/**\n * Join an endpoint base URL with the OTLP/HTTP traces path. Uses the `URL` API\n * for absolute endpoints so a path prefix is preserved, a trailing slash never\n * doubles, and any query/fragment (meaningless on the traces endpoint) is\n * dropped — `https://x?a=1` ⇒ `https://x/v1/traces`, not `https://x?a=1/v1/traces`.\n * A relative or empty endpoint falls back to a string join, yielding a\n * same-origin `…/v1/traces` path.\n */\nexport function buildTracesUrl(endpoint: string): string {\n try {\n const url = new URL(endpoint)\n url.pathname = `${url.pathname.replace(/\\/+$/, '')}/v1/traces`\n url.search = ''\n url.hash = ''\n return url.toString()\n } catch {\n return `${endpoint.replace(/\\/+$/, '')}/v1/traces`\n }\n}\n\n/**\n * Span processor whose only job is to stamp `session.id` on every span at start.\n *\n * Using a processor (rather than per-instrumentation `applyCustomAttributesOnSpan`)\n * guarantees ALL spans — fetch, XHR, and any future document-load — carry the\n * attribute through a single code path. It reads the id via {@link getSessionId},\n * which also refreshes the session's inactivity window (story 1.2 decision): a\n * span therefore counts as session activity.\n */\nexport class SessionAttributeSpanProcessor implements SpanProcessor {\n onStart(span: Span): void {\n // onStart runs synchronously inside the host page's own fetch/XHR. The SDK\n // must never throw out to the page, so guard here even though getSessionId()\n // is contractually no-throw: a missing attribute beats a broken host request.\n try {\n span.setAttribute(SESSION_ID_ATTRIBUTE, getSessionId())\n } catch {\n /* swallow — never let span stamping break a host request */\n }\n }\n onEnd(_span: ReadableSpan): void {}\n forceFlush(): Promise<void> {\n return Promise.resolve()\n }\n shutdown(): Promise<void> {\n return Promise.resolve()\n }\n}\n\nlet provider: WebTracerProvider | null = null\nlet disableInstrumentations: (() => void) | null = null\nlet activeConfig: { endpoint: string; key: string } | null = null\n\n/**\n * Wire up OTel RUM tracing. Idempotent: a second call is a no-op while a provider\n * is already active (mirrors the single-`init` contract; first call wins). Not\n * defensive on its own — `init` owns the try/catch so a setup failure never\n * escapes to the page — but it cleans up after itself: a partial init (register\n * or instrumentation throwing, e.g. SSR / no `fetch`/XHR) shuts the provider down\n * and resets the guard before rethrowing, so no `WebTracerProvider`\n * (BatchSpanProcessor timer + global registration) leaks and a later retry works.\n */\nexport function setupOtelRum(options: ObservInitOptions): void {\n if (provider) {\n // First init wins. Warn if a later call diverges (key rotation / env switch)\n // so the dropped config isn't silently swallowed by the idempotence guard.\n if (\n activeConfig &&\n (activeConfig.endpoint !== options.endpoint || activeConfig.key !== options.key)\n ) {\n console.warn(\n '[observ] OTel RUM already initialized; ignoring divergent endpoint/key on this init() (first call wins).',\n )\n }\n return\n }\n\n // API-key auth via the custom `x-observ-key` header — the receiver (story 1.4)\n // must decode the same name and list it in `Access-Control-Allow-Headers`. An\n // empty key would produce a malformed credential, so omit the header (and warn)\n // rather than send a blank one.\n const headers: Record<string, string> = {}\n if (options.key) {\n headers['x-observ-key'] = options.key\n } else {\n console.warn('[observ] no API key provided; front-end spans will export unauthenticated.')\n }\n\n const exporter = new OTLPTraceExporter({\n url: buildTracesUrl(options.endpoint),\n headers,\n })\n\n // SDK 2.x: span processors are constructor-only (`addSpanProcessor` was removed).\n // Order matters — stamp `session.id` before the batch processor exports.\n const p = new WebTracerProvider({\n spanProcessors: [new SessionAttributeSpanProcessor(), new BatchSpanProcessor(exporter)],\n })\n\n try {\n // Default registration: W3C trace-context propagator + web StackContextManager.\n p.register()\n disableInstrumentations = registerInstrumentations({\n tracerProvider: p,\n instrumentations: [\n new FetchInstrumentation({\n propagateTraceHeaderCorsUrls: options.propagateTraceHeaderCorsUrls,\n }),\n new XMLHttpRequestInstrumentation({\n propagateTraceHeaderCorsUrls: options.propagateTraceHeaderCorsUrls,\n }),\n ],\n })\n } catch (err) {\n // Roll back a partial init so nothing leaks and the guard doesn't latch.\n disableInstrumentations?.()\n disableInstrumentations = null\n void p.shutdown()\n throw err\n }\n\n provider = p\n activeConfig = { endpoint: options.endpoint, key: options.key }\n}\n\n/**\n * @internal Test-only. The active provider, or `null` when tracing is not set\n * up. Lets tests assert idempotence (same instance after a second `setupOtelRum`).\n */\nexport function getActiveProvider(): WebTracerProvider | null {\n return provider\n}\n\n/**\n * @internal Tear down instrumentations and the provider, resetting the\n * idempotence guard. Used by tests (and any future explicit teardown). Not on\n * the public {@link import('./types.js').ObservSdk} surface.\n */\nexport async function shutdownOtelRum(): Promise<void> {\n const p = provider\n // Atomic: even if disabling instrumentations throws, still null the guard and\n // shut the provider down so the BatchSpanProcessor timer can't outlive teardown.\n try {\n disableInstrumentations?.()\n } finally {\n disableInstrumentations = null\n provider = null\n activeConfig = null\n await p?.shutdown()\n }\n}\n","/**\n * PII masking / privacy controls for the heavy rrweb replay flux\n * (Epic 4 — Confidentialité, story 4.1).\n *\n * The heavy replay records the live DOM. By default rrweb captures the *values*\n * users type into inputs (passwords aside, which it already masks) and the\n * visible text. On any real-data surface that is PII — architecture decision D7\n * and PRD Open Q5 require masking to be enabled **before** any real data flows.\n * This module is that gate.\n *\n * SAFE-BY-DEFAULT: with no `privacy` config at all, ALL input values are masked\n * in the replay (`maskAllInputs: true`). A surface that is known to be free of\n * sensitive input can opt out with `privacy: { maskAllInputs: false }`.\n *\n * The defaults map the SDK's small `PrivacyOptions` bag onto rrweb's native\n * masking knobs, and expose three conventional CSS classes so a host page can\n * mark sensitive nodes declaratively without writing custom functions:\n * - `observ-mask` → mask the TEXT of the element (rrweb `maskTextClass`)\n * - `observ-block` → drop the element from the replay entirely (`blockClass`)\n * - `observ-ignore` → record the element but ignore its user input (`ignoreClass`)\n *\n * Channel scope: this governs the HEAVY flux only (rrweb → `/v1/replay`). Masking\n * the LIGHT semantic-event text (`element.text` on `observ.session.click`) is a\n * separate concern handled by a later story (4.2); the two channels are never\n * crossed (enforcement rule).\n */\nimport type { PrivacyOptions } from './types.js'\n\n/** Default class marking elements whose TEXT is masked in the replay (rrweb `maskTextClass`). */\nexport const DEFAULT_MASK_TEXT_CLASS = 'observ-mask'\n/** Default class marking elements dropped entirely from the replay (rrweb `blockClass`). */\nexport const DEFAULT_BLOCK_CLASS = 'observ-block'\n/** Default class marking inputs whose user input events are ignored (rrweb `ignoreClass`). */\nexport const DEFAULT_IGNORE_CLASS = 'observ-ignore'\n\n/**\n * The subset of rrweb `recordOptions` that govern masking. Intentionally a flat,\n * fully-resolved shape (no `undefined` for the always-present fields) so the\n * recorder can spread it straight into `record({ ... })` and tests can assert it.\n */\nexport interface ReplayMaskingOptions {\n /** Mask the value of every `<input>`/`<textarea>`/`<select>` in the replay. */\n maskAllInputs: boolean\n /** Mask the text of elements carrying this class. */\n maskTextClass: string\n /** Drop elements carrying this class from the replay. */\n blockClass: string\n /** Ignore user input on elements carrying this class. */\n ignoreClass: string\n /**\n * When `maskAllText` is on, a selector matching every element so rrweb masks\n * ALL text node content. Omitted otherwise (only class-scoped text masking).\n */\n maskTextSelector?: string\n}\n\n/**\n * Resolve the public {@link PrivacyOptions} into the rrweb masking options,\n * applying the safe-by-default policy. Pure (no DOM/global access) so it is\n * trivially unit-testable and reusable from `init()`.\n */\nexport function resolveReplayMasking(privacy?: PrivacyOptions): ReplayMaskingOptions {\n const resolved: ReplayMaskingOptions = {\n // Safe-by-default: inputs are masked unless the host *explicitly* opts out.\n maskAllInputs: privacy?.maskAllInputs ?? true,\n maskTextClass: privacy?.maskTextClass ?? DEFAULT_MASK_TEXT_CLASS,\n blockClass: privacy?.blockClass ?? DEFAULT_BLOCK_CLASS,\n ignoreClass: privacy?.ignoreClass ?? DEFAULT_IGNORE_CLASS,\n }\n // Full-text masking is opt-in: it heavily degrades replay usefulness, so it is\n // off by default and only class-scoped text masking applies. '*' tells rrweb\n // to treat every element as a masked-text element.\n if (privacy?.maskAllText) resolved.maskTextSelector = '*'\n return resolved\n}\n","/**\n * Replay chunk transport for the Observ RUM SDK (FR-2/FR-5, story 2.2).\n *\n * The heavy rrweb flux travels on its OWN channel — `POST /v1/replay` — never\n * through the OTLP exporter (architecture: the two channels are never crossed).\n * The body is the raw gzip bytes of a `.jsonl.gz` chunk; the backend (story 2.1)\n * stores it verbatim and rejects an empty body (400), so we never POST one.\n *\n * Contract frozen by story 2.1:\n * POST {endpoint}/v1/replay?session_id=<id>&seq=<n>\n * headers: x-observ-key: <key>, content-type: application/octet-stream\n * body: gzip bytes (≤ 16 MiB)\n */\n\n/**\n * Join an endpoint base URL with the `/v1/replay` path. Mirror of\n * {@link import('./otel-rum.js').buildTracesUrl}: the `URL` API preserves a path\n * prefix, never doubles a trailing slash, and drops any query/fragment; a\n * relative or empty endpoint falls back to a same-origin path.\n */\nexport function buildReplayUrl(endpoint: string): string {\n try {\n const url = new URL(endpoint)\n url.pathname = `${url.pathname.replace(/\\/+$/, '')}/v1/replay`\n url.search = ''\n url.hash = ''\n return url.toString()\n } catch {\n return `${endpoint.replace(/\\/+$/, '')}/v1/replay`\n }\n}\n\n/**\n * Gzip newline-delimited JSON lines via the native `CompressionStream` — no\n * dependency (resolves the story 1.1 gzip-strategy deferral). Available in every\n * evergreen browser and Node ≥ 18. Uses the `Blob.stream()` → `pipeThrough` →\n * `Response.arrayBuffer()` pipeline so there is no manual writer/reader (no\n * unhandled rejection, no backpressure deadlock). The output is the gzip stream,\n * stored as-is by the backend.\n */\nexport async function gzipJsonl(lines: string[]): Promise<Uint8Array> {\n const data = new TextEncoder().encode(lines.join('\\n'))\n const stream = new Blob([data as BlobPart]).stream().pipeThrough(new CompressionStream('gzip'))\n const buf = await new Response(stream).arrayBuffer()\n return new Uint8Array(buf)\n}\n\n/** Arguments for {@link postReplayChunk}. */\nexport interface PostReplayChunkOptions {\n /** Replay endpoint URL (from {@link buildReplayUrl}). */\n url: string\n /** API key for the `x-observ-key` header (omitted when empty). */\n key: string\n /** Current visit `session.id` (query param, keys the object-store prefix). */\n sessionId: string\n /** Monotonic chunk sequence within the session (query param). */\n seq: number\n /** Gzip bytes of the chunk. An empty body is never sent (server returns 400). */\n body: Uint8Array\n /** Use `fetch` keepalive for an unload flush (body capped ~64 KiB by the spec). */\n keepalive?: boolean\n}\n\n/**\n * POST one gzip replay chunk. **Best-effort: never throws** — a network failure\n * degrades to \"this chunk is lost\", it must not break the host page.\n */\nexport async function postReplayChunk(opts: PostReplayChunkOptions): Promise<void> {\n if (opts.body.length === 0) return // backend rejects an empty body (400)\n\n const headers: Record<string, string> = { 'content-type': 'application/octet-stream' }\n // Mirror otel-rum.ts: a blank key would be a malformed credential — omit it.\n if (opts.key) headers['x-observ-key'] = opts.key\n\n const url = `${opts.url}?session_id=${encodeURIComponent(opts.sessionId)}&seq=${opts.seq}`\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers,\n body: opts.body as BodyInit,\n keepalive: opts.keepalive,\n })\n // `fetch` only rejects on network failure — surface server rejections\n // (400 empty / 401 auth / 413 over the 16 MiB cap) instead of silent success.\n if (!res.ok) {\n console.warn(`[observ] replay chunk rejected: HTTP ${res.status}`)\n }\n } catch (err) {\n console.warn('[observ] replay chunk upload failed', err)\n }\n}\n","/**\n * rrweb recording + chunked replay upload for the Observ RUM SDK (FR-2, story 2.2).\n *\n * Captures the visual session with `@rrweb/record` (initial FullSnapshot + DOM\n * mutations), buffers events in memory, and POSTs gzip chunks to `/v1/replay`\n * (story 2.1) — triggered by SIZE or TIME, whichever comes first. The heavy flux\n * uses this channel exclusively, never the OTLP exporter.\n *\n * Invariant (1.1/1.2/1.3): the SDK must NEVER break the host page. Every path —\n * capture, serialize, gzip, upload — is defensive: a failure degrades to \"no\n * replay\", never an exception that escapes to the page.\n *\n * Flushes are SERIALIZED through a promise chain (`pending`): every trigger is\n * queued — never dropped — so the buffer cannot grow unbounded behind an\n * in-flight flush, `seq` is assigned in order, and two uploads never overlap.\n *\n * Session activity (1.2 deferral): `getSessionId` is read once per flush (~5 s),\n * NOT per rrweb event — touching the session on every high-frequency mutation\n * would thrash `sessionStorage` and jank the main thread.\n */\nimport { record } from '@rrweb/record'\n\nimport { resolveReplayMasking } from './privacy.js'\nimport { getSessionId } from './session.js'\nimport { buildReplayUrl, gzipJsonl, postReplayChunk } from './transport.js'\nimport type { ObservInitOptions } from './types.js'\n\n/** Serialized buffer size (pre-gzip) that triggers a size-based flush. */\nexport const CHUNK_MAX_BYTES = 512 * 1024 // ~512 KiB — well under the 16 MiB cap\n/** Interval (ms) for the time-based flush. */\nexport const CHUNK_INTERVAL_MS = 5_000\n/** rrweb full-snapshot cadence so a reader can resync from a checkpoint. */\nexport const CHECKOUT_INTERVAL_MS = 5 * 60 * 1_000\n\n/** Public handle for the running recorder. */\nexport interface ReplayRecorder {\n /** Stop capture, remove listeners, and flush the residual. Idempotent. */\n stop(): Promise<void>\n}\n\n/** No-op recorder returned in a non-DOM environment (SSR) — nothing runs. */\nconst NOOP_RECORDER: ReplayRecorder = { stop: () => Promise.resolve() }\n\n/**\n * Start rrweb capture + chunked upload. Returns a handle whose `stop()` tears\n * everything down and flushes the tail. Never throws: a capture/start failure is\n * swallowed (the page keeps working, just without replay).\n */\nexport function startReplayRecording(options: ObservInitOptions): ReplayRecorder {\n // SSR / non-DOM guard: rrweb and the lifecycle listeners need `document` /\n // `window`. Degrade to a no-op recorder (mirrors session.ts's globalThis-\n // defensive style) rather than throwing a ReferenceError / leaking a timer.\n if (typeof document === 'undefined' || typeof window === 'undefined') {\n return NOOP_RECORDER\n }\n\n const url = buildReplayUrl(options.endpoint)\n const key = options.key\n\n let buffer: string[] = []\n let bufferedBytes = 0\n let seq = 0\n let lastSessionId: string | null = null\n let stopped = false\n // Flush chain: each trigger appends to this promise so flushes run one after\n // another (never concurrently, never dropped). Kept reject-free by the inner\n // try/catch + the trailing `.catch`.\n let pending: Promise<void> = Promise.resolve()\n\n /** Serialize → gzip → upload the current buffer. Swapped before the first await. */\n async function doFlush(keepalive: boolean): Promise<void> {\n if (buffer.length === 0) return\n const lines = buffer\n buffer = []\n bufferedBytes = 0\n try {\n const body = await gzipJsonl(lines)\n const sessionId = getSessionId()\n // The session id rotated (idle tab past the 30-min window): restart `seq`\n // so the new object-store prefix begins at 0 (its own checkpoint).\n if (lastSessionId !== null && lastSessionId !== sessionId) seq = 0\n lastSessionId = sessionId\n await postReplayChunk({ url, key, sessionId, seq: seq++, body, keepalive })\n } catch (err) {\n console.warn('[observ] replay flush failed', err)\n }\n }\n\n /** Queue a flush after any in-flight one (never dropped). */\n function enqueueFlush(keepalive = false): void {\n pending = pending.then(() => doFlush(keepalive)).catch(() => {})\n }\n\n const emit = (event: unknown): void => {\n if (stopped) return // drop trailing events after teardown\n try {\n const line = JSON.stringify(event)\n buffer.push(line)\n bufferedBytes += line.length\n if (bufferedBytes >= CHUNK_MAX_BYTES) enqueueFlush()\n } catch {\n /* never let capture break the host page */\n }\n }\n\n // PII masking (story 4.1): safe-by-default — input values are masked unless\n // the host explicitly opts out via `privacy.maskAllInputs: false`. Resolved\n // once here and spread into the recorder config so the heavy replay flux is\n // privacy-safe by construction (architecture D7 / PRD Open Q5).\n const masking = resolveReplayMasking(options.privacy)\n\n let stopFn: (() => void) | undefined\n try {\n // `record` returns the stop handler (or undefined if unsupported).\n stopFn = record({ emit, checkoutEveryNms: CHECKOUT_INTERVAL_MS, ...masking }) ?? undefined\n } catch (err) {\n console.warn('[observ] rrweb recording failed to start', err)\n }\n\n const interval = setInterval(() => enqueueFlush(), CHUNK_INTERVAL_MS)\n\n // Lifecycle flush (1.3 deferral). visibilitychange→hidden fires on tab\n // switch / minimize / most mobile navigations while the page is still alive;\n // pagehide is the best-effort last resort (keepalive — a body the browser\n // can't keepalive-send is rejected and logged, not silently pre-dropped).\n const onVisibility = (): void => {\n if (document.visibilityState === 'hidden') enqueueFlush()\n }\n const onPageHide = (): void => {\n enqueueFlush(true)\n }\n document.addEventListener('visibilitychange', onVisibility)\n window.addEventListener('pagehide', onPageHide)\n\n return {\n async stop(): Promise<void> {\n if (stopped) return\n stopped = true\n clearInterval(interval)\n document.removeEventListener('visibilitychange', onVisibility)\n window.removeEventListener('pagehide', onPageHide)\n try {\n stopFn?.()\n } catch {\n /* swallow — teardown must not throw */\n }\n enqueueFlush() // final residual\n await pending // wait for the whole chain (incl. the final flush) to settle\n },\n }\n}\n","/**\n * Minimal, defensive CSS-selector + text derivation for semantic events\n * (story 2.3). Produces a short, reasonably stable selector for a clicked\n * element and a truncated text label. Never throws (the SDK must never break\n * the host page); degrades to the tag name or `''`.\n */\n\nconst MAX_TEXT = 100\nconst MAX_CLASSES = 3\nconst MAX_CLASS_LEN = 30\nconst MAX_DEPTH = 4\n\n/**\n * Build a short CSS selector for `el`:\n * - `tag#id` when the element has an id,\n * - else `tag.class1.class2` (≤ 3 short classes),\n * - else a depth-bounded `tag:nth-of-type(n)` ancestor path (≤ 4 levels).\n * Returns `''` for a non-element / null, or the bare tag name on any failure.\n */\nexport function cssSelector(el: Element | null): string {\n if (!el || el.nodeType !== 1) return ''\n try {\n // `localName` is lowercase for HTML but case-preserved for SVG\n // (`linearGradient`, `clipPath`) — `tagName.toLowerCase()` would corrupt those.\n const tag = el.localName\n if (el.id) return `${tag}#${el.id}`\n\n const classes = Array.from(el.classList)\n .filter((c) => c.length > 0 && c.length <= MAX_CLASS_LEN)\n .slice(0, MAX_CLASSES)\n if (classes.length > 0) return `${tag}.${classes.join('.')}`\n\n // Depth-bounded structural path.\n const parts: string[] = []\n let node: Element | null = el\n let depth = 0\n while (node && node.nodeType === 1 && depth < MAX_DEPTH) {\n const t = node.localName\n if (node.id) {\n parts.unshift(`${t}#${node.id}`)\n break\n }\n const parent: Element | null = node.parentElement\n if (parent) {\n const current = node\n const sameTag = Array.from(parent.children).filter((c) => c.tagName === current.tagName)\n if (sameTag.length > 1) {\n parts.unshift(`${t}:nth-of-type(${sameTag.indexOf(current) + 1})`)\n } else {\n parts.unshift(t)\n }\n } else {\n parts.unshift(t)\n }\n node = node.parentElement\n depth++\n }\n return parts.join(' > ')\n } catch {\n return el.localName ?? ''\n }\n}\n\n/** Trimmed, whitespace-collapsed, truncated `textContent` of `el` (≤ 100 chars). */\nexport function elementText(el: Element | null): string {\n if (!el) return ''\n try {\n const text = (el.textContent ?? '').trim().replace(/\\s+/g, ' ')\n return text.length > MAX_TEXT ? text.slice(0, MAX_TEXT) : text\n } catch {\n return ''\n }\n}\n","/**\n * Semantic event derivation for the Observ RUM SDK (FR-3 / D8, story 2.3).\n *\n * Derives high-value interaction facts from native DOM listeners and emits them\n * as OTel log records (`observ.session.*`) on the logs channel (`/v1/logs`):\n * - `observ.session.click` — every click, with `element.selector` / `element.text`\n * - `observ.session.rage_click` — ≥ 3 clicks < 1 s on the SAME selector\n * - `observ.session.navigate` — SPA navigation (`pushState`/`replaceState`/`popstate`), `url`\n *\n * Variance vs architecture D8 (\"tap rrweb emit\"): we use native DOM listeners\n * rather than reverse-mapping rrweb node ids — `element.selector`/`element.text`\n * are derived directly and reliably from `event.target`. Same OTel-logs output.\n *\n * Invariant (1.1/1.2/1.3): never break the host page. Listeners run in the\n * page's own context, so every handler is `try/catch`-guarded; the patched\n * `history` methods are restored on `stop()` (no leaked global).\n */\nimport type { Logger } from '@opentelemetry/api-logs'\n\nimport { emitSessionEvent, setupOtelLogs } from './otel-logs.js'\nimport { cssSelector, elementText } from './selector.js'\nimport type { ObservInitOptions } from './types.js'\n\n/** Rage-click window (ms) and click threshold (architecture D8 / AC#2). */\nexport const RAGE_CLICK_WINDOW_MS = 1_000\nexport const RAGE_CLICK_THRESHOLD = 3\n/** Cap on tracked selectors so the rage-click map can't grow unbounded over a\n * long session (cleared past this — rage detection is best-effort). */\nconst MAX_TRACKED_SELECTORS = 100\n\n/** Module-level guard so the tap is self-idempotent (a second start without a\n * stop() between would double-patch `history` / double-add listeners). */\nlet active: SemanticEventsHandle | null = null\n\n/** Public handle for the running semantic-event tap. */\nexport interface SemanticEventsHandle {\n /** Remove listeners, restore patched `history`, shut the logs provider down. */\n stop(): Promise<void>\n}\n\nconst NOOP: SemanticEventsHandle = { stop: () => Promise.resolve() }\n\n/** Resolve an `EventTarget` to the nearest `Element` (text/SVG nodes → parent). */\nfunction toElement(target: EventTarget | null): Element | null {\n if (target instanceof Element) return target\n const node = target as Node | null\n return node && node.parentElement ? node.parentElement : null\n}\n\n/**\n * Start deriving + emitting semantic events. Returns a handle whose `stop()`\n * tears everything down. Never throws; degrades to a no-op in a non-DOM env.\n * NOTE: not gated by `disableReplay` — semantic events are useful even without\n * the heavy replay capture.\n */\nexport function startSemanticEvents(options: ObservInitOptions): SemanticEventsHandle {\n if (active) return active\n if (typeof document === 'undefined' || typeof window === 'undefined') return NOOP\n\n let logger: Logger\n try {\n logger = setupOtelLogs(options)\n } catch (err) {\n console.warn('[observ] semantic events: logs setup failed', err)\n return NOOP\n }\n\n // Recent click timestamps per selector, for rage-click clustering.\n const clickTimes = new Map<string, number[]>()\n\n const onClick = (event: Event): void => {\n try {\n const el = toElement(event.target)\n if (!el) return\n const selector = cssSelector(el)\n emitSessionEvent(logger, 'observ.session.click', {\n 'element.selector': selector,\n 'element.text': elementText(el),\n })\n\n // Rage-click: ≥ THRESHOLD clicks within WINDOW on the same selector.\n const now = Date.now()\n const recent = (clickTimes.get(selector) ?? []).filter((t) => now - t < RAGE_CLICK_WINDOW_MS)\n recent.push(now)\n if (recent.length >= RAGE_CLICK_THRESHOLD) {\n emitSessionEvent(logger, 'observ.session.rage_click', { 'element.selector': selector })\n clickTimes.delete(selector) // reset the burst (one rage_click per burst)\n } else {\n // Bound the map: a selector clicked once never reaches the threshold and\n // would otherwise linger forever. Clear past the cap (rare; best-effort).\n if (clickTimes.size > MAX_TRACKED_SELECTORS) clickTimes.clear()\n clickTimes.set(selector, recent)\n }\n } catch {\n /* never let a click handler break the host page */\n }\n }\n\n const emitNavigate = (): void => {\n emitSessionEvent(logger, 'observ.session.navigate', { url: location.href })\n }\n const onPopState = (): void => emitNavigate()\n\n document.addEventListener('click', onClick, { capture: true })\n window.addEventListener('popstate', onPopState)\n\n // No native event fires for pushState/replaceState — patch them (and restore\n // on stop, so the global is never left monkey-patched).\n const origPushState = history.pushState\n const origReplaceState = history.replaceState\n let historyPatched = false\n try {\n history.pushState = function (this: History, ...args: Parameters<History['pushState']>): void {\n origPushState.apply(this, args)\n emitNavigate()\n }\n history.replaceState = function (\n this: History,\n ...args: Parameters<History['replaceState']>\n ): void {\n origReplaceState.apply(this, args)\n emitNavigate()\n }\n historyPatched = true\n } catch {\n /* leave history unpatched — popstate still works */\n }\n\n let stopped = false\n const handle: SemanticEventsHandle = {\n async stop(): Promise<void> {\n if (stopped) return\n stopped = true\n active = null\n document.removeEventListener('click', onClick, { capture: true })\n window.removeEventListener('popstate', onPopState)\n if (historyPatched) {\n try {\n history.pushState = origPushState\n history.replaceState = origReplaceState\n } catch {\n /* swallow — teardown must not throw */\n }\n }\n // The shared logs provider is shut down once by index.shutdown() (story\n // 2.4) — not here, since js-errors shares it.\n },\n }\n active = handle\n return handle\n}\n","import { startJsErrorCapture, type JsErrorHandle } from './js-errors.js'\nimport { shutdownOtelLogs } from './otel-logs.js'\nimport { setupOtelRum, shutdownOtelRum } from './otel-rum.js'\nimport { startReplayRecording, type ReplayRecorder } from './rrweb-record.js'\nimport { startSemanticEvents, type SemanticEventsHandle } from './semantic-events.js'\nimport { ensureSession } from './session.js'\nimport type { ObservInitOptions, ObservSdk } from './types.js'\n\nexport type { ObservInitOptions, ObservSdk } from './types.js'\n\n/**\n * Attribute key used everywhere to correlate signals of a single session.\n * Re-exported from {@link ./constants.js} (the leaf module that internal modules\n * also import, avoiding an `index ↔ otel-rum` cycle). EXACTLY `session.id`.\n */\nexport { SESSION_ID_ATTRIBUTE } from './constants.js'\n\n/** Singleton replay recorder handle (one capture per page, like the OTel provider). */\nlet replayRecorder: ReplayRecorder | null = null\n/** Singleton semantic-events tap (story 2.3). */\nlet semanticEvents: SemanticEventsHandle | null = null\n/** Singleton JS-error capture (story 2.4). */\nlet jsErrors: JsErrorHandle | null = null\n\n/**\n * Initialize the Observ RUM SDK.\n *\n * 1. Establishes (or resumes) the visit's `session.id` — the single\n * correlation key shared by every signal (story 1.2).\n * 2. Wires OTel RUM tracing (story 1.3): `http.client` spans for fetch/XHR,\n * carrying `session.id`, with W3C `traceparent` propagation and OTLP/HTTP\n * export to `{endpoint}/v1/traces`.\n * 3. Starts rrweb replay capture (story 2.2): buffered gzip chunks POSTed to\n * `{endpoint}/v1/replay` (size/time triggered), unless `disableReplay`.\n * 4. Derives semantic events (story 2.3): `click` / `rage_click` / `navigate`\n * emitted as OTel log records to `{endpoint}/v1/logs` (independent of\n * `disableReplay`).\n * 5. Captures JS errors (story 2.4): `observ.session.js_error` (uncaught\n * errors + unhandled rejections) on the same logs channel.\n *\n * Idempotent (a second call re-registers nothing) and never throws — any setup\n * failure is swallowed so the SDK can never break the host page; it degrades to\n * \"no tracing / no replay / no semantic events\".\n */\nexport function init(options: ObservInitOptions): void {\n try {\n ensureSession()\n setupOtelRum(options)\n if (!options.disableReplay && !replayRecorder) {\n replayRecorder = startReplayRecording(options)\n }\n // Semantic events + JS errors are useful even without replay → not gated.\n if (!semanticEvents) {\n semanticEvents = startSemanticEvents(options)\n }\n if (!jsErrors) {\n jsErrors = startJsErrorCapture(options)\n }\n } catch (err) {\n // The SDK must never break the host page (1.1/1.2 guarantee). Degrade to\n // \"no tracing / no replay / no semantic events\" rather than propagating.\n console.warn('[observ] RUM setup failed; tracing/replay/events degraded.', err)\n }\n}\n\n/**\n * Stop replay capture (flushing the residual chunk), the semantic-event tap, and\n * tear down OTel tracing + logs. Best-effort and never throws (resolves the\n * story 1.3 \"no public teardown\" deferral). After this, a later {@link init} can\n * re-start the SDK.\n */\nexport async function shutdown(): Promise<void> {\n const recorder = replayRecorder\n const events = semanticEvents\n const errors = jsErrors\n replayRecorder = null\n semanticEvents = null\n jsErrors = null\n try {\n await recorder?.stop()\n } catch {\n /* swallow — teardown must never throw to the host page */\n }\n try {\n await events?.stop()\n } catch {\n /* swallow */\n }\n try {\n errors?.stop()\n } catch {\n /* swallow */\n }\n // Both the semantic-events tap and the JS-error capture share ONE logs\n // provider — shut it down once, here, after both taps have stopped. Each\n // provider shutdown is guarded so a flush rejection can't skip the next one\n // or escape to the host page (shutdown must never throw — 1.3 guarantee).\n try {\n await shutdownOtelLogs()\n } catch {\n /* swallow */\n }\n try {\n await shutdownOtelRum()\n } catch {\n /* swallow */\n }\n}\n\n/** Singleton SDK handle. Usage: `observ.init({ endpoint, key })`. */\nexport const observ: ObservSdk = { init, shutdown }\n\nexport default observ\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/baggage.ts","../src/resource.ts","../src/constants.ts","../src/session.ts","../src/otel-logs.ts","../src/js-errors.ts","../src/otel-metrics.ts","../node_modules/@opentelemetry/instrumentation/src/autoLoaderUtils.ts","../node_modules/@opentelemetry/instrumentation/src/autoLoader.ts","../src/otel-rum.ts","../src/privacy.ts","../src/transport.ts","../src/rrweb-record.ts","../src/selector.ts","../src/semantic-events.ts","../src/index.ts"],"names":["init","provider","record","disableInstrumentations","current","active","NOOP","metrics"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Backend correlation via the W3C **baggage** header (parity story).\n *\n * The fetch/XHR auto-instrumentations already inject `traceparent` on requests to\n * allowed backends; this adds `session.id` to the **baggage** header on those same\n * requests, so the server spans (Strapi / API / KV) can read the session identity\n * and stamp it server-side — making `session.id ⇄ backend span` traversable\n * beyond the `trace_id` link.\n *\n * We wrap `window.fetch` (installed AFTER OTel's fetch instrumentation, so our\n * wrapper is the outermost) and append `session.id` to any existing `baggage`\n * header, but ONLY for URLs matching `backendUrls` — never leaking the id to\n * third parties. Best-effort and reversible (returns an uninstall fn).\n *\n * Gated by the `propagateBaggage` init option; the allowlist defaults to\n * `propagateTraceHeaderCorsUrls` (the same backends that receive `traceparent`).\n */\ntype Matcher = string | RegExp\n\nfunction matches(url: string, matchers: Matcher[]): boolean {\n return matchers.some((m) => (typeof m === 'string' ? url.startsWith(m) : m.test(url)))\n}\n\nfunction resolveUrl(input: RequestInfo | URL): string {\n if (typeof input === 'string') return input\n if (input instanceof URL) return input.href\n return input.url\n}\n\n/** Merge `session.id=<id>` into an existing W3C `baggage` header value. */\nexport function withSessionBaggage(existing: string | null, sessionId: string): string {\n const entry = `session.id=${encodeURIComponent(sessionId)}`\n if (!existing) return entry\n // Don't duplicate if a session.id is already present (e.g. nested wrappers).\n const hasSession = existing.split(',').some((p) => p.trim().startsWith('session.id='))\n return hasSession ? existing : `${existing},${entry}`\n}\n\n/**\n * Install the baggage propagation fetch wrapper. No-op (returns a no-op\n * uninstaller) when there is no `window.fetch`. `getId` is the session-id source\n * ({@link import('./session.js').getSessionId}); `ignoreUrls` excludes the\n * telemetry pipeline itself.\n */\nexport function installBaggagePropagation(\n getId: () => string,\n backendUrls: Matcher[],\n ignoreUrls: Matcher[] = [],\n): () => void {\n if (\n typeof window === 'undefined' ||\n typeof window.fetch !== 'function' ||\n backendUrls.length === 0\n ) {\n return () => {}\n }\n\n const originalFetch = window.fetch.bind(window)\n\n window.fetch = function patchedFetch(input: RequestInfo | URL, init?: RequestInit) {\n let sessionId = ''\n let url = ''\n try {\n sessionId = getId()\n url = resolveUrl(input)\n } catch {\n return originalFetch(input, init)\n }\n\n // Skip non-backend URLs and the telemetry pipeline itself.\n if (!sessionId || !matches(url, backendUrls) || matches(url, ignoreUrls)) {\n return originalFetch(input, init)\n }\n\n try {\n const headers = new Headers(\n init?.headers ?? (input instanceof Request ? input.headers : undefined),\n )\n headers.set('baggage', withSessionBaggage(headers.get('baggage'), sessionId))\n return originalFetch(input, { ...init, headers })\n } catch {\n // Header juggling failed → fall back to an unmodified request.\n return originalFetch(input, init)\n }\n }\n\n return () => {\n window.fetch = originalFetch\n }\n}\n","/**\n * Shared OTel `Resource` for every RUM signal (traces, logs, metrics).\n *\n * Parity story (epic \"RUM parity\"): the original SDK created its providers with\n * NO resource, so every signal landed under `unknown_service`. This builds a\n * single resource from the public init options + ambient browser facts, applied\n * identically to the trace / log / meter providers so all three carry the same\n * `service.name` and `session`-independent context.\n *\n * `telemetry.sdk.language: 'webjs'` marks the signal as browser-originated (the\n * backend uses it to tell RUM apart from server SDKs).\n */\nimport { resourceFromAttributes, type Resource } from '@opentelemetry/resources'\nimport { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'\n\nimport type { ObservInitOptions } from './types.js'\n\n/**\n * Build the resource attached to every provider. `service.name` is set only when\n * the host supplies one (otherwise the OTel default `unknown_service` stands, but\n * at least `telemetry.sdk.language` flags it as browser RUM). Browser facts are\n * read defensively so the SDK stays no-throw under SSR / no-`navigator`.\n */\nexport function buildResource(options: ObservInitOptions): Resource {\n const attrs: Record<string, string | boolean> = {\n 'telemetry.sdk.language': 'webjs',\n }\n if (options.serviceName) attrs[ATTR_SERVICE_NAME] = options.serviceName\n if (options.serviceVersion) attrs[ATTR_SERVICE_VERSION] = options.serviceVersion\n if (options.environment) attrs['deployment.environment.name'] = options.environment\n\n try {\n if (typeof navigator !== 'undefined') {\n if (navigator.language) attrs['browser.language'] = navigator.language\n attrs['browser.mobile'] = /Mobi|Android/i.test(navigator.userAgent || '')\n }\n } catch {\n /* swallow — resource enrichment must never break init */\n }\n\n return resourceFromAttributes(attrs)\n}\n","/**\n * Cross-cutting constants for the Observ RUM SDK.\n *\n * Kept in a leaf module (no SDK imports beyond semconv constants) so both the\n * public entry point (`index.ts`) and internal modules (`otel-rum.ts`) can share\n * them without a circular import.\n */\nimport { ATTR_SESSION_ID } from '@opentelemetry/semantic-conventions/incubating'\n\n/**\n * Attribute key used everywhere to correlate signals of a single session.\n * Sourced from the pinned `@opentelemetry/semantic-conventions` (incubating\n * `ATTR_SESSION_ID`) rather than a hand-typed literal, so the key tracks the\n * standard. Enforcement rule (architecture): the OTel session attribute is\n * EXACTLY `session.id` — never `sessionId` nor `session_id`. Single source of\n * truth so every signal (spans, semantic events, rrweb) stamps the identical key.\n * A test asserts `=== 'session.id'` to guard that invariant across semconv bumps.\n */\nexport const SESSION_ID_ATTRIBUTE: 'session.id' = ATTR_SESSION_ID\n","/**\n * Session lifecycle for the Observ RUM SDK (FR-1).\n *\n * Produces a single, stable `session.id` per visit and exposes it INTERNALLY as\n * the one source of truth that later modules (`rrweb-record`, `semantic-events`,\n * `otel-rum`) stamp onto every signal. Not part of the public `observ` surface.\n *\n * Authoritative rules (architecture D5):\n * - `session.id` is a UUID v4, generated in the browser,\n * - persisted in `sessionStorage` (per-tab — two tabs are two visits, by design),\n * - rotated after 30 min of inactivity.\n *\n * Rotation is LAZY: there is no background timer — the window is evaluated on the\n * next `ensureSession`/`getSessionId`/`touchSession` call, and a fresh id is\n * minted then if the window has elapsed.\n *\n * This module performs NO network I/O. It must never throw: if `sessionStorage`\n * or `crypto` are unavailable (private mode, disabled storage, SSR / insecure\n * context), it degrades to an in-memory session.id. NOTE: in that degraded mode\n * persistence is lost across full page loads, so each navigation starts a new\n * session (the per-visit guarantee holds only while `sessionStorage` works).\n */\n\n/** Single `sessionStorage` key holding the serialized session state. */\nexport const SESSION_STORAGE_KEY = 'observ.rum.session'\n\n/**\n * Inactivity window after which a new `session.id` is minted (architecture D5,\n * resolves PRD Open Q2). 30 minutes, in milliseconds.\n */\nexport const SESSION_INACTIVITY_TIMEOUT_MS = 30 * 60 * 1000\n\n/** Shape persisted under {@link SESSION_STORAGE_KEY}. Timestamps are epoch ms. */\ninterface PersistedSession {\n id: string\n startedAt: number\n lastActivityAt: number\n}\n\n/**\n * In-memory memoization. Within a single page it is the source of truth (one\n * `init` per SPA load); it is also the sole fallback when storage is unusable.\n */\nlet current: PersistedSession | null = null\n\n/** Build a UUID v4, preferring crypto; falls back gracefully (documented). */\nfunction generateSessionId(): string {\n const c: Crypto | undefined = globalThis.crypto\n // `crypto.randomUUID` requires a secure context (https or localhost). Detection\n // tests presence, not callability — guard the call so a throwing implementation\n // falls through instead of escaping (the module must never throw).\n if (c && typeof c.randomUUID === 'function') {\n try {\n return c.randomUUID()\n } catch {\n // fall through to getRandomValues\n }\n }\n if (c && typeof c.getRandomValues === 'function') {\n const bytes = c.getRandomValues(new Uint8Array(16))\n bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x40 // version 4\n bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80 // variant 10\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`\n }\n // Last-resort, non-crypto fallback (SSR / no Web Crypto). Not cryptographically\n // strong — acceptable only because no real entropy source exists here.\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (ch) => {\n const r = (Math.random() * 16) | 0\n const v = ch === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\n/** Read from `sessionStorage`, swallowing any access error. */\nfunction safeRead(): string | null {\n try {\n return globalThis.sessionStorage?.getItem(SESSION_STORAGE_KEY) ?? null\n } catch {\n return null\n }\n}\n\n/** Write to `sessionStorage`, swallowing any access/quota error. */\nfunction safeWrite(value: string): void {\n try {\n globalThis.sessionStorage?.setItem(SESSION_STORAGE_KEY, value)\n } catch {\n // In-memory `current` stays authoritative; nothing else to do.\n }\n}\n\n/** Validate the parsed storage payload before trusting it. */\nfunction isPersistedSession(value: unknown): value is PersistedSession {\n if (typeof value !== 'object' || value === null) return false\n const v = value as Record<string, unknown>\n return (\n typeof v.id === 'string' &&\n v.id.length > 0 &&\n typeof v.startedAt === 'number' &&\n Number.isFinite(v.startedAt) &&\n typeof v.lastActivityAt === 'number' &&\n Number.isFinite(v.lastActivityAt)\n )\n}\n\n/** Resolve the current session, from memory first, then storage. */\nfunction load(): PersistedSession | null {\n if (current) return current\n const raw = safeRead()\n if (raw === null) return null\n try {\n const parsed: unknown = JSON.parse(raw)\n return isPersistedSession(parsed) ? parsed : null\n } catch {\n return null // corrupt JSON → treat as absent\n }\n}\n\n/** Memoize in memory and best-effort persist to storage. */\nfunction save(session: PersistedSession): void {\n current = session\n safeWrite(JSON.stringify(session))\n}\n\n/**\n * Ensure a `session.id` exists for this visit and return it.\n *\n * Reuses the stored id when within the inactivity window (refreshing\n * `lastActivityAt`); otherwise mints a fresh session. `now` is injectable for\n * deterministic tests.\n */\nexport function ensureSession(now: number = Date.now()): string {\n const existing = load()\n if (existing) {\n // Clamp to 0 so a backward clock jump (now < lastActivityAt, e.g. NTP/DST)\n // can't yield a negative \"elapsed\" that silently extends the session; the\n // stale future timestamp is then corrected to `now` below.\n const elapsed = Math.max(0, now - existing.lastActivityAt)\n if (elapsed <= SESSION_INACTIVITY_TIMEOUT_MS) {\n save({ ...existing, lastActivityAt: now })\n return existing.id\n }\n }\n const fresh: PersistedSession = { id: generateSessionId(), startedAt: now, lastActivityAt: now }\n save(fresh)\n return fresh.id\n}\n\n/**\n * Return the `session.id` for the current visit.\n *\n * Delegates to {@link ensureSession} so every read enforces the inactivity\n * timeout (rotating to a fresh id once the window has elapsed) — a memoized\n * read must not be able to hand back a session that should have rotated. As a\n * consequence a read also refreshes `lastActivityAt`, i.e. reading the id counts\n * as activity.\n */\nexport function getSessionId(now: number = Date.now()): string {\n return ensureSession(now)\n}\n\n/**\n * Push the inactivity window forward on real activity (rotating if the session\n * has already expired). Wiring to actual interactions arrives in stories 2.x;\n * this is the hook + the write.\n */\nexport function touchSession(now: number = Date.now()): void {\n ensureSession(now)\n}\n\n/**\n * @internal Test-only. Clears the in-memory memoization to simulate a fresh\n * page load / module re-import (storage is left untouched).\n */\nexport function resetSessionState(): void {\n current = null\n}\n","/**\n * OpenTelemetry Logs pipeline for the Observ RUM SDK (story 2.3).\n *\n * Semantic events (`observ.session.*`) are emitted as OTel **log records** and\n * exported over OTLP/HTTP to `{endpoint}/v1/logs` (received by the backend since\n * story 1.4; the session-assembly writer taps them in 1.6). This is the mirror\n * of the trace pipeline in `otel-rum.ts`, on a dedicated, separate provider —\n * the light \"logs\" channel, never the heavy `/v1/replay` channel.\n *\n * `session.id` is stamped once, at emit time, by {@link emitSessionEvent} — the\n * single point through which every semantic event flows (so a separate\n * `LogRecordProcessor` would be redundant; this also keeps the SDK's\n * sdk-logs API surface minimal).\n */\nimport type { Logger } from '@opentelemetry/api-logs'\nimport { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'\nimport { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs'\n\nimport { buildResource } from './resource.js'\nimport { SESSION_ID_ATTRIBUTE } from './constants.js'\nimport { getSessionId } from './session.js'\nimport type { ObservInitOptions } from './types.js'\n\n/**\n * Join an endpoint base URL with the OTLP/HTTP logs path. Mirror of\n * {@link import('./otel-rum.js').buildTracesUrl}: preserves a path prefix, never\n * doubles a trailing slash, drops query/fragment; relative/empty → same-origin.\n */\nexport function buildLogsUrl(endpoint: string): string {\n try {\n const url = new URL(endpoint)\n url.pathname = `${url.pathname.replace(/\\/+$/, '')}/v1/logs`\n url.search = ''\n url.hash = ''\n return url.toString()\n } catch {\n return `${endpoint.replace(/\\/+$/, '')}/v1/logs`\n }\n}\n\nlet provider: LoggerProvider | null = null\nlet activeLogger: Logger | null = null\n\n/**\n * Wire up the OTel Logs provider and return a `Logger`. Idempotent: a second\n * call returns the already-active logger (first init wins, mirrors\n * `setupOtelRum`). The `x-observ-key` header is omitted when the key is empty\n * (the trace setup already warns about a missing key).\n */\nexport function setupOtelLogs(options: ObservInitOptions): Logger {\n if (activeLogger) return activeLogger\n\n const headers: Record<string, string> = {}\n if (options.key) headers['x-observ-key'] = options.key\n\n const exporter = new OTLPLogExporter({ url: buildLogsUrl(options.endpoint), headers })\n const p = new LoggerProvider({\n resource: buildResource(options),\n processors: [new BatchLogRecordProcessor(exporter)],\n })\n\n try {\n const logger = p.getLogger('@observtech/rum')\n // Commit the module state ONLY once everything succeeded — otherwise a\n // partial init would leak the provider's flush timer and latch the guard.\n provider = p\n activeLogger = logger\n return logger\n } catch (err) {\n // Roll back a partial init (mirrors otel-rum.ts): shut the provider down so\n // its BatchLogRecordProcessor timer can't outlive the failure, and leave the\n // guards null so a later setup can retry.\n void p.shutdown()\n throw err\n }\n}\n\n/** Inactivity refresh throttle for the cached session id (story 1.2 — avoid a\n * synchronous sessionStorage write on every event). */\nconst SESSION_ID_REFRESH_MS = 1_000\nlet cachedSessionId = ''\nlet cachedSessionIdAt = 0\n\n/**\n * Resolve the visit `session.id`, refreshing it (and the session's inactivity\n * window) at most once per second. A click burst therefore costs ≤ 1\n * `getSessionId` (→ ≤ 1 sessionStorage write) per second instead of one per\n * event, while still counting interaction as session activity.\n */\nfunction currentSessionId(): string {\n const now = Date.now()\n if (!cachedSessionId || now - cachedSessionIdAt >= SESSION_ID_REFRESH_MS) {\n cachedSessionId = getSessionId()\n cachedSessionIdAt = now\n }\n return cachedSessionId\n}\n\n/**\n * Emit one semantic event as an OTel log record: `event.name` in the\n * `observ.session.*` namespace + the exact `session.id` correlation attribute\n * (throttled refresh, so an event read also counts as session activity, 1.2) +\n * the caller's attributes. Best-effort: never throws to the host page.\n */\nexport function emitSessionEvent(\n logger: Logger,\n eventName: string,\n attrs: Record<string, string> = {},\n): void {\n try {\n logger.emit({\n attributes: {\n 'event.name': eventName,\n [SESSION_ID_ATTRIBUTE]: currentSessionId(),\n ...attrs,\n },\n })\n } catch (err) {\n console.warn('[observ] semantic event emit failed', err)\n }\n}\n\n/**\n * @internal Flush + shut the logs provider down and reset the idempotence guard\n * so a later setup can re-init. Called by the SDK teardown.\n */\nexport async function shutdownOtelLogs(): Promise<void> {\n const p = provider\n provider = null\n activeLogger = null\n await p?.shutdown()\n}\n\n/** @internal Test-only: the active logger, or `null` when logs are not set up. */\nexport function getActiveLogger(): Logger | null {\n return activeLogger\n}\n","/**\n * JS error capture for the Observ RUM SDK (FR-9, story 2.4).\n *\n * Captures uncaught errors (`window` `error`) and unhandled promise rejections\n * (`unhandledrejection`) and emits them as OTel log records\n * (`observ.session.js_error`) on the SAME logs channel as the semantic events\n * (story 2.3): standard exception semconv attributes + `session.id`. The stack\n * trace is carried as a log/event attribute — never a metric (architecture\n * enforcement).\n *\n * Invariant (1.1/1.2/1.3): never break the host page. Handlers run when the\n * browser is already handling an error, so each is `try/catch`-guarded and we\n * NEVER `preventDefault()` — the site's own error flow (console, other handlers)\n * is left untouched; we only observe.\n */\nimport type { Logger } from '@opentelemetry/api-logs'\nimport {\n ATTR_EXCEPTION_MESSAGE,\n ATTR_EXCEPTION_STACKTRACE,\n ATTR_EXCEPTION_TYPE,\n} from '@opentelemetry/semantic-conventions'\n\nimport { emitSessionEvent, setupOtelLogs } from './otel-logs.js'\nimport type { ObservInitOptions } from './types.js'\n\n/** Max `js_error` events per rolling window — bounds an error storm (AC#5)\n * without going permanently blind: the window resets, so capture self-heals. */\nexport const MAX_JS_ERRORS = 50\n/** Rolling window (ms) for the {@link MAX_JS_ERRORS} rate limit. */\nexport const RATE_WINDOW_MS = 60_000\n/** Truncation bounds so one error can't produce a huge log record. */\nconst MAX_MESSAGE = 1_024\nconst MAX_STACK = 4_096\n\n/** Best-effort string for a rejection reason: Error→message handled by the\n * caller; objects → JSON (so `{code:500}` isn't flattened to \"[object Object]\");\n * everything else → `String`. */\nfunction reasonToMessage(reason: unknown): string {\n if (typeof reason === 'string') return reason\n if (typeof reason === 'object' && reason !== null) {\n try {\n return JSON.stringify(reason) ?? String(reason)\n } catch {\n return String(reason)\n }\n }\n return String(reason)\n}\n\n/** Public handle for the running JS-error capture. */\nexport interface JsErrorHandle {\n /** Remove the global error listeners. Idempotent. The shared logs provider is\n * shut down by `index.shutdown()`, NOT here (two taps share it). */\n stop(): void\n}\n\nconst NOOP: JsErrorHandle = { stop: () => {} }\n\n/** Module-level guard so the capture is self-idempotent. */\nlet active: JsErrorHandle | null = null\n\nfunction truncate(s: string, max: number): string {\n return s.length > max ? s.slice(0, max) : s\n}\n\n/**\n * Start capturing uncaught JS errors + unhandled rejections. Returns a handle\n * whose `stop()` removes the listeners. Never throws; no-op in a non-DOM env.\n * Not gated by `disableReplay` — errors are useful even without replay.\n */\nexport function startJsErrorCapture(options: ObservInitOptions): JsErrorHandle {\n if (active) return active\n if (typeof window === 'undefined') return NOOP\n\n let logger: Logger\n try {\n logger = setupOtelLogs(options) // shared, idempotent — same logger as 2.3\n } catch (err) {\n console.warn('[observ] js-errors: logs setup failed', err)\n return NOOP\n }\n\n // Sliding-window rate limit: at most MAX_JS_ERRORS per RATE_WINDOW_MS. Resets\n // each window so a burst at boot doesn't blind capture for the rest of a long\n // SPA session.\n let windowStart = 0\n let inWindow = 0\n\n const emit = (type: string | undefined, message: string, stack: string | undefined): void => {\n const now = Date.now()\n if (now - windowStart >= RATE_WINDOW_MS) {\n windowStart = now\n inWindow = 0\n }\n if (inWindow >= MAX_JS_ERRORS) return // anti-flood cap (AC#5)\n inWindow++\n const attrs: Record<string, string> = {\n [ATTR_EXCEPTION_MESSAGE]: truncate(message, MAX_MESSAGE),\n }\n if (type) attrs[ATTR_EXCEPTION_TYPE] = type\n if (stack) attrs[ATTR_EXCEPTION_STACKTRACE] = truncate(stack, MAX_STACK)\n emitSessionEvent(logger, 'observ.session.js_error', attrs)\n }\n\n const onError = (e: ErrorEvent): void => {\n try {\n // `e.error` is the Error in modern browsers; cross-origin \"Script error.\"\n // has `e.error === null`, so fall back to `e.message` with no stack.\n const err = e.error as Error | null | undefined\n emit(err?.name, err?.message ?? e.message ?? 'unknown error', err?.stack ?? undefined)\n } catch {\n /* never let the error handler break the host page */\n }\n }\n\n const onRejection = (e: PromiseRejectionEvent): void => {\n try {\n const reason: unknown = e.reason\n if (reason instanceof Error) {\n emit(reason.name, reason.message, reason.stack ?? undefined)\n } else {\n emit('UnhandledRejection', reasonToMessage(reason), undefined)\n }\n } catch {\n /* swallow — observe, never interfere */\n }\n }\n\n // Do NOT use `window.onerror =` (would clobber a host handler). Never call\n // preventDefault — we observe without suppressing the site's error flow.\n window.addEventListener('error', onError)\n window.addEventListener('unhandledrejection', onRejection)\n\n const handle: JsErrorHandle = {\n stop(): void {\n window.removeEventListener('error', onError)\n window.removeEventListener('unhandledrejection', onRejection)\n active = null\n },\n }\n active = handle\n return handle\n}\n","/**\n * OpenTelemetry Metrics + Core Web Vitals for the Observ RUM SDK (parity story).\n *\n * Net-new \"metrics\" channel — the third OTLP/HTTP signal, exported to\n * `{endpoint}/v1/metrics` on its own `MeterProvider` (the heavy `/v1/replay`\n * channel is never crossed). It captures Core Web Vitals (LCP / INP / CLS / FCP /\n * TTFB) as **histograms** (queryable as metrics) and ALSO mirrors each as an\n * `observ.session.web_vital` **log event** carrying `session.id`, so a single\n * vital appears both on the metrics dashboards and on the session timeline.\n *\n * Gated by `disableMetrics` (default ON). Like the rest of the SDK it is\n * best-effort and never throws to the host page.\n */\nimport { type Meter } from '@opentelemetry/api'\nimport { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'\nimport { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'\nimport { onCLS, onFCP, onINP, onLCP, onTTFB, type Metric } from 'web-vitals'\n\nimport { buildResource } from './resource.js'\nimport { emitSessionEvent, setupOtelLogs } from './otel-logs.js'\nimport type { ObservInitOptions } from './types.js'\n\n/** Export interval for the periodic metric reader (ms). */\nconst METRIC_EXPORT_INTERVAL_MS = 30_000\n\n/** Web-Vital name → histogram spec. CLS is unit-less; the rest are milliseconds. */\nconst VITAL_HISTOGRAMS: Record<string, { name: string; unit: string; description: string }> = {\n LCP: { name: 'web_vitals.lcp', unit: 'ms', description: 'Largest Contentful Paint' },\n INP: { name: 'web_vitals.inp', unit: 'ms', description: 'Interaction to Next Paint' },\n FCP: { name: 'web_vitals.fcp', unit: 'ms', description: 'First Contentful Paint' },\n TTFB: { name: 'web_vitals.ttfb', unit: 'ms', description: 'Time To First Byte' },\n CLS: { name: 'web_vitals.cls', unit: '1', description: 'Cumulative Layout Shift' },\n}\n\n/**\n * Join an endpoint base URL with the OTLP/HTTP metrics path. Mirror of\n * {@link import('./otel-rum.js').buildTracesUrl}.\n */\nexport function buildMetricsUrl(endpoint: string): string {\n try {\n const url = new URL(endpoint)\n url.pathname = `${url.pathname.replace(/\\/+$/, '')}/v1/metrics`\n url.search = ''\n url.hash = ''\n return url.toString()\n } catch {\n return `${endpoint.replace(/\\/+$/, '')}/v1/metrics`\n }\n}\n\nlet provider: MeterProvider | null = null\n\n/** Handle returned by {@link setupOtelMetrics} for teardown. */\nexport interface MetricsHandle {\n meter: Meter\n stop(): Promise<void>\n}\n\n/**\n * Wire up the meter provider + Web-Vitals capture. Idempotent: a second call is a\n * no-op returning the existing handle (first init wins, mirrors `setupOtelLogs`).\n * The `x-observ-key` header is omitted when the key is empty.\n */\nexport function setupOtelMetrics(options: ObservInitOptions): MetricsHandle {\n if (provider) return { meter: provider.getMeter('@observtech/rum'), stop: shutdownOtelMetrics }\n\n const headers: Record<string, string> = {}\n if (options.key) headers['x-observ-key'] = options.key\n\n const reader = new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({ url: buildMetricsUrl(options.endpoint), headers }),\n exportIntervalMillis: METRIC_EXPORT_INTERVAL_MS,\n })\n\n const p = new MeterProvider({ resource: buildResource(options), readers: [reader] })\n const meter = p.getMeter('@observtech/rum')\n\n // Histograms for each Core Web Vital; the logger mirrors them as timeline events.\n const histograms = Object.fromEntries(\n Object.entries(VITAL_HISTOGRAMS).map(([key, h]) => [\n key,\n meter.createHistogram(h.name, { unit: h.unit, description: h.description }),\n ]),\n )\n // Reuse the (idempotent) logs provider so a vital also lands on the session\n // timeline as `observ.session.web_vital`. Never let a logs failure break metrics.\n let logger: ReturnType<typeof setupOtelLogs> | null = null\n try {\n logger = setupOtelLogs(options)\n } catch {\n /* metrics still work without the timeline mirror */\n }\n\n const record = (m: Metric): void => {\n try {\n const path = typeof location !== 'undefined' ? location.pathname : ''\n const dims = {\n 'web_vital.rating': m.rating,\n 'web_vital.navigation_type': m.navigationType,\n 'url.path': path,\n }\n histograms[m.name]?.record(m.value, dims)\n if (logger) {\n emitSessionEvent(logger, 'observ.session.web_vital', {\n 'web_vital.name': m.name,\n 'web_vital.value': String(m.value),\n 'web_vital.id': m.id,\n 'web_vital.rating': m.rating,\n 'web_vital.navigation_type': m.navigationType,\n 'url.path': path,\n })\n }\n } catch {\n /* swallow — a single vital must never break the page */\n }\n }\n\n try {\n onLCP(record)\n onINP(record)\n onFCP(record)\n onTTFB(record)\n onCLS(record)\n } catch {\n /* web-vitals unavailable (e.g. non-browser) — provider still set up */\n }\n\n provider = p\n return { meter, stop: shutdownOtelMetrics }\n}\n\n/**\n * @internal Flush + shut the meter provider down and reset the idempotence guard.\n * Called by the SDK teardown.\n */\nexport async function shutdownOtelMetrics(): Promise<void> {\n const p = provider\n provider = null\n await p?.shutdown()\n}\n\n/** @internal Test-only: the active meter provider, or `null` when metrics are off. */\nexport function getActiveMeterProvider(): MeterProvider | null {\n return provider\n}\n","/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { TracerProvider, MeterProvider } from '@opentelemetry/api';\nimport type { Instrumentation } from './types';\nimport type { LoggerProvider } from '@opentelemetry/api-logs';\n\n/**\n * Enable instrumentations\n * @param instrumentations\n * @param tracerProvider\n * @param meterProvider\n */\nexport function enableInstrumentations(\n instrumentations: Instrumentation[],\n tracerProvider?: TracerProvider,\n meterProvider?: MeterProvider,\n loggerProvider?: LoggerProvider\n): void {\n for (let i = 0, j = instrumentations.length; i < j; i++) {\n const instrumentation = instrumentations[i];\n if (tracerProvider) {\n instrumentation.setTracerProvider(tracerProvider);\n }\n if (meterProvider) {\n instrumentation.setMeterProvider(meterProvider);\n }\n if (loggerProvider && instrumentation.setLoggerProvider) {\n instrumentation.setLoggerProvider(loggerProvider);\n }\n // instrumentations have been already enabled during creation\n // so enable only if user prevented that by setting enabled to false\n // this is to prevent double enabling but when calling register all\n // instrumentations should be now enabled\n if (!instrumentation.getConfig().enabled) {\n instrumentation.enable();\n }\n }\n}\n\n/**\n * Disable instrumentations\n * @param instrumentations\n */\nexport function disableInstrumentations(\n instrumentations: Instrumentation[]\n): void {\n instrumentations.forEach(instrumentation => instrumentation.disable());\n}\n","/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { trace, metrics } from '@opentelemetry/api';\nimport { logs } from '@opentelemetry/api-logs';\nimport {\n disableInstrumentations,\n enableInstrumentations,\n} from './autoLoaderUtils';\nimport type { AutoLoaderOptions } from './types_internal';\n\n/**\n * It will register instrumentations and plugins\n * @param options\n * @return returns function to unload instrumentation and plugins that were\n * registered\n */\nexport function registerInstrumentations(\n options: AutoLoaderOptions\n): () => void {\n const tracerProvider = options.tracerProvider || trace.getTracerProvider();\n const meterProvider = options.meterProvider || metrics.getMeterProvider();\n const loggerProvider = options.loggerProvider || logs.getLoggerProvider();\n const instrumentations = options.instrumentations?.flat() ?? [];\n\n enableInstrumentations(\n instrumentations,\n tracerProvider,\n meterProvider,\n loggerProvider\n );\n\n return () => {\n disableInstrumentations(instrumentations);\n };\n}\n","/**\n * OpenTelemetry RUM wiring for the Observ SDK (FR-4, story 1.3).\n *\n * Sets up a {@link WebTracerProvider} that:\n * - creates `http.client` spans for browser `fetch`/XHR (auto-instrumentation),\n * - stamps every span with the visit's `session.id` (via {@link SessionAttributeSpanProcessor}),\n * - injects the W3C `traceparent` header on outgoing requests (default\n * trace-context propagator; cross-origin gated by `propagateTraceHeaderCorsUrls`),\n * - exports spans over OTLP/HTTP to `{endpoint}/v1/traces` (BatchSpanProcessor).\n *\n * Like the rest of the SDK, this must never throw out to the host page: the\n * caller ({@link import('./index.js')}) wraps {@link setupOtelRum} defensively.\n *\n * NOTE: until the backend OTLP/HTTP receiver lands (story 1.4), exports will fail\n * at the network layer (CORS / 404). That is expected — span creation, attribute\n * stamping and header propagation are fully exercised regardless.\n */\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport { registerInstrumentations } from '@opentelemetry/instrumentation'\nimport { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load'\nimport { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'\nimport { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'\nimport {\n AlwaysOnSampler,\n ParentBasedSampler,\n TraceIdRatioBasedSampler,\n type Sampler,\n} from '@opentelemetry/sdk-trace-base'\nimport {\n BatchSpanProcessor,\n WebTracerProvider,\n type ReadableSpan,\n type Span,\n type SpanProcessor,\n} from '@opentelemetry/sdk-trace-web'\n\nimport { buildResource } from './resource.js'\nimport { SESSION_ID_ATTRIBUTE } from './constants.js'\nimport { getSessionId } from './session.js'\nimport type { ObservInitOptions } from './types.js'\n\n/**\n * Head sampler from `sampleRate`: a `ParentBasedSampler(TraceIdRatioBased)` for a\n * ratio in `[0, 1)`, else `AlwaysOn` (sample everything — the default).\n */\nexport function buildSampler(options: ObservInitOptions): Sampler {\n const rate = options.sampleRate\n if (typeof rate === 'number' && Number.isFinite(rate) && rate >= 0 && rate < 1) {\n return new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(rate) })\n }\n return new AlwaysOnSampler()\n}\n\n/**\n * URLs the auto-instrumentations must NEVER trace: the telemetry pipeline itself\n * (`{endpoint}/v1/...`). Without this the exporter's own POST gets traced,\n * spawning a self-perpetuating trickle of `http.client` spans.\n */\nexport function telemetryIgnoreUrls(endpoint: string): RegExp[] {\n const escaped = endpoint.replace(/\\/+$/, '').replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n return [new RegExp(`${escaped}/v1/`)]\n}\n\n/**\n * Join an endpoint base URL with the OTLP/HTTP traces path. Uses the `URL` API\n * for absolute endpoints so a path prefix is preserved, a trailing slash never\n * doubles, and any query/fragment (meaningless on the traces endpoint) is\n * dropped — `https://x?a=1` ⇒ `https://x/v1/traces`, not `https://x?a=1/v1/traces`.\n * A relative or empty endpoint falls back to a string join, yielding a\n * same-origin `…/v1/traces` path.\n */\nexport function buildTracesUrl(endpoint: string): string {\n try {\n const url = new URL(endpoint)\n url.pathname = `${url.pathname.replace(/\\/+$/, '')}/v1/traces`\n url.search = ''\n url.hash = ''\n return url.toString()\n } catch {\n return `${endpoint.replace(/\\/+$/, '')}/v1/traces`\n }\n}\n\n/**\n * Span processor whose only job is to stamp `session.id` on every span at start.\n *\n * Using a processor (rather than per-instrumentation `applyCustomAttributesOnSpan`)\n * guarantees ALL spans — fetch, XHR, and any future document-load — carry the\n * attribute through a single code path. It reads the id via {@link getSessionId},\n * which also refreshes the session's inactivity window (story 1.2 decision): a\n * span therefore counts as session activity.\n */\nexport class SessionAttributeSpanProcessor implements SpanProcessor {\n onStart(span: Span): void {\n // onStart runs synchronously inside the host page's own fetch/XHR. The SDK\n // must never throw out to the page, so guard here even though getSessionId()\n // is contractually no-throw: a missing attribute beats a broken host request.\n try {\n span.setAttribute(SESSION_ID_ATTRIBUTE, getSessionId())\n } catch {\n /* swallow — never let span stamping break a host request */\n }\n }\n onEnd(_span: ReadableSpan): void {}\n forceFlush(): Promise<void> {\n return Promise.resolve()\n }\n shutdown(): Promise<void> {\n return Promise.resolve()\n }\n}\n\nlet provider: WebTracerProvider | null = null\nlet disableInstrumentations: (() => void) | null = null\nlet activeConfig: { endpoint: string; key: string } | null = null\n\n/**\n * Wire up OTel RUM tracing. Idempotent: a second call is a no-op while a provider\n * is already active (mirrors the single-`init` contract; first call wins). Not\n * defensive on its own — `init` owns the try/catch so a setup failure never\n * escapes to the page — but it cleans up after itself: a partial init (register\n * or instrumentation throwing, e.g. SSR / no `fetch`/XHR) shuts the provider down\n * and resets the guard before rethrowing, so no `WebTracerProvider`\n * (BatchSpanProcessor timer + global registration) leaks and a later retry works.\n */\nexport function setupOtelRum(options: ObservInitOptions): void {\n if (provider) {\n // First init wins. Warn if a later call diverges (key rotation / env switch)\n // so the dropped config isn't silently swallowed by the idempotence guard.\n if (\n activeConfig &&\n (activeConfig.endpoint !== options.endpoint || activeConfig.key !== options.key)\n ) {\n console.warn(\n '[observ] OTel RUM already initialized; ignoring divergent endpoint/key on this init() (first call wins).',\n )\n }\n return\n }\n\n // API-key auth via the custom `x-observ-key` header — the receiver (story 1.4)\n // must decode the same name and list it in `Access-Control-Allow-Headers`. An\n // empty key would produce a malformed credential, so omit the header (and warn)\n // rather than send a blank one.\n const headers: Record<string, string> = {}\n if (options.key) {\n headers['x-observ-key'] = options.key\n } else {\n console.warn('[observ] no API key provided; front-end spans will export unauthenticated.')\n }\n\n const exporter = new OTLPTraceExporter({\n url: buildTracesUrl(options.endpoint),\n headers,\n })\n\n const ignoreUrls = telemetryIgnoreUrls(options.endpoint)\n\n // SDK 2.x: span processors are constructor-only (`addSpanProcessor` was removed).\n // Order matters — stamp `session.id` before the batch processor exports.\n const p = new WebTracerProvider({\n resource: buildResource(options),\n sampler: buildSampler(options),\n spanProcessors: [new SessionAttributeSpanProcessor(), new BatchSpanProcessor(exporter)],\n })\n\n try {\n // Default registration: W3C trace-context propagator + web StackContextManager.\n p.register()\n disableInstrumentations = registerInstrumentations({\n tracerProvider: p,\n instrumentations: [\n // Page-load timing: documentLoad / documentFetch / resourceFetch spans.\n new DocumentLoadInstrumentation(),\n new FetchInstrumentation({\n propagateTraceHeaderCorsUrls: options.propagateTraceHeaderCorsUrls,\n ignoreUrls,\n }),\n new XMLHttpRequestInstrumentation({\n propagateTraceHeaderCorsUrls: options.propagateTraceHeaderCorsUrls,\n ignoreUrls,\n }),\n ],\n })\n } catch (err) {\n // Roll back a partial init so nothing leaks and the guard doesn't latch.\n disableInstrumentations?.()\n disableInstrumentations = null\n void p.shutdown()\n throw err\n }\n\n provider = p\n activeConfig = { endpoint: options.endpoint, key: options.key }\n}\n\n/**\n * @internal Test-only. The active provider, or `null` when tracing is not set\n * up. Lets tests assert idempotence (same instance after a second `setupOtelRum`).\n */\nexport function getActiveProvider(): WebTracerProvider | null {\n return provider\n}\n\n/**\n * @internal Tear down instrumentations and the provider, resetting the\n * idempotence guard. Used by tests (and any future explicit teardown). Not on\n * the public {@link import('./types.js').ObservSdk} surface.\n */\nexport async function shutdownOtelRum(): Promise<void> {\n const p = provider\n // Atomic: even if disabling instrumentations throws, still null the guard and\n // shut the provider down so the BatchSpanProcessor timer can't outlive teardown.\n try {\n disableInstrumentations?.()\n } finally {\n disableInstrumentations = null\n provider = null\n activeConfig = null\n await p?.shutdown()\n }\n}\n","/**\n * PII masking / privacy controls for the heavy rrweb replay flux\n * (Epic 4 — Confidentialité, story 4.1).\n *\n * The heavy replay records the live DOM. By default rrweb captures the *values*\n * users type into inputs (passwords aside, which it already masks) and the\n * visible text. On any real-data surface that is PII — architecture decision D7\n * and PRD Open Q5 require masking to be enabled **before** any real data flows.\n * This module is that gate.\n *\n * SAFE-BY-DEFAULT: with no `privacy` config at all, ALL input values are masked\n * in the replay (`maskAllInputs: true`). A surface that is known to be free of\n * sensitive input can opt out with `privacy: { maskAllInputs: false }`.\n *\n * The defaults map the SDK's small `PrivacyOptions` bag onto rrweb's native\n * masking knobs, and expose three conventional CSS classes so a host page can\n * mark sensitive nodes declaratively without writing custom functions:\n * - `observ-mask` → mask the TEXT of the element (rrweb `maskTextClass`)\n * - `observ-block` → drop the element from the replay entirely (`blockClass`)\n * - `observ-ignore` → record the element but ignore its user input (`ignoreClass`)\n *\n * Channel scope: this governs the HEAVY flux only (rrweb → `/v1/replay`). Masking\n * the LIGHT semantic-event text (`element.text` on `observ.session.click`) is a\n * separate concern handled by a later story (4.2); the two channels are never\n * crossed (enforcement rule).\n */\nimport type { PrivacyOptions } from './types.js'\n\n/** Default class marking elements whose TEXT is masked in the replay (rrweb `maskTextClass`). */\nexport const DEFAULT_MASK_TEXT_CLASS = 'observ-mask'\n/** Default class marking elements dropped entirely from the replay (rrweb `blockClass`). */\nexport const DEFAULT_BLOCK_CLASS = 'observ-block'\n/** Default class marking inputs whose user input events are ignored (rrweb `ignoreClass`). */\nexport const DEFAULT_IGNORE_CLASS = 'observ-ignore'\n\n/**\n * The subset of rrweb `recordOptions` that govern masking. Intentionally a flat,\n * fully-resolved shape (no `undefined` for the always-present fields) so the\n * recorder can spread it straight into `record({ ... })` and tests can assert it.\n */\nexport interface ReplayMaskingOptions {\n /** Mask the value of every `<input>`/`<textarea>`/`<select>` in the replay. */\n maskAllInputs: boolean\n /** Mask the text of elements carrying this class. */\n maskTextClass: string\n /** Drop elements carrying this class from the replay. */\n blockClass: string\n /** Ignore user input on elements carrying this class. */\n ignoreClass: string\n /**\n * When `maskAllText` is on, a selector matching every element so rrweb masks\n * ALL text node content. Omitted otherwise (only class-scoped text masking).\n */\n maskTextSelector?: string\n}\n\n/**\n * Resolve the public {@link PrivacyOptions} into the rrweb masking options,\n * applying the safe-by-default policy. Pure (no DOM/global access) so it is\n * trivially unit-testable and reusable from `init()`.\n */\nexport function resolveReplayMasking(privacy?: PrivacyOptions): ReplayMaskingOptions {\n const resolved: ReplayMaskingOptions = {\n // Safe-by-default: inputs are masked unless the host *explicitly* opts out.\n maskAllInputs: privacy?.maskAllInputs ?? true,\n maskTextClass: privacy?.maskTextClass ?? DEFAULT_MASK_TEXT_CLASS,\n blockClass: privacy?.blockClass ?? DEFAULT_BLOCK_CLASS,\n ignoreClass: privacy?.ignoreClass ?? DEFAULT_IGNORE_CLASS,\n }\n // Full-text masking is opt-in: it heavily degrades replay usefulness, so it is\n // off by default and only class-scoped text masking applies. '*' tells rrweb\n // to treat every element as a masked-text element.\n if (privacy?.maskAllText) resolved.maskTextSelector = '*'\n return resolved\n}\n","/**\n * Replay chunk transport for the Observ RUM SDK (FR-2/FR-5, story 2.2).\n *\n * The heavy rrweb flux travels on its OWN channel — `POST /v1/replay` — never\n * through the OTLP exporter (architecture: the two channels are never crossed).\n * The body is the raw gzip bytes of a `.jsonl.gz` chunk; the backend (story 2.1)\n * stores it verbatim and rejects an empty body (400), so we never POST one.\n *\n * Contract frozen by story 2.1:\n * POST {endpoint}/v1/replay?session_id=<id>&seq=<n>\n * headers: x-observ-key: <key>, content-type: application/octet-stream\n * body: gzip bytes (≤ 16 MiB)\n */\n\n/**\n * Join an endpoint base URL with the `/v1/replay` path. Mirror of\n * {@link import('./otel-rum.js').buildTracesUrl}: the `URL` API preserves a path\n * prefix, never doubles a trailing slash, and drops any query/fragment; a\n * relative or empty endpoint falls back to a same-origin path.\n */\nexport function buildReplayUrl(endpoint: string): string {\n try {\n const url = new URL(endpoint)\n url.pathname = `${url.pathname.replace(/\\/+$/, '')}/v1/replay`\n url.search = ''\n url.hash = ''\n return url.toString()\n } catch {\n return `${endpoint.replace(/\\/+$/, '')}/v1/replay`\n }\n}\n\n/**\n * Gzip newline-delimited JSON lines via the native `CompressionStream` — no\n * dependency (resolves the story 1.1 gzip-strategy deferral). Available in every\n * evergreen browser and Node ≥ 18. Uses the `Blob.stream()` → `pipeThrough` →\n * `Response.arrayBuffer()` pipeline so there is no manual writer/reader (no\n * unhandled rejection, no backpressure deadlock). The output is the gzip stream,\n * stored as-is by the backend.\n */\nexport async function gzipJsonl(lines: string[]): Promise<Uint8Array> {\n const data = new TextEncoder().encode(lines.join('\\n'))\n const stream = new Blob([data as BlobPart]).stream().pipeThrough(new CompressionStream('gzip'))\n const buf = await new Response(stream).arrayBuffer()\n return new Uint8Array(buf)\n}\n\n/** Arguments for {@link postReplayChunk}. */\nexport interface PostReplayChunkOptions {\n /** Replay endpoint URL (from {@link buildReplayUrl}). */\n url: string\n /** API key for the `x-observ-key` header (omitted when empty). */\n key: string\n /** Current visit `session.id` (query param, keys the object-store prefix). */\n sessionId: string\n /** Monotonic chunk sequence within the session (query param). */\n seq: number\n /** Gzip bytes of the chunk. An empty body is never sent (server returns 400). */\n body: Uint8Array\n /** Use `fetch` keepalive for an unload flush (body capped ~64 KiB by the spec). */\n keepalive?: boolean\n}\n\n/**\n * POST one gzip replay chunk. **Best-effort: never throws** — a network failure\n * degrades to \"this chunk is lost\", it must not break the host page.\n */\nexport async function postReplayChunk(opts: PostReplayChunkOptions): Promise<void> {\n if (opts.body.length === 0) return // backend rejects an empty body (400)\n\n const headers: Record<string, string> = { 'content-type': 'application/octet-stream' }\n // Mirror otel-rum.ts: a blank key would be a malformed credential — omit it.\n if (opts.key) headers['x-observ-key'] = opts.key\n\n const url = `${opts.url}?session_id=${encodeURIComponent(opts.sessionId)}&seq=${opts.seq}`\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers,\n body: opts.body as BodyInit,\n keepalive: opts.keepalive,\n })\n // `fetch` only rejects on network failure — surface server rejections\n // (400 empty / 401 auth / 413 over the 16 MiB cap) instead of silent success.\n if (!res.ok) {\n console.warn(`[observ] replay chunk rejected: HTTP ${res.status}`)\n }\n } catch (err) {\n console.warn('[observ] replay chunk upload failed', err)\n }\n}\n","/**\n * rrweb recording + chunked replay upload for the Observ RUM SDK (FR-2, story 2.2).\n *\n * Captures the visual session with `@rrweb/record` (initial FullSnapshot + DOM\n * mutations), buffers events in memory, and POSTs gzip chunks to `/v1/replay`\n * (story 2.1) — triggered by SIZE or TIME, whichever comes first. The heavy flux\n * uses this channel exclusively, never the OTLP exporter.\n *\n * Invariant (1.1/1.2/1.3): the SDK must NEVER break the host page. Every path —\n * capture, serialize, gzip, upload — is defensive: a failure degrades to \"no\n * replay\", never an exception that escapes to the page.\n *\n * Flushes are SERIALIZED through a promise chain (`pending`): every trigger is\n * queued — never dropped — so the buffer cannot grow unbounded behind an\n * in-flight flush, `seq` is assigned in order, and two uploads never overlap.\n *\n * Session activity (1.2 deferral): `getSessionId` is read once per flush (~5 s),\n * NOT per rrweb event — touching the session on every high-frequency mutation\n * would thrash `sessionStorage` and jank the main thread.\n */\nimport { record } from '@rrweb/record'\n\nimport { resolveReplayMasking } from './privacy.js'\nimport { getSessionId } from './session.js'\nimport { buildReplayUrl, gzipJsonl, postReplayChunk } from './transport.js'\nimport type { ObservInitOptions } from './types.js'\n\n/** Serialized buffer size (pre-gzip) that triggers a size-based flush. */\nexport const CHUNK_MAX_BYTES = 512 * 1024 // ~512 KiB — well under the 16 MiB cap\n/** Interval (ms) for the time-based flush. */\nexport const CHUNK_INTERVAL_MS = 5_000\n/** rrweb full-snapshot cadence so a reader can resync from a checkpoint. */\nexport const CHECKOUT_INTERVAL_MS = 5 * 60 * 1_000\n\n/** Public handle for the running recorder. */\nexport interface ReplayRecorder {\n /** Stop capture, remove listeners, and flush the residual. Idempotent. */\n stop(): Promise<void>\n}\n\n/** No-op recorder returned in a non-DOM environment (SSR) — nothing runs. */\nconst NOOP_RECORDER: ReplayRecorder = { stop: () => Promise.resolve() }\n\n/**\n * Start rrweb capture + chunked upload. Returns a handle whose `stop()` tears\n * everything down and flushes the tail. Never throws: a capture/start failure is\n * swallowed (the page keeps working, just without replay).\n */\nexport function startReplayRecording(options: ObservInitOptions): ReplayRecorder {\n // SSR / non-DOM guard: rrweb and the lifecycle listeners need `document` /\n // `window`. Degrade to a no-op recorder (mirrors session.ts's globalThis-\n // defensive style) rather than throwing a ReferenceError / leaking a timer.\n if (typeof document === 'undefined' || typeof window === 'undefined') {\n return NOOP_RECORDER\n }\n\n const url = buildReplayUrl(options.endpoint)\n const key = options.key\n\n let buffer: string[] = []\n let bufferedBytes = 0\n let seq = 0\n let lastSessionId: string | null = null\n let stopped = false\n // Flush chain: each trigger appends to this promise so flushes run one after\n // another (never concurrently, never dropped). Kept reject-free by the inner\n // try/catch + the trailing `.catch`.\n let pending: Promise<void> = Promise.resolve()\n\n /** Serialize → gzip → upload the current buffer. Swapped before the first await. */\n async function doFlush(keepalive: boolean): Promise<void> {\n if (buffer.length === 0) return\n const lines = buffer\n buffer = []\n bufferedBytes = 0\n try {\n const body = await gzipJsonl(lines)\n const sessionId = getSessionId()\n // The session id rotated (idle tab past the 30-min window): restart `seq`\n // so the new object-store prefix begins at 0 (its own checkpoint).\n if (lastSessionId !== null && lastSessionId !== sessionId) seq = 0\n lastSessionId = sessionId\n await postReplayChunk({ url, key, sessionId, seq: seq++, body, keepalive })\n } catch (err) {\n console.warn('[observ] replay flush failed', err)\n }\n }\n\n /** Queue a flush after any in-flight one (never dropped). */\n function enqueueFlush(keepalive = false): void {\n pending = pending.then(() => doFlush(keepalive)).catch(() => {})\n }\n\n const emit = (event: unknown): void => {\n if (stopped) return // drop trailing events after teardown\n try {\n const line = JSON.stringify(event)\n buffer.push(line)\n bufferedBytes += line.length\n if (bufferedBytes >= CHUNK_MAX_BYTES) enqueueFlush()\n } catch {\n /* never let capture break the host page */\n }\n }\n\n // PII masking (story 4.1): safe-by-default — input values are masked unless\n // the host explicitly opts out via `privacy.maskAllInputs: false`. Resolved\n // once here and spread into the recorder config so the heavy replay flux is\n // privacy-safe by construction (architecture D7 / PRD Open Q5).\n const masking = resolveReplayMasking(options.privacy)\n\n let stopFn: (() => void) | undefined\n try {\n // `record` returns the stop handler (or undefined if unsupported).\n stopFn = record({ emit, checkoutEveryNms: CHECKOUT_INTERVAL_MS, ...masking }) ?? undefined\n } catch (err) {\n console.warn('[observ] rrweb recording failed to start', err)\n }\n\n const interval = setInterval(() => enqueueFlush(), CHUNK_INTERVAL_MS)\n\n // Lifecycle flush (1.3 deferral). visibilitychange→hidden fires on tab\n // switch / minimize / most mobile navigations while the page is still alive;\n // pagehide is the best-effort last resort (keepalive — a body the browser\n // can't keepalive-send is rejected and logged, not silently pre-dropped).\n const onVisibility = (): void => {\n if (document.visibilityState === 'hidden') enqueueFlush()\n }\n const onPageHide = (): void => {\n enqueueFlush(true)\n }\n document.addEventListener('visibilitychange', onVisibility)\n window.addEventListener('pagehide', onPageHide)\n\n return {\n async stop(): Promise<void> {\n if (stopped) return\n stopped = true\n clearInterval(interval)\n document.removeEventListener('visibilitychange', onVisibility)\n window.removeEventListener('pagehide', onPageHide)\n try {\n stopFn?.()\n } catch {\n /* swallow — teardown must not throw */\n }\n enqueueFlush() // final residual\n await pending // wait for the whole chain (incl. the final flush) to settle\n },\n }\n}\n","/**\n * Minimal, defensive CSS-selector + text derivation for semantic events\n * (story 2.3). Produces a short, reasonably stable selector for a clicked\n * element and a truncated text label. Never throws (the SDK must never break\n * the host page); degrades to the tag name or `''`.\n */\n\nconst MAX_TEXT = 100\nconst MAX_CLASSES = 3\nconst MAX_CLASS_LEN = 30\nconst MAX_DEPTH = 4\n\n/**\n * Build a short CSS selector for `el`:\n * - `tag#id` when the element has an id,\n * - else `tag.class1.class2` (≤ 3 short classes),\n * - else a depth-bounded `tag:nth-of-type(n)` ancestor path (≤ 4 levels).\n * Returns `''` for a non-element / null, or the bare tag name on any failure.\n */\nexport function cssSelector(el: Element | null): string {\n if (!el || el.nodeType !== 1) return ''\n try {\n // `localName` is lowercase for HTML but case-preserved for SVG\n // (`linearGradient`, `clipPath`) — `tagName.toLowerCase()` would corrupt those.\n const tag = el.localName\n if (el.id) return `${tag}#${el.id}`\n\n const classes = Array.from(el.classList)\n .filter((c) => c.length > 0 && c.length <= MAX_CLASS_LEN)\n .slice(0, MAX_CLASSES)\n if (classes.length > 0) return `${tag}.${classes.join('.')}`\n\n // Depth-bounded structural path.\n const parts: string[] = []\n let node: Element | null = el\n let depth = 0\n while (node && node.nodeType === 1 && depth < MAX_DEPTH) {\n const t = node.localName\n if (node.id) {\n parts.unshift(`${t}#${node.id}`)\n break\n }\n const parent: Element | null = node.parentElement\n if (parent) {\n const current = node\n const sameTag = Array.from(parent.children).filter((c) => c.tagName === current.tagName)\n if (sameTag.length > 1) {\n parts.unshift(`${t}:nth-of-type(${sameTag.indexOf(current) + 1})`)\n } else {\n parts.unshift(t)\n }\n } else {\n parts.unshift(t)\n }\n node = node.parentElement\n depth++\n }\n return parts.join(' > ')\n } catch {\n return el.localName ?? ''\n }\n}\n\n/** Trimmed, whitespace-collapsed, truncated `textContent` of `el` (≤ 100 chars). */\nexport function elementText(el: Element | null): string {\n if (!el) return ''\n try {\n const text = (el.textContent ?? '').trim().replace(/\\s+/g, ' ')\n return text.length > MAX_TEXT ? text.slice(0, MAX_TEXT) : text\n } catch {\n return ''\n }\n}\n","/**\n * Semantic event derivation for the Observ RUM SDK (FR-3 / D8, story 2.3).\n *\n * Derives high-value interaction facts from native DOM listeners and emits them\n * as OTel log records (`observ.session.*`) on the logs channel (`/v1/logs`):\n * - `observ.session.click` — every click, with `element.selector` / `element.text`\n * - `observ.session.rage_click` — ≥ 3 clicks < 1 s on the SAME selector\n * - `observ.session.navigate` — SPA navigation (`pushState`/`replaceState`/`popstate`), `url`\n *\n * Variance vs architecture D8 (\"tap rrweb emit\"): we use native DOM listeners\n * rather than reverse-mapping rrweb node ids — `element.selector`/`element.text`\n * are derived directly and reliably from `event.target`. Same OTel-logs output.\n *\n * Invariant (1.1/1.2/1.3): never break the host page. Listeners run in the\n * page's own context, so every handler is `try/catch`-guarded; the patched\n * `history` methods are restored on `stop()` (no leaked global).\n */\nimport type { Logger } from '@opentelemetry/api-logs'\n\nimport { emitSessionEvent, setupOtelLogs } from './otel-logs.js'\nimport { cssSelector, elementText } from './selector.js'\nimport type { ObservInitOptions } from './types.js'\n\n/** Rage-click window (ms) and click threshold (architecture D8 / AC#2). */\nexport const RAGE_CLICK_WINDOW_MS = 1_000\nexport const RAGE_CLICK_THRESHOLD = 3\n/** Cap on tracked selectors so the rage-click map can't grow unbounded over a\n * long session (cleared past this — rage detection is best-effort). */\nconst MAX_TRACKED_SELECTORS = 100\n\n/** Module-level guard so the tap is self-idempotent (a second start without a\n * stop() between would double-patch `history` / double-add listeners). */\nlet active: SemanticEventsHandle | null = null\n\n/** Public handle for the running semantic-event tap. */\nexport interface SemanticEventsHandle {\n /** Remove listeners, restore patched `history`, shut the logs provider down. */\n stop(): Promise<void>\n}\n\nconst NOOP: SemanticEventsHandle = { stop: () => Promise.resolve() }\n\n/** Resolve an `EventTarget` to the nearest `Element` (text/SVG nodes → parent). */\nfunction toElement(target: EventTarget | null): Element | null {\n if (target instanceof Element) return target\n const node = target as Node | null\n return node && node.parentElement ? node.parentElement : null\n}\n\n/**\n * Start deriving + emitting semantic events. Returns a handle whose `stop()`\n * tears everything down. Never throws; degrades to a no-op in a non-DOM env.\n * NOTE: not gated by `disableReplay` — semantic events are useful even without\n * the heavy replay capture.\n */\nexport function startSemanticEvents(options: ObservInitOptions): SemanticEventsHandle {\n if (active) return active\n if (typeof document === 'undefined' || typeof window === 'undefined') return NOOP\n\n let logger: Logger\n try {\n logger = setupOtelLogs(options)\n } catch (err) {\n console.warn('[observ] semantic events: logs setup failed', err)\n return NOOP\n }\n\n // Recent click timestamps per selector, for rage-click clustering.\n const clickTimes = new Map<string, number[]>()\n\n const onClick = (event: Event): void => {\n try {\n const el = toElement(event.target)\n if (!el) return\n const selector = cssSelector(el)\n emitSessionEvent(logger, 'observ.session.click', {\n 'element.selector': selector,\n 'element.text': elementText(el),\n })\n\n // Rage-click: ≥ THRESHOLD clicks within WINDOW on the same selector.\n const now = Date.now()\n const recent = (clickTimes.get(selector) ?? []).filter((t) => now - t < RAGE_CLICK_WINDOW_MS)\n recent.push(now)\n if (recent.length >= RAGE_CLICK_THRESHOLD) {\n emitSessionEvent(logger, 'observ.session.rage_click', { 'element.selector': selector })\n clickTimes.delete(selector) // reset the burst (one rage_click per burst)\n } else {\n // Bound the map: a selector clicked once never reaches the threshold and\n // would otherwise linger forever. Clear past the cap (rare; best-effort).\n if (clickTimes.size > MAX_TRACKED_SELECTORS) clickTimes.clear()\n clickTimes.set(selector, recent)\n }\n } catch {\n /* never let a click handler break the host page */\n }\n }\n\n const emitNavigate = (): void => {\n emitSessionEvent(logger, 'observ.session.navigate', { url: location.href })\n }\n const onPopState = (): void => emitNavigate()\n\n document.addEventListener('click', onClick, { capture: true })\n window.addEventListener('popstate', onPopState)\n\n // No native event fires for pushState/replaceState — patch them (and restore\n // on stop, so the global is never left monkey-patched).\n const origPushState = history.pushState\n const origReplaceState = history.replaceState\n let historyPatched = false\n try {\n history.pushState = function (this: History, ...args: Parameters<History['pushState']>): void {\n origPushState.apply(this, args)\n emitNavigate()\n }\n history.replaceState = function (\n this: History,\n ...args: Parameters<History['replaceState']>\n ): void {\n origReplaceState.apply(this, args)\n emitNavigate()\n }\n historyPatched = true\n } catch {\n /* leave history unpatched — popstate still works */\n }\n\n let stopped = false\n const handle: SemanticEventsHandle = {\n async stop(): Promise<void> {\n if (stopped) return\n stopped = true\n active = null\n document.removeEventListener('click', onClick, { capture: true })\n window.removeEventListener('popstate', onPopState)\n if (historyPatched) {\n try {\n history.pushState = origPushState\n history.replaceState = origReplaceState\n } catch {\n /* swallow — teardown must not throw */\n }\n }\n // The shared logs provider is shut down once by index.shutdown() (story\n // 2.4) — not here, since js-errors shares it.\n },\n }\n active = handle\n return handle\n}\n","import { installBaggagePropagation } from './baggage.js'\nimport { startJsErrorCapture, type JsErrorHandle } from './js-errors.js'\nimport { shutdownOtelLogs } from './otel-logs.js'\nimport { setupOtelMetrics, type MetricsHandle } from './otel-metrics.js'\nimport { setupOtelRum, shutdownOtelRum, telemetryIgnoreUrls } from './otel-rum.js'\nimport { startReplayRecording, type ReplayRecorder } from './rrweb-record.js'\nimport { startSemanticEvents, type SemanticEventsHandle } from './semantic-events.js'\nimport { ensureSession, getSessionId } from './session.js'\nimport type { ObservInitOptions, ObservSdk } from './types.js'\n\nexport type { ObservInitOptions, ObservSdk } from './types.js'\n\n/**\n * Attribute key used everywhere to correlate signals of a single session.\n * Re-exported from {@link ./constants.js} (the leaf module that internal modules\n * also import, avoiding an `index ↔ otel-rum` cycle). EXACTLY `session.id`.\n */\nexport { SESSION_ID_ATTRIBUTE } from './constants.js'\n\n/** Singleton replay recorder handle (one capture per page, like the OTel provider). */\nlet replayRecorder: ReplayRecorder | null = null\n/** Singleton semantic-events tap (story 2.3). */\nlet semanticEvents: SemanticEventsHandle | null = null\n/** Singleton JS-error capture (story 2.4). */\nlet jsErrors: JsErrorHandle | null = null\n/** Singleton metrics handle — Core Web Vitals + OTLP metrics (parity). */\nlet metrics: MetricsHandle | null = null\n/** Baggage-propagation uninstaller, set when `propagateBaggage` is on (parity). */\nlet baggageUninstall: (() => void) | null = null\n\n/**\n * Initialize the Observ RUM SDK.\n *\n * 1. Establishes (or resumes) the visit's `session.id` — the single\n * correlation key shared by every signal (story 1.2).\n * 2. Wires OTel RUM tracing (story 1.3): `http.client` spans for fetch/XHR,\n * carrying `session.id`, with W3C `traceparent` propagation and OTLP/HTTP\n * export to `{endpoint}/v1/traces`.\n * 3. Starts rrweb replay capture (story 2.2): buffered gzip chunks POSTed to\n * `{endpoint}/v1/replay` (size/time triggered), unless `disableReplay`.\n * 4. Derives semantic events (story 2.3): `click` / `rage_click` / `navigate`\n * emitted as OTel log records to `{endpoint}/v1/logs` (independent of\n * `disableReplay`).\n * 5. Captures JS errors (story 2.4): `observ.session.js_error` (uncaught\n * errors + unhandled rejections) on the same logs channel.\n *\n * Idempotent (a second call re-registers nothing) and never throws — any setup\n * failure is swallowed so the SDK can never break the host page; it degrades to\n * \"no tracing / no replay / no semantic events\".\n */\nexport function init(options: ObservInitOptions): void {\n try {\n ensureSession()\n setupOtelRum(options)\n if (!options.disableReplay && !replayRecorder) {\n replayRecorder = startReplayRecording(options)\n }\n // Semantic events + JS errors are useful even without replay → not gated.\n if (!semanticEvents) {\n semanticEvents = startSemanticEvents(options)\n }\n if (!jsErrors) {\n jsErrors = startJsErrorCapture(options)\n }\n // Metrics + Core Web Vitals (parity) — independent of replay; opt-out via `disableMetrics`.\n if (!options.disableMetrics && !metrics) {\n metrics = setupOtelMetrics(options)\n }\n // Baggage: stamp `session.id` on requests to the same backends that receive\n // `traceparent` (+ same-origin), so backend spans can read the session identity.\n if (options.propagateBaggage && !baggageUninstall) {\n const backends = [...(options.propagateTraceHeaderCorsUrls ?? [])]\n if (typeof location !== 'undefined') backends.push(location.origin)\n baggageUninstall = installBaggagePropagation(\n getSessionId,\n backends,\n telemetryIgnoreUrls(options.endpoint),\n )\n }\n } catch (err) {\n // The SDK must never break the host page (1.1/1.2 guarantee). Degrade to\n // \"no tracing / no replay / no semantic events\" rather than propagating.\n console.warn('[observ] RUM setup failed; tracing/replay/events degraded.', err)\n }\n}\n\n/**\n * Stop replay capture (flushing the residual chunk), the semantic-event tap, and\n * tear down OTel tracing + logs. Best-effort and never throws (resolves the\n * story 1.3 \"no public teardown\" deferral). After this, a later {@link init} can\n * re-start the SDK.\n */\nexport async function shutdown(): Promise<void> {\n const recorder = replayRecorder\n const events = semanticEvents\n const errors = jsErrors\n const metricsHandle = metrics\n const uninstallBaggage = baggageUninstall\n replayRecorder = null\n semanticEvents = null\n jsErrors = null\n metrics = null\n baggageUninstall = null\n try {\n uninstallBaggage?.()\n } catch {\n /* swallow — restoring window.fetch must never throw */\n }\n try {\n await recorder?.stop()\n } catch {\n /* swallow — teardown must never throw to the host page */\n }\n try {\n await events?.stop()\n } catch {\n /* swallow */\n }\n try {\n errors?.stop()\n } catch {\n /* swallow */\n }\n try {\n await metricsHandle?.stop()\n } catch {\n /* swallow */\n }\n // Both the semantic-events tap and the JS-error capture share ONE logs\n // provider — shut it down once, here, after both taps have stopped. Each\n // provider shutdown is guarded so a flush rejection can't skip the next one\n // or escape to the host page (shutdown must never throw — 1.3 guarantee).\n try {\n await shutdownOtelLogs()\n } catch {\n /* swallow */\n }\n try {\n await shutdownOtelRum()\n } catch {\n /* swallow */\n }\n}\n\n/** Singleton SDK handle. Usage: `observ.init({ endpoint, key })`. */\nexport const observ: ObservSdk = { init, shutdown }\n\nexport default observ\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@observtech/rum",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
4
4
|
"description": "Observ browser RUM + Session Replay SDK (rrweb + OpenTelemetry, replay-as-context).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Observ",
|
|
@@ -67,14 +67,19 @@
|
|
|
67
67
|
"@opentelemetry/api": "1.9.1",
|
|
68
68
|
"@opentelemetry/api-logs": "0.218.0",
|
|
69
69
|
"@opentelemetry/exporter-logs-otlp-http": "0.218.0",
|
|
70
|
+
"@opentelemetry/exporter-metrics-otlp-http": "0.218.0",
|
|
70
71
|
"@opentelemetry/exporter-trace-otlp-http": "0.218.0",
|
|
71
72
|
"@opentelemetry/instrumentation-document-load": "0.63.0",
|
|
72
73
|
"@opentelemetry/instrumentation-fetch": "0.218.0",
|
|
73
74
|
"@opentelemetry/instrumentation-xml-http-request": "0.218.0",
|
|
75
|
+
"@opentelemetry/resources": "2.7.1",
|
|
74
76
|
"@opentelemetry/sdk-logs": "0.218.0",
|
|
77
|
+
"@opentelemetry/sdk-metrics": "2.7.1",
|
|
78
|
+
"@opentelemetry/sdk-trace-base": "2.7.1",
|
|
75
79
|
"@opentelemetry/sdk-trace-web": "2.7.1",
|
|
76
80
|
"@opentelemetry/semantic-conventions": "1.41.1",
|
|
77
81
|
"@rrweb/record": "2.0.1",
|
|
78
|
-
"rrweb": "2.0.1"
|
|
82
|
+
"rrweb": "2.0.1",
|
|
83
|
+
"web-vitals": "4.2.4"
|
|
79
84
|
}
|
|
80
85
|
}
|