@owlpane/browser 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Owlpane
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @owlpane/browser
2
+
3
+ Zero-dependency real-user-monitoring SDK for browsers. Sends OTLP/HTTP JSON traces to the Owlpane ingest gateway.
4
+
5
+ ## Install and use
6
+
7
+ ```ts
8
+ import { init } from "@owlpane/browser";
9
+
10
+ init({
11
+ endpoint: "https://ingest.example.com", // `/v1/traces` is appended
12
+ projectKey: "owl_ing_...", // sent as `Authorization: Bearer <key>`
13
+ serviceName: "web-shop",
14
+ environment: "production",
15
+ release: "1.4.2",
16
+ sampleRate: 1, // 0..1, decided per trace
17
+ propagateTraceTo: ["https://api.example.com", /^https:\/\/[a-z]+\.internal\.io\//],
18
+ });
19
+ ```
20
+
21
+ Script tag: `npm run build:iife -w @owlpane/browser` produces `dist/owlpane-browser.iife.js` (global `OwlpaneBrowser`). It uses `esbuild`, which is not a dependency of this package; it must be present in the workspace `node_modules`.
22
+
23
+ `init()` never throws, is a no-op if called twice, and returns `{ sessionId, flush(), shutdown(), captureException(err) }`.
24
+
25
+ ## What it captures
26
+
27
+ | Signal | Span name | Notes |
28
+ | --- | --- | --- |
29
+ | Page load | `documentLoad` | Root span of the page-view trace, navigation start to `loadEventEnd`. Phase timings are attributes, not child spans. |
30
+ | Web Vitals | `webvital.LCP`, `webvital.CLS`, `webvital.INP` | Spans (not metrics) so they need no extra ingest path and share the page trace. Reported once, when the page becomes hidden. Child of `documentLoad`. |
31
+ | JS errors | `exception` | `error` and `unhandledrejection`, with an `exception` span event (`exception.type`, `exception.message`, `exception.stacktrace`). Capped at 20 per page view. |
32
+ | fetch / XHR | `HTTP <METHOD>` | Client spans, each its own trace. |
33
+
34
+ ### Attributes
35
+
36
+ Resource: `service.name`, `service.version`, `deployment.environment.name`, `deployment.environment`, `telemetry.sdk.name=owlpane-browser`, `telemetry.sdk.language=webjs`, `telemetry.sdk.version`, `owlpane.rum=true`.
37
+
38
+ Every span: `owlpane.rum="true"`, `session.id`, `browser.page.url` (origin + path), `browser.page.path`, `user_agent.original`, `browser.language`, `browser.platform`, `browser.mobile`.
39
+
40
+ - `documentLoad`: `browser.navigation.type`, `browser.timing.{redirect,dns,connect,ttfb,response,dom_interactive,dom_content_loaded,load}_ms`, `browser.timing.transfer_size_bytes`.
41
+ - `webvital.*`: `webvital.name`, `webvital.value`, `webvital.unit` (`ms` or `score`), `webvital.rating` (`good|needs-improvement|poor`), INP also `webvital.interactions`.
42
+ - `exception`: `owlpane.error.source` (`onerror|unhandledrejection|manual`), `code.filepath`, `code.lineno`, `code.column`; span status ERROR.
43
+ - HTTP: `http.request.method`, `url.full`, `server.address`, `http.response.status_code`, `error.type`; status ERROR for >= 400 or network failure.
44
+
45
+ ## Trace propagation
46
+
47
+ A W3C `traceparent` header is added to requests to the page's own origin, and to any URL matching `propagateTraceTo` (string = URL prefix, RegExp = tested against the absolute URL). An existing `traceparent` is left alone. Cross-origin targets must allow the header in their CORS config (`Access-Control-Allow-Headers: traceparent`), otherwise the browser will fail the preflight and the request itself breaks. Only list origins you control.
48
+
49
+ ## Privacy
50
+
51
+ Query strings, hashes and URL credentials are stripped from recorded URLs. `keepQuery: true` keeps the query (still never the hash). Request/response bodies and headers are never recorded. Page paths are recorded as-is, so avoid putting identifiers in paths you do not want stored.
52
+
53
+ ## Delivery and limits
54
+
55
+ - Batched every 5s or 100 spans, bodies capped at 60,000 bytes (browsers limit `keepalive` bodies to 64 KiB), queue capped at 500 spans (oldest dropped).
56
+ - Uses `fetch` with `keepalive: true` and flushes on `pagehide` and when the page becomes hidden. `navigator.sendBeacon` is deliberately not used: it cannot set an `Authorization` header.
57
+ - Failures are dropped silently, no retry. Telemetry is best-effort.
58
+ - The export request is never instrumented (the original `fetch` is captured before patching and the ingest endpoint is skipped).
59
+ - The project key is visible to anyone who loads your page. Use a dedicated, revocable key for the browser.
60
+ - The ingest gateway must answer CORS preflight for the browser to deliver anything (see the gateway notes from the platform team).
61
+ - INP uses the `event` timing observer, LCP/CLS the standard entry types; unsupported browsers (e.g. Safari for INP/LCP in some versions) simply report fewer vitals. Values are approximations of the `web-vitals` library, not bit-identical. Vitals are reported once per page view (no SPA soft-navigation vitals).
62
+ - XHR instrumentation patches `XMLHttpRequest.prototype`; it does not capture `sendBeacon`, WebSocket or resource-loading errors.
63
+ - SPA route changes are not tracked as separate page views; `browser.page.path` reflects the path at span end.
64
+
65
+ ## Tests
66
+
67
+ `npm test -w @owlpane/browser` (node:test + tsx, no DOM library; browser globals are injected fakes).
@@ -0,0 +1,13 @@
1
+ import { type LogFetchLike } from "./log-otlp.js";
2
+ import type { Attrs } from "./otlp.js";
3
+ export type DomReplayOptions = {
4
+ logsUrl: string;
5
+ projectKey: string;
6
+ sessionId: string;
7
+ resource: Attrs;
8
+ fetch: LogFetchLike | undefined;
9
+ /** Max rrweb events buffered before flush (default 8). */
10
+ batchSize?: number;
11
+ };
12
+ /** Starts rrweb recording and ships events as OTLP logs. Returns stop handle. */
13
+ export declare function startDomReplay(opts: DomReplayOptions): () => void;
@@ -0,0 +1,40 @@
1
+ import { buildLogsRequestBody, postLogs } from "./log-otlp.js";
2
+ /** Starts rrweb recording and ships events as OTLP logs. Returns stop handle. */
3
+ export function startDomReplay(opts) {
4
+ let seq = 0;
5
+ let buffer = [];
6
+ let stopRecord;
7
+ const flush = async () => {
8
+ if (!buffer.length)
9
+ return;
10
+ const batch = buffer;
11
+ buffer = [];
12
+ const records = batch.map((ev) => ({
13
+ timeMs: ev.timestamp,
14
+ body: JSON.stringify(ev),
15
+ attributes: {
16
+ "owlpane.replay.kind": "rrweb",
17
+ "owlpane.replay.session_id": opts.sessionId,
18
+ "owlpane.replay.seq": seq++,
19
+ },
20
+ }));
21
+ const body = buildLogsRequestBody(opts.resource, records);
22
+ await postLogs(opts.logsUrl, opts.projectKey, body, opts.fetch);
23
+ };
24
+ void import("rrweb").then(({ record }) => {
25
+ stopRecord = record({
26
+ emit(event) {
27
+ buffer.push(event);
28
+ if (buffer.length >= (opts.batchSize ?? 8))
29
+ void flush();
30
+ },
31
+ maskAllInputs: true,
32
+ blockClass: "owlpane-replay-block",
33
+ });
34
+ });
35
+ return () => {
36
+ stopRecord?.();
37
+ void flush();
38
+ };
39
+ }
40
+ //# sourceMappingURL=dom-replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom-replay.js","sourceRoot":"","sources":["../src/dom-replay.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAqB,MAAM,eAAe,CAAC;AAelF,iFAAiF;AACjF,MAAM,UAAU,cAAc,CAAC,IAAsB;IACnD,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,MAAM,GAAiB,EAAE,CAAC;IAC9B,IAAI,UAAoC,CAAC;IAEzC,MAAM,KAAK,GAAG,KAAK,IAAI,EAAE;QACvB,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,OAAO;QAC3B,MAAM,KAAK,GAAG,MAAM,CAAC;QACrB,MAAM,GAAG,EAAE,CAAC;QACZ,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YACjC,MAAM,EAAE,EAAE,CAAC,SAAS;YACpB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YACxB,UAAU,EAAE;gBACV,qBAAqB,EAAE,OAAO;gBAC9B,2BAA2B,EAAE,IAAI,CAAC,SAAS;gBAC3C,oBAAoB,EAAE,GAAG,EAAE;aAC5B;SACF,CAAC,CAAC,CAAC;QACJ,MAAM,IAAI,GAAG,oBAAoB,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC1D,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAClE,CAAC,CAAC;IAEF,KAAK,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE;QACvC,UAAU,GAAG,MAAM,CAAC;YAClB,IAAI,CAAC,KAAK;gBACR,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACnB,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;oBAAE,KAAK,KAAK,EAAE,CAAC;YAC3D,CAAC;YACD,aAAa,EAAE,IAAI;YACnB,UAAU,EAAE,sBAAsB;SACnC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,EAAE;QACV,UAAU,EAAE,EAAE,CAAC;QACf,KAAK,KAAK,EAAE,CAAC;IACf,CAAC,CAAC;AACJ,CAAC"}
package/dist/env.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ /** Everything the SDK needs from the browser, injectable so it can be tested with fakes. */
2
+ export type Env = {
3
+ /** Object whose `fetch` we patch (window). */
4
+ global: Record<string, any>;
5
+ fetch?: (...a: any[]) => Promise<any>;
6
+ Headers?: any;
7
+ Request?: any;
8
+ XMLHttpRequest?: any;
9
+ PerformanceObserver?: any;
10
+ addEventListener?: (type: string, fn: (ev: any) => void, opts?: any) => void;
11
+ location?: {
12
+ href: string;
13
+ origin?: string;
14
+ };
15
+ document?: {
16
+ visibilityState?: string;
17
+ readyState?: string;
18
+ };
19
+ navigator?: {
20
+ userAgent?: string;
21
+ language?: string;
22
+ platform?: string;
23
+ userAgentData?: {
24
+ mobile?: boolean;
25
+ platform?: string;
26
+ };
27
+ };
28
+ performance?: {
29
+ timeOrigin?: number;
30
+ now(): number;
31
+ getEntriesByType?: (t: string) => any[];
32
+ };
33
+ storage?: {
34
+ getItem(k: string): string | null;
35
+ setItem(k: string, v: string): void;
36
+ };
37
+ setTimeout?: (fn: () => void, ms: number) => unknown;
38
+ clearTimeout?: (h: unknown) => void;
39
+ now(): number;
40
+ random(): number;
41
+ randomBytes?: (n: number) => Uint8Array;
42
+ };
43
+ export declare function browserEnv(): Env;
package/dist/env.js ADDED
@@ -0,0 +1,29 @@
1
+ export function browserEnv() {
2
+ const g = globalThis;
3
+ let storage;
4
+ try {
5
+ storage = g.sessionStorage;
6
+ }
7
+ catch {
8
+ storage = undefined;
9
+ }
10
+ return {
11
+ global: g,
12
+ fetch: typeof g.fetch === "function" ? g.fetch.bind(g) : undefined,
13
+ Headers: g.Headers,
14
+ Request: g.Request,
15
+ XMLHttpRequest: g.XMLHttpRequest,
16
+ PerformanceObserver: g.PerformanceObserver,
17
+ addEventListener: typeof g.addEventListener === "function" ? g.addEventListener.bind(g) : undefined,
18
+ location: g.location,
19
+ document: g.document,
20
+ navigator: g.navigator,
21
+ performance: g.performance,
22
+ storage,
23
+ setTimeout: (fn, ms) => g.setTimeout(fn, ms),
24
+ clearTimeout: (h) => g.clearTimeout(h),
25
+ now: () => Date.now(),
26
+ random: () => Math.random(),
27
+ };
28
+ }
29
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAsBA,MAAM,UAAU,UAAU;IACxB,MAAM,CAAC,GAAG,UAAiB,CAAC;IAC5B,IAAI,OAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,CAAC,cAAc,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,SAAS,CAAC;IACtB,CAAC;IACD,OAAO;QACL,MAAM,EAAE,CAAC;QACT,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;QAClE,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,cAAc,EAAE,CAAC,CAAC,cAAc;QAChC,mBAAmB,EAAE,CAAC,CAAC,mBAAmB;QAC1C,gBAAgB,EAAE,OAAO,CAAC,CAAC,gBAAgB,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;QACnG,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,OAAO;QACP,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,CAAC;QAC5C,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;QACtC,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;QACrB,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE;KAC5B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { Attrs, SpanData } from "./otlp.js";
2
+ import { SpanQueue } from "./queue.js";
3
+ export type FetchLike = (input: string, init: {
4
+ method: string;
5
+ headers: Record<string, string>;
6
+ body: string;
7
+ keepalive: boolean;
8
+ credentials?: "omit";
9
+ }) => Promise<unknown>;
10
+ export type ExporterOptions = {
11
+ /** Full OTLP traces URL, e.g. https://ingest.example.com/v1/traces */
12
+ url: string;
13
+ projectKey: string;
14
+ resource: Attrs;
15
+ version: string;
16
+ /** The ORIGINAL (un-instrumented) fetch. */
17
+ fetch: FetchLike | undefined;
18
+ setTimeout?: (fn: () => void, ms: number) => unknown;
19
+ clearTimeout?: (h: unknown) => void;
20
+ flushIntervalMs?: number;
21
+ maxBatchSpans?: number;
22
+ maxQueueSpans?: number;
23
+ /** Per-request body cap. Browsers cap keepalive fetch bodies at 64 KiB, so stay under it. */
24
+ maxPayloadBytes?: number;
25
+ };
26
+ export type Exporter = {
27
+ enqueue(span: SpanData): void;
28
+ flush(): Promise<void>;
29
+ readonly queue: SpanQueue;
30
+ stop(): void;
31
+ };
32
+ /** Batches spans and posts OTLP/HTTP JSON. Never throws and never rejects. */
33
+ export declare function createExporter(o: ExporterOptions): Exporter;
@@ -0,0 +1,85 @@
1
+ import { buildRequestBody, encodeSpan } from "./otlp.js";
2
+ import { SpanQueue } from "./queue.js";
3
+ /** Batches spans and posts OTLP/HTTP JSON. Never throws and never rejects. */
4
+ export function createExporter(o) {
5
+ const st = o.setTimeout ?? ((fn, ms) => setTimeout(fn, ms));
6
+ const ct = o.clearTimeout ?? ((h) => clearTimeout(h));
7
+ const interval = o.flushIntervalMs ?? 5000;
8
+ const maxBatch = o.maxBatchSpans ?? 100;
9
+ const maxPayload = o.maxPayloadBytes ?? 60_000;
10
+ const queue = new SpanQueue(o.maxQueueSpans ?? 500);
11
+ let timer;
12
+ let stopped = false;
13
+ // Leave room for the envelope (resource + scope) when sizing batches.
14
+ const envelope = buildRequestBody(o.resource, [], o.version).length + 64;
15
+ function schedule() {
16
+ if (timer !== undefined || stopped)
17
+ return;
18
+ timer = st(() => {
19
+ timer = undefined;
20
+ void flush();
21
+ }, interval);
22
+ }
23
+ function enqueue(span) {
24
+ if (stopped)
25
+ return;
26
+ try {
27
+ const json = JSON.stringify(encodeSpan(span));
28
+ queue.push({ json, bytes: json.length + 1 });
29
+ if (queue.size >= maxBatch)
30
+ void flush();
31
+ else
32
+ schedule();
33
+ }
34
+ catch {
35
+ /* never throw into the host app */
36
+ }
37
+ }
38
+ /** Starts every request synchronously (important on pagehide), then resolves when all settle. */
39
+ function flush() {
40
+ if (timer !== undefined) {
41
+ ct(timer);
42
+ timer = undefined;
43
+ }
44
+ const pending = [];
45
+ try {
46
+ while (queue.size) {
47
+ const batch = queue.take(maxBatch, Math.max(1000, maxPayload - envelope));
48
+ if (!batch.length)
49
+ continue;
50
+ const body = buildRequestBody(o.resource, batch.map((b) => b.json), o.version);
51
+ try {
52
+ const f = o.fetch;
53
+ if (!f)
54
+ continue;
55
+ pending.push(Promise.resolve(f(o.url, {
56
+ method: "POST",
57
+ headers: { "content-type": "application/json", authorization: `Bearer ${o.projectKey}` },
58
+ body,
59
+ keepalive: true,
60
+ credentials: "omit",
61
+ })).catch(() => undefined));
62
+ }
63
+ catch {
64
+ /* dropped: telemetry is best-effort */
65
+ }
66
+ }
67
+ }
68
+ catch {
69
+ /* ignore */
70
+ }
71
+ return Promise.all(pending).then(() => undefined);
72
+ }
73
+ return {
74
+ enqueue,
75
+ flush,
76
+ queue,
77
+ stop() {
78
+ stopped = true;
79
+ if (timer !== undefined)
80
+ ct(timer);
81
+ timer = undefined;
82
+ },
83
+ };
84
+ }
85
+ //# sourceMappingURL=exporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exporter.js","sourceRoot":"","sources":["../src/exporter.ts"],"names":[],"mappings":"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"}
package/dist/ids.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ /** Fills a Uint8Array with random bytes. Injected so tests are deterministic. */
2
+ export type RandomBytes = (n: number) => Uint8Array;
3
+ export declare function defaultRandomBytes(g?: {
4
+ crypto?: {
5
+ getRandomValues?: (a: Uint8Array) => Uint8Array;
6
+ };
7
+ }): RandomBytes;
8
+ /** Non-zero random hex id of `bytes` bytes (16 for trace ids, 8 for span ids). */
9
+ export declare function randomId(rand: RandomBytes, bytes: number): string;
10
+ export declare const newTraceId: (rand: RandomBytes) => string;
11
+ export declare const newSpanId: (rand: RandomBytes) => string;
12
+ export declare function formatTraceparent(traceId: string, spanId: string, sampled: boolean): string;
13
+ export type Traceparent = {
14
+ traceId: string;
15
+ spanId: string;
16
+ sampled: boolean;
17
+ };
18
+ /** Parses a W3C traceparent (version 00 layout; unknown future versions accepted per spec). */
19
+ export declare function parseTraceparent(value: string): Traceparent | null;
package/dist/ids.js ADDED
@@ -0,0 +1,46 @@
1
+ export function defaultRandomBytes(g = globalThis) {
2
+ return (n) => {
3
+ const out = new Uint8Array(n);
4
+ if (g.crypto?.getRandomValues)
5
+ g.crypto.getRandomValues(out);
6
+ else
7
+ for (let i = 0; i < n; i++)
8
+ out[i] = Math.floor(Math.random() * 256);
9
+ return out;
10
+ };
11
+ }
12
+ function hex(bytes) {
13
+ let s = "";
14
+ for (const b of bytes)
15
+ s += (b < 16 ? "0" : "") + b.toString(16);
16
+ return s;
17
+ }
18
+ /** Non-zero random hex id of `bytes` bytes (16 for trace ids, 8 for span ids). */
19
+ export function randomId(rand, bytes) {
20
+ for (let i = 0; i < 4; i++) {
21
+ const id = hex(rand(bytes));
22
+ if (/[1-9a-f]/.test(id))
23
+ return id;
24
+ }
25
+ return "0".repeat(bytes * 2 - 1) + "1";
26
+ }
27
+ export const newTraceId = (rand) => randomId(rand, 16);
28
+ export const newSpanId = (rand) => randomId(rand, 8);
29
+ export function formatTraceparent(traceId, spanId, sampled) {
30
+ return `00-${traceId}-${spanId}-${sampled ? "01" : "00"}`;
31
+ }
32
+ /** Parses a W3C traceparent (version 00 layout; unknown future versions accepted per spec). */
33
+ export function parseTraceparent(value) {
34
+ const m = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})(-.*)?$/.exec(value.trim());
35
+ if (!m)
36
+ return null;
37
+ const [, ver, traceId, spanId, flags, rest] = m;
38
+ if (ver === "ff")
39
+ return null;
40
+ if (ver === "00" && rest)
41
+ return null;
42
+ if (/^0+$/.test(traceId) || /^0+$/.test(spanId))
43
+ return null;
44
+ return { traceId, spanId, sampled: (parseInt(flags, 16) & 1) === 1 };
45
+ }
46
+ //# sourceMappingURL=ids.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ids.js","sourceRoot":"","sources":["../src/ids.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,kBAAkB,CAAC,IAAsE,UAAmB;IAC1H,OAAO,CAAC,CAAC,EAAE,EAAE;QACX,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,CAAC,CAAC,MAAM,EAAE,eAAe;YAAE,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;;YACxD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;gBAAE,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;QAC1E,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,GAAG,CAAC,KAAiB;IAC5B,IAAI,CAAC,GAAG,EAAE,CAAC;IACX,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACjE,OAAO,CAAC,CAAC;AACX,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,QAAQ,CAAC,IAAiB,EAAE,KAAa;IACvD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5B,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAAE,OAAO,EAAE,CAAC;IACrC,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;AACzC,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAiB,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AACpE,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAiB,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAElE,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,MAAc,EAAE,OAAgB;IACjF,OAAO,MAAM,OAAO,IAAI,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5D,CAAC;AAID,+FAA+F;AAC/F,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,MAAM,CAAC,GAAG,mEAAmE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACjG,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAChD,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9B,IAAI,GAAG,KAAK,IAAI,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;AACvE,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { Env } from "./env.js";
2
+ import { newSpanId, newTraceId } from "./ids.js";
3
+ export type OwlpaneBrowserOptions = {
4
+ /** Ingest base URL, e.g. 'https://ingest.example.com'. `/v1/traces` is appended. */
5
+ endpoint?: string;
6
+ /** Ingest key ('owl_ing_...'), sent as `Authorization: Bearer <key>`. */
7
+ projectKey: string;
8
+ serviceName: string;
9
+ environment?: string;
10
+ release?: string;
11
+ /** 0..1 share of page views/requests/errors kept (decided per trace). Default 1. */
12
+ sampleRate?: number;
13
+ /** Extra URL prefixes / regexes that receive a `traceparent` header. Same-origin always does. */
14
+ propagateTraceTo?: (string | RegExp)[];
15
+ /** Keep the query string in recorded URLs (default false: stripped to avoid leaking PII). */
16
+ keepQuery?: boolean;
17
+ /** Max exception spans per page view (default 20). */
18
+ maxErrorsPerPage?: number;
19
+ /** Test seam: replaces browser globals. */
20
+ env?: Env;
21
+ /** Record rrweb DOM to OTLP logs (requires server OWLPANE_DOM_REPLAY_ENABLED). */
22
+ recordDomReplay?: boolean;
23
+ };
24
+ export type OwlpaneBrowser = {
25
+ sessionId: string;
26
+ flush(): Promise<void>;
27
+ shutdown(): Promise<void>;
28
+ captureException(error: unknown): void;
29
+ };
30
+ export declare const DEFAULT_ENDPOINT = "https://ingest.owlpane.com";
31
+ /** Starts RUM collection. Safe to call once per page; never throws. */
32
+ export declare function init(options: OwlpaneBrowserOptions): OwlpaneBrowser;
33
+ export { newSpanId, newTraceId };
34
+ export { formatTraceparent, parseTraceparent } from "./ids.js";
35
+ export { sanitizeUrl, shouldPropagate } from "./url.js";
36
+ export { startDomReplay } from "./dom-replay.js";
package/dist/index.js ADDED
@@ -0,0 +1,131 @@
1
+ import { browserEnv } from "./env.js";
2
+ import { createExporter } from "./exporter.js";
3
+ import { defaultRandomBytes, newSpanId, newTraceId } from "./ids.js";
4
+ import { instrumentFetch, instrumentXhr } from "./instrument-http.js";
5
+ import { captureDocumentLoad, captureErrors, captureVitals, createErrorReporter } from "./observers.js";
6
+ import { startDomReplay } from "./dom-replay.js";
7
+ import { VERSION, createTracer } from "./tracer.js";
8
+ import { sanitizeUrl, urlPath } from "./url.js";
9
+ export const DEFAULT_ENDPOINT = "https://ingest.owlpane.com";
10
+ let active;
11
+ const NOOP = { sessionId: "", flush: async () => undefined, shutdown: async () => undefined, captureException: () => undefined };
12
+ function sessionIdFor(env, rand) {
13
+ const K = "owlpane.sid";
14
+ try {
15
+ const v = env.storage?.getItem(K);
16
+ if (v && /^[0-9a-f]{16}$/.test(v))
17
+ return v;
18
+ }
19
+ catch {
20
+ /* storage may throw (privacy mode) */
21
+ }
22
+ const id = newSpanId(rand);
23
+ try {
24
+ env.storage?.setItem(K, id);
25
+ }
26
+ catch {
27
+ /* ignore */
28
+ }
29
+ return id;
30
+ }
31
+ /** Starts RUM collection. Safe to call once per page; never throws. */
32
+ export function init(options) {
33
+ try {
34
+ if (active)
35
+ return active;
36
+ const env = options.env ?? browserEnv();
37
+ if (!options.projectKey || !options.serviceName)
38
+ return NOOP;
39
+ const opts = {
40
+ endpoint: (options.endpoint || DEFAULT_ENDPOINT).replace(/\/+$/, ""),
41
+ projectKey: options.projectKey,
42
+ serviceName: options.serviceName,
43
+ environment: options.environment,
44
+ release: options.release,
45
+ sampleRate: options.sampleRate ?? 1,
46
+ propagateTraceTo: options.propagateTraceTo ?? [],
47
+ keepQuery: options.keepQuery ?? false,
48
+ maxErrorsPerPage: options.maxErrorsPerPage ?? 20,
49
+ };
50
+ const tracesUrl = `${opts.endpoint}/v1/traces`;
51
+ const originalFetch = env.fetch; // captured BEFORE patching: exports must not be instrumented
52
+ const rand = env.randomBytes ?? defaultRandomBytes();
53
+ const sessionId = sessionIdFor(env, rand);
54
+ const resource = {
55
+ "service.name": opts.serviceName,
56
+ "service.version": opts.release,
57
+ "deployment.environment.name": opts.environment,
58
+ "deployment.environment": opts.environment,
59
+ "telemetry.sdk.name": "owlpane-browser",
60
+ "telemetry.sdk.language": "webjs",
61
+ "telemetry.sdk.version": VERSION,
62
+ "owlpane.rum": "true",
63
+ };
64
+ const exporter = createExporter({
65
+ url: tracesUrl,
66
+ projectKey: opts.projectKey,
67
+ version: VERSION,
68
+ fetch: originalFetch,
69
+ setTimeout: env.setTimeout,
70
+ clearTimeout: env.clearTimeout,
71
+ resource,
72
+ });
73
+ const pageUrl = () => {
74
+ const href = env.location?.href ?? "";
75
+ return { url: sanitizeUrl(href, undefined, opts.keepQuery), path: urlPath(href, undefined) };
76
+ };
77
+ const tracer = createTracer(env, opts, (s) => exporter.enqueue(s), sessionId, pageUrl);
78
+ const isOwnUrl = (abs) => abs.startsWith(opts.endpoint + "/") || abs === opts.endpoint;
79
+ // One trace per page view: 'documentLoad' is its root; vitals and errors hang off it.
80
+ const page = tracer.start("documentLoad", 1, { traceId: newTraceId(rand), startMs: env.performance?.timeOrigin ?? env.now() });
81
+ const ctx = { env, opts, tracer, page };
82
+ const undo = [];
83
+ if (originalFetch)
84
+ undo.push(instrumentFetch({ env, opts, tracer, isOwnUrl }, originalFetch));
85
+ undo.push(instrumentXhr({ env, opts, tracer, isOwnUrl }));
86
+ const report = createErrorReporter(ctx);
87
+ captureErrors(ctx, report);
88
+ captureDocumentLoad(ctx);
89
+ const vitals = captureVitals(ctx);
90
+ const hide = () => {
91
+ vitals.report();
92
+ void exporter.flush();
93
+ };
94
+ env.addEventListener?.("visibilitychange", () => {
95
+ if (env.document?.visibilityState === "hidden")
96
+ hide();
97
+ });
98
+ env.addEventListener?.("pagehide", hide);
99
+ let stopDomReplay;
100
+ if (options.recordDomReplay) {
101
+ stopDomReplay = startDomReplay({
102
+ logsUrl: `${opts.endpoint}/v1/logs`,
103
+ projectKey: opts.projectKey,
104
+ sessionId,
105
+ resource,
106
+ fetch: originalFetch,
107
+ });
108
+ }
109
+ active = {
110
+ sessionId,
111
+ flush: () => exporter.flush(),
112
+ captureException: (e) => report(e, "manual"),
113
+ async shutdown() {
114
+ stopDomReplay?.();
115
+ undo.forEach((u) => u());
116
+ exporter.stop();
117
+ await exporter.flush();
118
+ active = undefined;
119
+ },
120
+ };
121
+ return active;
122
+ }
123
+ catch {
124
+ return NOOP;
125
+ }
126
+ }
127
+ export { newSpanId, newTraceId };
128
+ export { formatTraceparent, parseTraceparent } from "./ids.js";
129
+ export { sanitizeUrl, shouldPropagate } from "./url.js";
130
+ export { startDomReplay } from "./dom-replay.js";
131
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { Env } from "./env.js";
2
+ import type { ResolvedOptions, Tracer } from "./tracer.js";
3
+ type Ctx = {
4
+ env: Env;
5
+ opts: ResolvedOptions;
6
+ tracer: Tracer;
7
+ isOwnUrl: (abs: string) => boolean;
8
+ };
9
+ /** Replaces `global.fetch` with an instrumented wrapper. Returns an undo function. */
10
+ export declare function instrumentFetch(c: Ctx, original: NonNullable<Env["fetch"]>): () => void;
11
+ /** Patches XMLHttpRequest.prototype open/send. Returns an undo function. */
12
+ export declare function instrumentXhr(c: Ctx): () => void;
13
+ export {};