@zharp/browser 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zharp
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 CHANGED
@@ -1,3 +1,133 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # @zharp/browser
2
+
3
+ Application Monitoring for web pages, for [Zharp](https://zharp.io). One `init()` and the page reports its errors, its logs, how long each page load, navigation and click took and which requests it made — joined to your back end's traces — and, if you want, a replay of the session.
4
+
5
+ Everything is sent as OpenTelemetry (OTLP/JSON) to Zharp. The back-end half is [`@zharp/node`](https://www.npmjs.com/package/@zharp/node) (or any OpenTelemetry SDK).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @zharp/browser
11
+ ```
12
+
13
+ ```typescript
14
+ import { Zharp } from '@zharp/browser';
15
+
16
+ Zharp.init({
17
+ key: 'zpk_…', // the app key from the dashboard — safe in a page
18
+ service: 'checkout-web',
19
+ environment: 'production',
20
+ release: 'web@2.3.0', // optional
21
+ tracingOrigins: ['api.example.com'], // your API, so its spans join the page's trace
22
+ replay: { onErrorSampleRate: 1.0 }, // optional: a replay for every session with an error
23
+ });
24
+
25
+ // After login, and on every page load while logged in:
26
+ Zharp.setUser({ id: user.id, email: user.email });
27
+ ```
28
+
29
+ Or with a script tag, which gives the page a global `Zharp` with the same API:
30
+
31
+ ```html
32
+ <script src="https://cdn.jsdelivr.net/npm/@zharp/browser/dist/zharp.min.js"></script>
33
+ <script>
34
+ Zharp.init({ key: 'zpk_…', service: 'checkout-web', environment: 'production' });
35
+ </script>
36
+ ```
37
+
38
+ `zharp.min.js` is about 24 KB (9 KB gzipped). The recorder is a separate file, `zharp-replay.min.js`, loaded from next to it only when a session records.
39
+
40
+ ## What is captured
41
+
42
+ | | |
43
+ |---|---|
44
+ | **Errors** | Uncaught errors and unhandled rejections (as unhandled), and `Zharp.captureError(err)` (as handled). Sent as error logs carrying `exception.type`, `exception.message` and `exception.stacktrace`, in the trace that was active, and grouped into issues. |
45
+ | **Page loads** | A root span `pageload /path` from navigation start until the load event and its requests have finished, with time to first byte, DOMContentLoaded and load. |
46
+ | **Navigations** | A root span `navigation /path` for each SPA route change: `history.pushState`, `replaceState` (when the path changes), back/forward, and `#/` hash routes. |
47
+ | **Interactions** | A root span `click “Pay now”` for a click that leads to requests — clicks that do nothing are not recorded. The label is the element's `data-zharp-name`, `aria-label`, text or title. |
48
+ | **Requests** | A CLIENT span for every `fetch` and `XMLHttpRequest`, with `http.request.method`, `url.full`, `http.response.status_code`. 4xx/5xx and network failures are errors. |
49
+ | **Context** | `session.id`, `enduser.id`/`email`/`name`, browser and OS (from the user agent), plus your tags, on every span and log. |
50
+
51
+ ### Traces into your back end
52
+
53
+ Requests to the page's own origin and to anything in `tracingOrigins` carry a W3C `traceparent` header (so the back end continues the trace) and a `baggage` header with `session.id` and `enduser.id` (so the back end's spans and logs are filed under the same session and user). Nothing else gets them: an unknown header on a third-party request can make it fail CORS. Third-party requests still get a span.
54
+
55
+ `tracingOrigins` entries: a host (`"api.example.com"`, any scheme and port, or `"localhost:3001"` for one port), a URL prefix (`"https://example.com/api"`), or a `RegExp` tested against the full URL. Your API has to allow the two headers in CORS (`Access-Control-Allow-Headers: traceparent, baggage`); `@zharp/node` does not do that for you.
56
+
57
+ ### Sessions
58
+
59
+ A session is one person's continuous use of one tab. It is kept in `sessionStorage` — it survives reloads and navigations, and each tab has its own — and ends after 30 minutes without clicks, keys or scrolling (`sessionTimeoutMinutes`), or on `Zharp.clearUser()`. Where storage is unavailable (some private modes, sandboxed iframes) the session lasts as long as the page.
60
+
61
+ ### Session replay
62
+
63
+ ```typescript
64
+ replay: {
65
+ sampleRate: 0.1, // record 10% of sessions from start to end
66
+ onErrorSampleRate: 1.0, // of the rest, keep the last minute in memory and upload it — and the
67
+ // rest of the session — only if an error happens
68
+ }
69
+ ```
70
+
71
+ The decision is made once per session. A session that does not record never downloads the recorder ([rrweb](https://www.rrweb.io/)), and one in error mode uploads nothing unless an error happens. Every input is masked by default (`maskAllInputs: false` to change that — then use `blockSelector` for anything sensitive); `maskAllText`, `maskTextSelector` and `blockSelector` cover the rest of the page, and `beforeSend(events)` can drop a batch. Each recording lists the traces that happened during it, so you can go from a replay to the requests and back.
72
+
73
+ ### Logs
74
+
75
+ ```typescript
76
+ Zharp.log.info('Checkout started', { cartSize: 3 }); // trace, debug, info, warn, error, fatal
77
+ Zharp.log.error('Payment failed', { error }); // an Error here is sent as the exception
78
+ ```
79
+
80
+ The console is not copied by default — on most pages it is mostly other people's libraries. `captureConsole: true` copies every level, `captureConsole: ['warn', 'error']` just those.
81
+
82
+ ### Logs only
83
+
84
+ ```typescript
85
+ Zharp.init({ key: 'zpk_…', tracing: false });
86
+ ```
87
+
88
+ Logs, errors (uncaught and `captureError`, with their stacks), the user, the session and tags are sent as usual; no spans are made, `fetch` and `XMLHttpRequest` are not patched, and no `traceparent` or `baggage` header is added to any request. `Zharp.startSpan(name, fn)` just runs `fn`. Session replay is independent of it: it still follows `replay`, but a recording has no traces to link to.
89
+
90
+ ## API
91
+
92
+ | | |
93
+ |---|---|
94
+ | `Zharp.init(options)` | Start. Call once; a second call is ignored with a warning. Does nothing outside a browser (SSR). |
95
+ | `Zharp.setUser({ id, email?, name? })` | `enduser.id` (and email, name) on everything from now on, and in `baggage`. |
96
+ | `Zharp.clearUser()` | Forget the user and start a new session — on logout. |
97
+ | `Zharp.setTag(key, value)` | An attribute on every span and log from now on; `null` removes it. |
98
+ | `Zharp.captureError(err, attrs?)` | Report a caught error. |
99
+ | `Zharp.log.<level>(message, attrs?)` | A log in the current trace. |
100
+ | `Zharp.startSpan(name, fn, attrs?)` | Run `fn` in a span; requests and logs from its synchronous part belong to it, and a returned promise ends it when it settles. |
101
+ | `Zharp.flush()` | Send everything waiting now. |
102
+ | `Zharp.close()` | Flush, stop recording and undo every patch. |
103
+ | `Zharp.sessionId` | The current session's ID. |
104
+
105
+ ## Options
106
+
107
+ | Option | Default | |
108
+ |---|---|---|
109
+ | `key` | — | Required. The app key (`zpk_…`). It can only send, and can be limited to your origins in the dashboard. |
110
+ | `service` | `"web"` | `service.name`. |
111
+ | `environment` | — | `deployment.environment.name`. |
112
+ | `release` | — | `service.version`. Use the same value as your source maps' release. |
113
+ | `endpoint` | `https://api.zharp.io/api/v1/otlp` | |
114
+ | `tracingOrigins` | `[]` | See above. Same-origin requests are always included. |
115
+ | `tracing` | `true` | `false` for logs only (see above). |
116
+ | `sampleRate` | `1` | Fraction of traces kept. Errors, logs and replays are not sampled by it; the decision travels in `traceparent`, so the back end keeps or drops the same traces. |
117
+ | `replay` | off | See above. |
118
+ | `captureConsole` | `false` | `true` or a list of levels. |
119
+ | `interactions` | `true` | Click spans. |
120
+ | `ignoreUrls` | `[]` | URLs never traced, as strings (host or prefix) or RegExps. |
121
+ | `sessionTimeoutMinutes` | `30` | |
122
+
123
+ ## Readable stack traces
124
+
125
+ Minified stacks become original files, lines and code once the build's source maps are uploaded with a [Zharp bundler plugin](../bundler-plugins/README.md) or [`@zharp/cli`](../cli/README.md). Those tools register each emitted file's debug ID at load time; every error this SDK sends carries the IDs of the files in its stack as `zharp.debug_ids`, so the server finds the right map whatever URL the file was served from. Nothing to configure here.
126
+
127
+ ## Delivery
128
+
129
+ Batches of up to 100 records go out every 5 seconds, gzipped where the browser has `CompressionStream`. 429, 5xx and network failures are retried with backoff; the queue holds at most 1,000 records of each kind and drops the oldest past that. When the page is hidden or closed, what is left goes out with `fetch(…, { keepalive: true })` so it outlives the page. A rejected key (401/403) prints one console warning and stops sending from that page. The SDK never throws into your code.
130
+
131
+ ## License
132
+
133
+ MIT
@@ -0,0 +1,2 @@
1
+ 'use strict';function a(e){let t=new Uint8Array(e),n=typeof crypto!="undefined"?crypto:void 0;if(n&&n.getRandomValues)n.getRandomValues(t);else for(let r=0;r<e;r++)t[r]=Math.random()*256|0;let i="";for(let r=0;r<e;r++)i+=(t[r]<16?"0":"")+t[r].toString(16);return /^0+$/.test(i)?a(e):i}var q=()=>a(16),A=()=>a(8);function E(){let e=typeof performance!="undefined"?performance:void 0;return e&&e.timeOrigin?e.timeOrigin+e.now():Date.now()}function V(e){let t=Math.floor(e),n=Math.round((e-t)*1e6);return n>=1e6?`${t+1}000000`:`${t}${String(n).padStart(6,"0")}`}function g(e,t=0){return typeof e=="string"?{stringValue:e}:typeof e=="boolean"?{boolValue:e}:typeof e=="number"?Number.isInteger(e)?{intValue:e}:{doubleValue:e}:e instanceof Error?{stringValue:`${e.name}: ${e.message}`}:e==null?{stringValue:""}:t<4&&Array.isArray(e)?{arrayValue:{values:e.map(n=>g(n,t+1))}}:t<4&&typeof e=="object"?{kvlistValue:{values:T(e,t+1)}}:{stringValue:S(e)}}function T(e,t=0){let n=[];for(let i in e){let r=e[i];r!==void 0&&n.push({key:i,value:g(r,t)});}return n}function S(e){if(typeof e=="string")return e;if(e instanceof Error)return `${e.name}: ${e.message}`;try{let t=JSON.stringify(e);return t===void 0?String(e):t}catch(t){return String(e)}}function $(e){try{return e()}catch(t){return}}var M=()=>typeof window!="undefined"&&typeof document!="undefined";function u(e){return typeof TextEncoder!="undefined"?new TextEncoder().encode(e).length:e.length*2}var b=100,f=5e3,c=1e3,k=5*6e4,x=1e6,y=6e4,h=false;async function l(e,t,n,i=false){if(h)return 401;let r={"Content-Type":"application/json",Authorization:`Bearer ${e.key}`},s=n;if(!i&&n.length>2048&&typeof CompressionStream!="undefined")try{s=await new Response(new Blob([n]).stream().pipeThrough(new CompressionStream("gzip"))).arrayBuffer(),r["Content-Encoding"]="gzip";}catch(o){s=n;}try{let o=await e.fetch.call(globalThis,`${e.url}/v1/${t}`,{method:"POST",headers:r,body:s,keepalive:i,credentials:"omit"});return (o.status===401||o.status===403)&&(h||(h=!0,console.warn(`[Zharp] The key was rejected (HTTP ${o.status}); nothing more will be sent from this page. Check Zharp.init({ key }) and the app's allowed origins.`))),o.status}catch(o){return 0}}var _=e=>e===0||e===429||e>=500;function m(e,t,n){let i=u(JSON.stringify(n([]))),r=[],s=[],o=i;for(let d of e){let p=u(JSON.stringify(d))+1;s.length&&o+p>t&&(r.push({body:JSON.stringify(n(s)),items:s}),s=[],o=i),s.push(d),o+=p;}return s.length&&r.push({body:JSON.stringify(n(s)),items:s}),r}var w=class{constructor(t,n,i){this.endpoint=t;this.signal=n;this.wrap=i;this.queue=[];this.timer=null;this.sending=null;this.failures=0;}add(t){this.queue.push(t),this.queue.length>c&&this.queue.splice(0,this.queue.length-c),this.queue.length>=b&&!this.failures?this.flush():this.schedule(f);}async flush(){for(;this.sending;)await this.sending;if(this.queue.length)return this.sending=this.send().finally(()=>this.sending=null),this.sending}flushOnHide(){if(!this.queue.length)return;let t=this.queue;this.queue=[];for(let{body:n}of m(t,y,this.wrap)){let i=u(n)<=y;l(this.endpoint,this.signal,n,i).then(r=>{i&&r===0&&l(this.endpoint,this.signal,n);});}}schedule(t){this.timer||(this.timer=setTimeout(()=>{this.timer=null,this.flush();},t));}async send(){this.timer&&(clearTimeout(this.timer),this.timer=null);let t=this.queue;this.queue=[];let n=m(t,x,this.wrap);for(let i=0;i<n.length;i++){let r=await l(this.endpoint,this.signal,n[i].body);if(_(r)){let s=n.slice(i).flatMap(o=>o.items);this.queue=s.concat(this.queue).slice(-c),this.failures++,this.schedule(Math.min(f*2**this.failures,k));return}this.failures=0;}this.queue.length&&this.schedule(f);}};exports.a=a;exports.b=q;exports.c=A;exports.d=E;exports.e=V;exports.f=T;exports.g=S;exports.h=$;exports.i=M;exports.j=x;exports.k=y;exports.l=l;exports.m=_;exports.n=m;exports.o=w;//# sourceMappingURL=chunk-H5MED5NB.js.map
2
+ //# sourceMappingURL=chunk-H5MED5NB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/util.ts","../src/transport.ts"],"names":["randomHex","bytes","buf","c","i","out","newTraceId","newSpanId","now","perf","toNanos","ms","whole","fraction","anyValue","value","depth","v","keyValues","safeString","attributes","key","json","e","safely","fn","isBrowser","byteLength","text","BATCH_SIZE","FLUSH_MS","MAX_QUEUE","MAX_BACKOFF_MS","MAX_BODY_BYTES","MAX_KEEPALIVE_BYTES","keyRejected","post","endpoint","signal","body","keepalive","headers","payload","response","retryable","status","split","items","limit","wrap","overhead","group","size","item","itemSize","Exporter","record","records","batches","unsent","b"],"mappings":"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","file":"chunk-H5MED5NB.js","sourcesContent":["/**\r\n * Small pieces everything else uses: IDs, clocks, and OTLP/JSON values.\r\n */\r\n\r\n/** Random bytes as lowercase hex. `crypto.getRandomValues` everywhere it exists; the fallback is about collisions, not secrecy. */\r\nexport function randomHex(bytes: number): string {\r\n const buf = new Uint8Array(bytes);\r\n const c = typeof crypto !== 'undefined' ? crypto : undefined;\r\n if (c && c.getRandomValues) c.getRandomValues(buf);\r\n else for (let i = 0; i < bytes; i++) buf[i] = (Math.random() * 256) | 0;\r\n let out = '';\r\n for (let i = 0; i < bytes; i++) out += (buf[i] < 16 ? '0' : '') + buf[i].toString(16);\r\n // All zeros means \"absent\" in W3C Trace Context; astronomically unlikely, but free to rule out.\r\n return /^0+$/.test(out) ? randomHex(bytes) : out;\r\n}\r\n\r\nexport const newTraceId = () => randomHex(16);\r\nexport const newSpanId = () => randomHex(8);\r\n\r\n/** Milliseconds since the epoch, with the sub-millisecond precision `performance` gives. */\r\nexport function now(): number {\r\n const perf = typeof performance !== 'undefined' ? performance : undefined;\r\n return perf && perf.timeOrigin ? perf.timeOrigin + perf.now() : Date.now();\r\n}\r\n\r\n/**\r\n * Epoch milliseconds as the decimal nanosecond string OTLP wants. Built as text: nanoseconds\r\n * since 1970 are past what a double holds exactly.\r\n */\r\nexport function toNanos(ms: number): string {\r\n const whole = Math.floor(ms);\r\n const fraction = Math.round((ms - whole) * 1e6);\r\n return fraction >= 1e6 ? `${whole + 1}000000` : `${whole}${String(fraction).padStart(6, '0')}`;\r\n}\r\n\r\n/** One OTLP AnyValue. */\r\nexport function anyValue(value: unknown, depth = 0): Record<string, unknown> {\r\n if (typeof value === 'string') return { stringValue: value };\r\n if (typeof value === 'boolean') return { boolValue: value };\r\n if (typeof value === 'number') return Number.isInteger(value) ? { intValue: value } : { doubleValue: value };\r\n if (value instanceof Error) return { stringValue: `${value.name}: ${value.message}` };\r\n if (value === null || value === undefined) return { stringValue: '' };\r\n if (depth < 4 && Array.isArray(value)) return { arrayValue: { values: value.map((v) => anyValue(v, depth + 1)) } };\r\n if (depth < 4 && typeof value === 'object') return { kvlistValue: { values: keyValues(value as Record<string, unknown>, depth + 1) } };\r\n return { stringValue: safeString(value) };\r\n}\r\n\r\n/** An attribute map as an OTLP KeyValue list. `undefined` values are left out. */\r\nexport function keyValues(attributes: Record<string, unknown>, depth = 0): Array<{ key: string; value: unknown }> {\r\n const out: Array<{ key: string; value: unknown }> = [];\r\n for (const key in attributes) {\r\n const value = attributes[key];\r\n if (value !== undefined) out.push({ key, value: anyValue(value, depth) });\r\n }\r\n return out;\r\n}\r\n\r\nexport function safeString(value: unknown): string {\r\n if (typeof value === 'string') return value;\r\n if (value instanceof Error) return `${value.name}: ${value.message}`;\r\n try {\r\n const json = JSON.stringify(value);\r\n return json === undefined ? String(value) : json;\r\n } catch {\r\n return String(value);\r\n }\r\n}\r\n\r\n/** Run `fn`, swallowing anything it throws: the SDK must never break the page it is on. */\r\nexport function safely<T>(fn: () => T): T | undefined {\r\n try {\r\n return fn();\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\nexport const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';\r\n\r\n/** UTF-8 byte length. */\r\nexport function byteLength(text: string): number {\r\n return typeof TextEncoder !== 'undefined' ? new TextEncoder().encode(text).length : text.length * 2;\r\n}\r\n","/**\r\n * Getting telemetry to Zharp from a page.\r\n *\r\n * Records are queued and sent in batches every few seconds (sooner when a batch fills), gzipped\r\n * where the browser can, and retried with backoff when the server or the network asks for it.\r\n * The queue is capped — when it is full the oldest go — so a page that is offline for an hour\r\n * does not grow without bound.\r\n *\r\n * When the page is hidden or closed everything left goes out at once with `keepalive`, which\r\n * lets the request outlive the page. (`sendBeacon` would too, but cannot send the key header.)\r\n */\r\nimport { byteLength } from './util';\r\n\r\nexport type Signal = 'traces' | 'logs' | 'replays';\r\n\r\nconst BATCH_SIZE = 100;\r\nconst FLUSH_MS = 5_000;\r\nconst MAX_QUEUE = 1_000;\r\nconst MAX_BACKOFF_MS = 5 * 60_000;\r\n/** A normal request carries at most this much; the server takes 10 MB, but one huge upload holds everything behind it. */\r\nexport const MAX_BODY_BYTES = 1_000_000;\r\n/** Browsers allow 64 KB of keepalive bodies in flight per page. */\r\nexport const MAX_KEEPALIVE_BYTES = 60_000;\r\n\r\nexport interface Endpoint {\r\n url: string;\r\n key: string;\r\n /** The page's fetch as it was before instrumentation, so the SDK never traces itself. */\r\n fetch: typeof fetch;\r\n}\r\n\r\n/** Shared by every sender on a page: one rejected key stops all of them, with one warning. */\r\nlet keyRejected = false;\r\n\r\n/**\r\n * POST one body. Resolves to the HTTP status, or 0 when the request never completed.\r\n */\r\nexport async function post(endpoint: Endpoint, signal: Signal, body: string, keepalive = false): Promise<number> {\r\n if (keyRejected) return 401;\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n Authorization: `Bearer ${endpoint.key}`,\r\n };\r\n let payload: BodyInit = body;\r\n // gzip when it pays and the browser can; a keepalive request is sent as the page goes away,\r\n // with no time for an async compression step.\r\n if (!keepalive && body.length > 2_048 && typeof CompressionStream !== 'undefined') {\r\n try {\r\n payload = await new Response(new Blob([body]).stream().pipeThrough(new CompressionStream('gzip'))).arrayBuffer();\r\n headers['Content-Encoding'] = 'gzip';\r\n } catch {\r\n payload = body;\r\n }\r\n }\r\n try {\r\n const response = await endpoint.fetch.call(globalThis, `${endpoint.url}/v1/${signal}`, {\r\n method: 'POST',\r\n headers,\r\n body: payload,\r\n keepalive,\r\n credentials: 'omit',\r\n });\r\n if (response.status === 401 || response.status === 403) {\r\n if (!keyRejected) {\r\n keyRejected = true;\r\n console.warn(`[Zharp] The key was rejected (HTTP ${response.status}); nothing more will be sent from this page. Check Zharp.init({ key }) and the app's allowed origins.`);\r\n }\r\n }\r\n return response.status;\r\n } catch {\r\n return 0;\r\n }\r\n}\r\n\r\n/** 429, 5xx and network failures are worth another try; anything else will fail the same way again. */\r\nexport const retryable = (status: number) => status === 0 || status === 429 || status >= 500;\r\n\r\n/**\r\n * Cut `items` into JSON bodies of at most `limit` bytes. An item too large for any body still\r\n * gets one of its own: whether it goes through is for the network to decide.\r\n */\r\nexport function split<T>(items: T[], limit: number, wrap: (group: T[]) => unknown): Array<{ body: string; items: T[] }> {\r\n const overhead = byteLength(JSON.stringify(wrap([])));\r\n const out: Array<{ body: string; items: T[] }> = [];\r\n let group: T[] = [];\r\n let size = overhead;\r\n for (const item of items) {\r\n const itemSize = byteLength(JSON.stringify(item)) + 1;\r\n if (group.length && size + itemSize > limit) {\r\n out.push({ body: JSON.stringify(wrap(group)), items: group });\r\n group = [];\r\n size = overhead;\r\n }\r\n group.push(item);\r\n size += itemSize;\r\n }\r\n if (group.length) out.push({ body: JSON.stringify(wrap(group)), items: group });\r\n return out;\r\n}\r\n\r\n/** A queue of OTLP records of one signal, and the timer that sends them. */\r\nexport class Exporter<T> {\r\n private queue: T[] = [];\r\n private timer: ReturnType<typeof setTimeout> | null = null;\r\n private sending: Promise<void> | null = null;\r\n private failures = 0;\r\n\r\n constructor(\r\n private readonly endpoint: Endpoint,\r\n private readonly signal: 'traces' | 'logs',\r\n /** Wraps records in the export request: resource, scope, and the signal's envelope. */\r\n private readonly wrap: (records: T[]) => unknown,\r\n ) {}\r\n\r\n add(record: T): void {\r\n this.queue.push(record);\r\n if (this.queue.length > MAX_QUEUE) this.queue.splice(0, this.queue.length - MAX_QUEUE);\r\n if (this.queue.length >= BATCH_SIZE && !this.failures) this.flush();\r\n else this.schedule(FLUSH_MS);\r\n }\r\n\r\n /** Send everything queued. One send at a time; a call during one waits and then sends what is left. */\r\n async flush(): Promise<void> {\r\n while (this.sending) await this.sending;\r\n if (!this.queue.length) return;\r\n this.sending = this.send().finally(() => (this.sending = null));\r\n return this.sending;\r\n }\r\n\r\n /** The page is going away: everything left, now, with keepalive. Nothing to wait for. */\r\n flushOnHide(): void {\r\n if (!this.queue.length) return;\r\n const records = this.queue;\r\n this.queue = [];\r\n for (const { body } of split(records, MAX_KEEPALIVE_BYTES, this.wrap)) {\r\n // Too large for keepalive's budget: an ordinary request may still finish before the page dies.\r\n const keepalive = byteLength(body) <= MAX_KEEPALIVE_BYTES;\r\n post(this.endpoint, this.signal, body, keepalive).then((status) => {\r\n if (keepalive && status === 0) post(this.endpoint, this.signal, body);\r\n });\r\n }\r\n }\r\n\r\n private schedule(ms: number): void {\r\n if (this.timer) return;\r\n this.timer = setTimeout(() => {\r\n this.timer = null;\r\n this.flush();\r\n }, ms);\r\n }\r\n\r\n private async send(): Promise<void> {\r\n if (this.timer) {\r\n clearTimeout(this.timer);\r\n this.timer = null;\r\n }\r\n const records = this.queue;\r\n this.queue = [];\r\n const batches = split(records, MAX_BODY_BYTES, this.wrap);\r\n for (let i = 0; i < batches.length; i++) {\r\n const status = await post(this.endpoint, this.signal, batches[i].body);\r\n if (retryable(status)) {\r\n // This batch and the ones after it go back to the front, oldest first, and wait.\r\n const unsent = batches.slice(i).flatMap((b) => b.items);\r\n this.queue = unsent.concat(this.queue).slice(-MAX_QUEUE);\r\n this.failures++;\r\n this.schedule(Math.min(FLUSH_MS * 2 ** this.failures, MAX_BACKOFF_MS));\r\n return;\r\n }\r\n // Delivered, or refused for good (400, 401/403): either way it is done.\r\n this.failures = 0;\r\n }\r\n if (this.queue.length) this.schedule(FLUSH_MS);\r\n }\r\n}\r\n"]}
@@ -0,0 +1,2 @@
1
+ function a(e){let t=new Uint8Array(e),n=typeof crypto!="undefined"?crypto:void 0;if(n&&n.getRandomValues)n.getRandomValues(t);else for(let r=0;r<e;r++)t[r]=Math.random()*256|0;let i="";for(let r=0;r<e;r++)i+=(t[r]<16?"0":"")+t[r].toString(16);return /^0+$/.test(i)?a(e):i}var q=()=>a(16),A=()=>a(8);function E(){let e=typeof performance!="undefined"?performance:void 0;return e&&e.timeOrigin?e.timeOrigin+e.now():Date.now()}function V(e){let t=Math.floor(e),n=Math.round((e-t)*1e6);return n>=1e6?`${t+1}000000`:`${t}${String(n).padStart(6,"0")}`}function g(e,t=0){return typeof e=="string"?{stringValue:e}:typeof e=="boolean"?{boolValue:e}:typeof e=="number"?Number.isInteger(e)?{intValue:e}:{doubleValue:e}:e instanceof Error?{stringValue:`${e.name}: ${e.message}`}:e==null?{stringValue:""}:t<4&&Array.isArray(e)?{arrayValue:{values:e.map(n=>g(n,t+1))}}:t<4&&typeof e=="object"?{kvlistValue:{values:T(e,t+1)}}:{stringValue:S(e)}}function T(e,t=0){let n=[];for(let i in e){let r=e[i];r!==void 0&&n.push({key:i,value:g(r,t)});}return n}function S(e){if(typeof e=="string")return e;if(e instanceof Error)return `${e.name}: ${e.message}`;try{let t=JSON.stringify(e);return t===void 0?String(e):t}catch(t){return String(e)}}function $(e){try{return e()}catch(t){return}}var M=()=>typeof window!="undefined"&&typeof document!="undefined";function u(e){return typeof TextEncoder!="undefined"?new TextEncoder().encode(e).length:e.length*2}var b=100,f=5e3,c=1e3,k=5*6e4,x=1e6,y=6e4,h=false;async function l(e,t,n,i=false){if(h)return 401;let r={"Content-Type":"application/json",Authorization:`Bearer ${e.key}`},s=n;if(!i&&n.length>2048&&typeof CompressionStream!="undefined")try{s=await new Response(new Blob([n]).stream().pipeThrough(new CompressionStream("gzip"))).arrayBuffer(),r["Content-Encoding"]="gzip";}catch(o){s=n;}try{let o=await e.fetch.call(globalThis,`${e.url}/v1/${t}`,{method:"POST",headers:r,body:s,keepalive:i,credentials:"omit"});return (o.status===401||o.status===403)&&(h||(h=!0,console.warn(`[Zharp] The key was rejected (HTTP ${o.status}); nothing more will be sent from this page. Check Zharp.init({ key }) and the app's allowed origins.`))),o.status}catch(o){return 0}}var _=e=>e===0||e===429||e>=500;function m(e,t,n){let i=u(JSON.stringify(n([]))),r=[],s=[],o=i;for(let d of e){let p=u(JSON.stringify(d))+1;s.length&&o+p>t&&(r.push({body:JSON.stringify(n(s)),items:s}),s=[],o=i),s.push(d),o+=p;}return s.length&&r.push({body:JSON.stringify(n(s)),items:s}),r}var w=class{constructor(t,n,i){this.endpoint=t;this.signal=n;this.wrap=i;this.queue=[];this.timer=null;this.sending=null;this.failures=0;}add(t){this.queue.push(t),this.queue.length>c&&this.queue.splice(0,this.queue.length-c),this.queue.length>=b&&!this.failures?this.flush():this.schedule(f);}async flush(){for(;this.sending;)await this.sending;if(this.queue.length)return this.sending=this.send().finally(()=>this.sending=null),this.sending}flushOnHide(){if(!this.queue.length)return;let t=this.queue;this.queue=[];for(let{body:n}of m(t,y,this.wrap)){let i=u(n)<=y;l(this.endpoint,this.signal,n,i).then(r=>{i&&r===0&&l(this.endpoint,this.signal,n);});}}schedule(t){this.timer||(this.timer=setTimeout(()=>{this.timer=null,this.flush();},t));}async send(){this.timer&&(clearTimeout(this.timer),this.timer=null);let t=this.queue;this.queue=[];let n=m(t,x,this.wrap);for(let i=0;i<n.length;i++){let r=await l(this.endpoint,this.signal,n[i].body);if(_(r)){let s=n.slice(i).flatMap(o=>o.items);this.queue=s.concat(this.queue).slice(-c),this.failures++,this.schedule(Math.min(f*2**this.failures,k));return}this.failures=0;}this.queue.length&&this.schedule(f);}};export{a,q as b,A as c,E as d,V as e,T as f,S as g,$ as h,M as i,x as j,y as k,l,_ as m,m as n,w as o};//# sourceMappingURL=chunk-X34YPHYT.mjs.map
2
+ //# sourceMappingURL=chunk-X34YPHYT.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/util.ts","../src/transport.ts"],"names":["randomHex","bytes","buf","c","i","out","newTraceId","newSpanId","now","perf","toNanos","ms","whole","fraction","anyValue","value","depth","v","keyValues","safeString","attributes","key","json","e","safely","fn","isBrowser","byteLength","text","BATCH_SIZE","FLUSH_MS","MAX_QUEUE","MAX_BACKOFF_MS","MAX_BODY_BYTES","MAX_KEEPALIVE_BYTES","keyRejected","post","endpoint","signal","body","keepalive","headers","payload","response","retryable","status","split","items","limit","wrap","overhead","group","size","item","itemSize","Exporter","record","records","batches","unsent","b"],"mappings":"AAKO,SAASA,CAAAA,CAAUC,EAAuB,CAC/C,IAAMC,EAAM,IAAI,UAAA,CAAWD,CAAK,CAAA,CAC1BE,CAAAA,CAAI,OAAO,QAAW,WAAA,CAAc,MAAA,CAAS,MAAA,CACnD,GAAIA,CAAAA,EAAKA,CAAAA,CAAE,gBAAiBA,CAAAA,CAAE,eAAA,CAAgBD,CAAG,CAAA,CAAA,KAC5C,IAAA,IAASE,CAAAA,CAAI,EAAGA,CAAAA,CAAIH,CAAAA,CAAOG,IAAKF,CAAAA,CAAIE,CAAC,EAAK,IAAA,CAAK,MAAA,EAAO,CAAI,GAAA,CAAO,CAAA,CACtE,IAAIC,EAAM,EAAA,CACV,IAAA,IAASD,CAAAA,CAAI,CAAA,CAAGA,CAAAA,CAAIH,CAAAA,CAAOG,IAAKC,CAAAA,EAAAA,CAAQH,CAAAA,CAAIE,CAAC,CAAA,CAAI,EAAA,CAAK,GAAA,CAAM,IAAMF,CAAAA,CAAIE,CAAC,EAAE,QAAA,CAAS,EAAE,EAEpF,OAAO,MAAA,CAAO,IAAA,CAAKC,CAAG,CAAA,CAAIL,CAAAA,CAAUC,CAAK,CAAA,CAAII,CAC/C,CAEO,IAAMC,CAAAA,CAAa,IAAMN,EAAU,EAAE,CAAA,CAC/BO,CAAAA,CAAY,IAAMP,CAAAA,CAAU,CAAC,EAGnC,SAASQ,CAAAA,EAAc,CAC5B,IAAMC,CAAAA,CAAO,OAAO,aAAgB,WAAA,CAAc,WAAA,CAAc,MAAA,CAChE,OAAOA,CAAAA,EAAQA,CAAAA,CAAK,WAAaA,CAAAA,CAAK,UAAA,CAAaA,CAAAA,CAAK,GAAA,EAAI,CAAI,IAAA,CAAK,KACvE,CAMO,SAASC,CAAAA,CAAQC,CAAAA,CAAoB,CAC1C,IAAMC,CAAAA,CAAQ,IAAA,CAAK,MAAMD,CAAE,CAAA,CACrBE,EAAW,IAAA,CAAK,KAAA,CAAA,CAAOF,CAAAA,CAAKC,CAAAA,EAAS,GAAG,CAAA,CAC9C,OAAOC,CAAAA,EAAY,GAAA,CAAM,CAAA,EAAGD,CAAAA,CAAQ,CAAC,CAAA,MAAA,CAAA,CAAW,GAAGA,CAAK,CAAA,EAAG,MAAA,CAAOC,CAAQ,CAAA,CAAE,QAAA,CAAS,EAAG,GAAG,CAAC,EAC9F,CAGO,SAASC,EAASC,CAAAA,CAAgBC,CAAAA,CAAQ,CAAA,CAA4B,CAC3E,OAAI,OAAOD,GAAU,QAAA,CAAiB,CAAE,WAAA,CAAaA,CAAM,CAAA,CACvD,OAAOA,GAAU,SAAA,CAAkB,CAAE,SAAA,CAAWA,CAAM,CAAA,CACtD,OAAOA,GAAU,QAAA,CAAiB,MAAA,CAAO,UAAUA,CAAK,CAAA,CAAI,CAAE,QAAA,CAAUA,CAAM,CAAA,CAAI,CAAE,WAAA,CAAaA,CAAM,EACvGA,CAAAA,YAAiB,KAAA,CAAc,CAAE,WAAA,CAAa,CAAA,EAAGA,CAAAA,CAAM,IAAI,CAAA,EAAA,EAAKA,CAAAA,CAAM,OAAO,CAAA,CAAG,CAAA,CAChFA,CAAAA,EAAU,KAAoC,CAAE,WAAA,CAAa,EAAG,CAAA,CAChEC,CAAAA,CAAQ,CAAA,EAAK,MAAM,OAAA,CAAQD,CAAK,CAAA,CAAU,CAAE,UAAA,CAAY,CAAE,OAAQA,CAAAA,CAAM,GAAA,CAAKE,CAAAA,EAAMH,CAAAA,CAASG,CAAAA,CAAGD,CAAAA,CAAQ,CAAC,CAAC,CAAE,CAAE,CAAA,CAC7GA,CAAAA,CAAQ,CAAA,EAAK,OAAOD,CAAAA,EAAU,QAAA,CAAiB,CAAE,WAAA,CAAa,CAAE,OAAQG,CAAAA,CAAUH,CAAAA,CAAkCC,CAAAA,CAAQ,CAAC,CAAE,CAAE,EAC9H,CAAE,WAAA,CAAaG,CAAAA,CAAWJ,CAAK,CAAE,CAC1C,CAGO,SAASG,CAAAA,CAAUE,CAAAA,CAAqCJ,CAAAA,CAAQ,CAAA,CAA2C,CAChH,IAAMX,CAAAA,CAA8C,GACpD,IAAA,IAAWgB,CAAAA,IAAOD,EAAY,CAC5B,IAAML,CAAAA,CAAQK,CAAAA,CAAWC,CAAG,CAAA,CACxBN,IAAU,MAAA,EAAWV,CAAAA,CAAI,IAAA,CAAK,CAAE,GAAA,CAAAgB,CAAAA,CAAK,MAAOP,CAAAA,CAASC,CAAAA,CAAOC,CAAK,CAAE,CAAC,EAC1E,CACA,OAAOX,CACT,CAEO,SAASc,CAAAA,CAAWJ,EAAwB,CACjD,GAAI,OAAOA,CAAAA,EAAU,QAAA,CAAU,OAAOA,EACtC,GAAIA,CAAAA,YAAiB,KAAA,CAAO,OAAO,CAAA,EAAGA,CAAAA,CAAM,IAAI,CAAA,EAAA,EAAKA,CAAAA,CAAM,OAAO,CAAA,CAAA,CAClE,GAAI,CACF,IAAMO,CAAAA,CAAO,IAAA,CAAK,SAAA,CAAUP,CAAK,CAAA,CACjC,OAAOO,IAAS,KAAA,CAAA,CAAY,MAAA,CAAOP,CAAK,CAAA,CAAIO,CAC9C,CAAA,MAAQC,EAAA,CACN,OAAO,MAAA,CAAOR,CAAK,CACrB,CACF,CAGO,SAASS,CAAAA,CAAUC,CAAAA,CAA4B,CACpD,GAAI,CACF,OAAOA,CAAAA,EACT,OAAQF,CAAAA,CAAA,CACN,MACF,CACF,CAEO,IAAMG,CAAAA,CAAY,IAAM,OAAO,QAAW,WAAA,EAAe,OAAO,QAAA,EAAa,YAG7E,SAASC,CAAAA,CAAWC,EAAsB,CAC/C,OAAO,OAAO,WAAA,EAAgB,WAAA,CAAc,IAAI,aAAY,CAAE,MAAA,CAAOA,CAAI,CAAA,CAAE,MAAA,CAASA,EAAK,MAAA,CAAS,CACpG,CCnEA,IAAMC,CAAAA,CAAa,GAAA,CACbC,EAAW,GAAA,CACXC,CAAAA,CAAY,GAAA,CACZC,CAAAA,CAAiB,CAAA,CAAI,GAAA,CAEdC,EAAiB,GAAA,CAEjBC,CAAAA,CAAsB,GAAA,CAU/BC,CAAAA,CAAc,MAKlB,eAAsBC,EAAKC,CAAAA,CAAoBC,CAAAA,CAAgBC,EAAcC,CAAAA,CAAY,KAAA,CAAwB,CAC/G,GAAIL,CAAAA,CAAa,OAAO,GAAA,CACxB,IAAMM,CAAAA,CAAkC,CACtC,cAAA,CAAgB,kBAAA,CAChB,aAAA,CAAe,CAAA,OAAA,EAAUJ,CAAAA,CAAS,GAAG,EACvC,CAAA,CACIK,CAAAA,CAAoBH,CAAAA,CAGxB,GAAI,CAACC,CAAAA,EAAaD,EAAK,MAAA,CAAS,IAAA,EAAS,OAAO,iBAAA,EAAsB,WAAA,CACpE,GAAI,CACFG,CAAAA,CAAU,MAAM,IAAI,QAAA,CAAS,IAAI,IAAA,CAAK,CAACH,CAAI,CAAC,CAAA,CAAE,MAAA,EAAO,CAAE,WAAA,CAAY,IAAI,iBAAA,CAAkB,MAAM,CAAC,CAAC,CAAA,CAAE,WAAA,GACnGE,CAAAA,CAAQ,kBAAkB,EAAI,OAChC,CAAA,MAAQlB,EAAA,CACNmB,CAAAA,CAAUH,EACZ,CAEF,GAAI,CACF,IAAMI,CAAAA,CAAW,MAAMN,CAAAA,CAAS,KAAA,CAAM,IAAA,CAAK,UAAA,CAAY,GAAGA,CAAAA,CAAS,GAAG,CAAA,IAAA,EAAOC,CAAM,CAAA,CAAA,CAAI,CACrF,OAAQ,MAAA,CACR,OAAA,CAAAG,EACA,IAAA,CAAMC,CAAAA,CACN,UAAAF,CAAAA,CACA,WAAA,CAAa,MACf,CAAC,CAAA,CACD,OAAA,CAAIG,EAAS,MAAA,GAAW,GAAA,EAAOA,CAAAA,CAAS,MAAA,GAAW,GAAA,IAC5CR,CAAAA,GACHA,EAAc,CAAA,CAAA,CACd,OAAA,CAAQ,IAAA,CAAK,CAAA,mCAAA,EAAsCQ,CAAAA,CAAS,MAAM,uGAAuG,CAAA,CAAA,CAAA,CAGtKA,CAAAA,CAAS,MAClB,CAAA,MAAQpB,CAAAA,CAAA,CACN,OAAO,CACT,CACF,CAGO,IAAMqB,CAAAA,CAAaC,GAAmBA,CAAAA,GAAW,CAAA,EAAKA,CAAAA,GAAW,GAAA,EAAOA,CAAAA,EAAU,IAMlF,SAASC,CAAAA,CAASC,CAAAA,CAAYC,CAAAA,CAAeC,CAAAA,CAAoE,CACtH,IAAMC,EAAWvB,CAAAA,CAAW,IAAA,CAAK,SAAA,CAAUsB,CAAAA,CAAK,EAAE,CAAC,CAAC,CAAA,CAC9C5C,CAAAA,CAA2C,EAAC,CAC9C8C,CAAAA,CAAa,EAAC,CACdC,CAAAA,CAAOF,CAAAA,CACX,IAAA,IAAWG,CAAAA,IAAQN,CAAAA,CAAO,CACxB,IAAMO,CAAAA,CAAW3B,CAAAA,CAAW,IAAA,CAAK,SAAA,CAAU0B,CAAI,CAAC,CAAA,CAAI,CAAA,CAChDF,EAAM,MAAA,EAAUC,CAAAA,CAAOE,EAAWN,CAAAA,GACpC3C,CAAAA,CAAI,IAAA,CAAK,CAAE,IAAA,CAAM,IAAA,CAAK,UAAU4C,CAAAA,CAAKE,CAAK,CAAC,CAAA,CAAG,KAAA,CAAOA,CAAM,CAAC,CAAA,CAC5DA,CAAAA,CAAQ,EAAC,CACTC,CAAAA,CAAOF,CAAAA,CAAAA,CAETC,EAAM,IAAA,CAAKE,CAAI,EACfD,CAAAA,EAAQE,EACV,CACA,OAAIH,CAAAA,CAAM,MAAA,EAAQ9C,CAAAA,CAAI,IAAA,CAAK,CAAE,KAAM,IAAA,CAAK,SAAA,CAAU4C,CAAAA,CAAKE,CAAK,CAAC,CAAA,CAAG,MAAOA,CAAM,CAAC,CAAA,CACvE9C,CACT,CAGO,IAAMkD,EAAN,KAAkB,CAMvB,YACmBlB,CAAAA,CACAC,CAAAA,CAEAW,EACjB,CAJiB,IAAA,CAAA,QAAA,CAAAZ,CAAAA,CACA,IAAA,CAAA,MAAA,CAAAC,CAAAA,CAEA,IAAA,CAAA,IAAA,CAAAW,EATnB,IAAA,CAAQ,KAAA,CAAa,EAAC,CACtB,IAAA,CAAQ,KAAA,CAA8C,KACtD,IAAA,CAAQ,OAAA,CAAgC,IAAA,CACxC,IAAA,CAAQ,QAAA,CAAW,EAOhB,CAEH,GAAA,CAAIO,CAAAA,CAAiB,CACnB,IAAA,CAAK,KAAA,CAAM,IAAA,CAAKA,CAAM,CAAA,CAClB,IAAA,CAAK,KAAA,CAAM,MAAA,CAASzB,CAAAA,EAAW,IAAA,CAAK,MAAM,MAAA,CAAO,CAAA,CAAG,IAAA,CAAK,KAAA,CAAM,MAAA,CAASA,CAAS,EACjF,IAAA,CAAK,KAAA,CAAM,MAAA,EAAUF,CAAAA,EAAc,CAAC,IAAA,CAAK,SAAU,IAAA,CAAK,KAAA,GACvD,IAAA,CAAK,QAAA,CAASC,CAAQ,EAC7B,CAGA,MAAM,KAAA,EAAuB,CAC3B,KAAO,KAAK,OAAA,EAAS,MAAM,IAAA,CAAK,OAAA,CAChC,GAAK,IAAA,CAAK,MAAM,MAAA,CAChB,OAAA,IAAA,CAAK,OAAA,CAAU,IAAA,CAAK,IAAA,EAAK,CAAE,QAAQ,IAAO,IAAA,CAAK,QAAU,IAAK,CAAA,CACvD,KAAK,OACd,CAGA,WAAA,EAAoB,CAClB,GAAI,CAAC,KAAK,KAAA,CAAM,MAAA,CAAQ,OACxB,IAAM2B,CAAAA,CAAU,IAAA,CAAK,MACrB,IAAA,CAAK,KAAA,CAAQ,EAAC,CACd,IAAA,GAAW,CAAE,KAAAlB,CAAK,CAAA,GAAKO,EAAMW,CAAAA,CAASvB,CAAAA,CAAqB,KAAK,IAAI,CAAA,CAAG,CAErE,IAAMM,CAAAA,CAAYb,CAAAA,CAAWY,CAAI,CAAA,EAAKL,CAAAA,CACtCE,CAAAA,CAAK,IAAA,CAAK,QAAA,CAAU,IAAA,CAAK,OAAQG,CAAAA,CAAMC,CAAS,CAAA,CAAE,IAAA,CAAMK,CAAAA,EAAW,CAC7DL,GAAaK,CAAAA,GAAW,CAAA,EAAGT,CAAAA,CAAK,IAAA,CAAK,QAAA,CAAU,IAAA,CAAK,OAAQG,CAAI,EACtE,CAAC,EACH,CACF,CAEQ,SAAS5B,CAAAA,CAAkB,CAC7B,IAAA,CAAK,KAAA,GACT,IAAA,CAAK,KAAA,CAAQ,WAAW,IAAM,CAC5B,IAAA,CAAK,KAAA,CAAQ,IAAA,CACb,IAAA,CAAK,QACP,CAAA,CAAGA,CAAE,CAAA,EACP,CAEA,MAAc,IAAA,EAAsB,CAC9B,IAAA,CAAK,KAAA,GACP,YAAA,CAAa,IAAA,CAAK,KAAK,CAAA,CACvB,IAAA,CAAK,KAAA,CAAQ,IAAA,CAAA,CAEf,IAAM8C,CAAAA,CAAU,KAAK,KAAA,CACrB,IAAA,CAAK,KAAA,CAAQ,EAAC,CACd,IAAMC,EAAUZ,CAAAA,CAAMW,CAAAA,CAASxB,CAAAA,CAAgB,IAAA,CAAK,IAAI,CAAA,CACxD,QAAS,CAAA,CAAI,CAAA,CAAG,CAAA,CAAIyB,CAAAA,CAAQ,MAAA,CAAQ,CAAA,EAAA,CAAK,CACvC,IAAMb,CAAAA,CAAS,MAAMT,CAAAA,CAAK,IAAA,CAAK,QAAA,CAAU,KAAK,MAAA,CAAQsB,CAAAA,CAAQ,CAAC,CAAA,CAAE,IAAI,CAAA,CACrE,GAAId,CAAAA,CAAUC,CAAM,EAAG,CAErB,IAAMc,EAASD,CAAAA,CAAQ,KAAA,CAAM,CAAC,CAAA,CAAE,OAAA,CAASE,CAAAA,EAAMA,EAAE,KAAK,CAAA,CACtD,IAAA,CAAK,KAAA,CAAQD,CAAAA,CAAO,MAAA,CAAO,KAAK,KAAK,CAAA,CAAE,KAAA,CAAM,CAAC5B,CAAS,CAAA,CACvD,KAAK,QAAA,EAAA,CACL,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,GAAA,CAAID,CAAAA,CAAW,GAAK,IAAA,CAAK,QAAA,CAAUE,CAAc,CAAC,CAAA,CACrE,MACF,CAEA,IAAA,CAAK,QAAA,CAAW,EAClB,CACI,IAAA,CAAK,KAAA,CAAM,QAAQ,IAAA,CAAK,QAAA,CAASF,CAAQ,EAC/C,CACF","file":"chunk-X34YPHYT.mjs","sourcesContent":["/**\r\n * Small pieces everything else uses: IDs, clocks, and OTLP/JSON values.\r\n */\r\n\r\n/** Random bytes as lowercase hex. `crypto.getRandomValues` everywhere it exists; the fallback is about collisions, not secrecy. */\r\nexport function randomHex(bytes: number): string {\r\n const buf = new Uint8Array(bytes);\r\n const c = typeof crypto !== 'undefined' ? crypto : undefined;\r\n if (c && c.getRandomValues) c.getRandomValues(buf);\r\n else for (let i = 0; i < bytes; i++) buf[i] = (Math.random() * 256) | 0;\r\n let out = '';\r\n for (let i = 0; i < bytes; i++) out += (buf[i] < 16 ? '0' : '') + buf[i].toString(16);\r\n // All zeros means \"absent\" in W3C Trace Context; astronomically unlikely, but free to rule out.\r\n return /^0+$/.test(out) ? randomHex(bytes) : out;\r\n}\r\n\r\nexport const newTraceId = () => randomHex(16);\r\nexport const newSpanId = () => randomHex(8);\r\n\r\n/** Milliseconds since the epoch, with the sub-millisecond precision `performance` gives. */\r\nexport function now(): number {\r\n const perf = typeof performance !== 'undefined' ? performance : undefined;\r\n return perf && perf.timeOrigin ? perf.timeOrigin + perf.now() : Date.now();\r\n}\r\n\r\n/**\r\n * Epoch milliseconds as the decimal nanosecond string OTLP wants. Built as text: nanoseconds\r\n * since 1970 are past what a double holds exactly.\r\n */\r\nexport function toNanos(ms: number): string {\r\n const whole = Math.floor(ms);\r\n const fraction = Math.round((ms - whole) * 1e6);\r\n return fraction >= 1e6 ? `${whole + 1}000000` : `${whole}${String(fraction).padStart(6, '0')}`;\r\n}\r\n\r\n/** One OTLP AnyValue. */\r\nexport function anyValue(value: unknown, depth = 0): Record<string, unknown> {\r\n if (typeof value === 'string') return { stringValue: value };\r\n if (typeof value === 'boolean') return { boolValue: value };\r\n if (typeof value === 'number') return Number.isInteger(value) ? { intValue: value } : { doubleValue: value };\r\n if (value instanceof Error) return { stringValue: `${value.name}: ${value.message}` };\r\n if (value === null || value === undefined) return { stringValue: '' };\r\n if (depth < 4 && Array.isArray(value)) return { arrayValue: { values: value.map((v) => anyValue(v, depth + 1)) } };\r\n if (depth < 4 && typeof value === 'object') return { kvlistValue: { values: keyValues(value as Record<string, unknown>, depth + 1) } };\r\n return { stringValue: safeString(value) };\r\n}\r\n\r\n/** An attribute map as an OTLP KeyValue list. `undefined` values are left out. */\r\nexport function keyValues(attributes: Record<string, unknown>, depth = 0): Array<{ key: string; value: unknown }> {\r\n const out: Array<{ key: string; value: unknown }> = [];\r\n for (const key in attributes) {\r\n const value = attributes[key];\r\n if (value !== undefined) out.push({ key, value: anyValue(value, depth) });\r\n }\r\n return out;\r\n}\r\n\r\nexport function safeString(value: unknown): string {\r\n if (typeof value === 'string') return value;\r\n if (value instanceof Error) return `${value.name}: ${value.message}`;\r\n try {\r\n const json = JSON.stringify(value);\r\n return json === undefined ? String(value) : json;\r\n } catch {\r\n return String(value);\r\n }\r\n}\r\n\r\n/** Run `fn`, swallowing anything it throws: the SDK must never break the page it is on. */\r\nexport function safely<T>(fn: () => T): T | undefined {\r\n try {\r\n return fn();\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\nexport const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';\r\n\r\n/** UTF-8 byte length. */\r\nexport function byteLength(text: string): number {\r\n return typeof TextEncoder !== 'undefined' ? new TextEncoder().encode(text).length : text.length * 2;\r\n}\r\n","/**\r\n * Getting telemetry to Zharp from a page.\r\n *\r\n * Records are queued and sent in batches every few seconds (sooner when a batch fills), gzipped\r\n * where the browser can, and retried with backoff when the server or the network asks for it.\r\n * The queue is capped — when it is full the oldest go — so a page that is offline for an hour\r\n * does not grow without bound.\r\n *\r\n * When the page is hidden or closed everything left goes out at once with `keepalive`, which\r\n * lets the request outlive the page. (`sendBeacon` would too, but cannot send the key header.)\r\n */\r\nimport { byteLength } from './util';\r\n\r\nexport type Signal = 'traces' | 'logs' | 'replays';\r\n\r\nconst BATCH_SIZE = 100;\r\nconst FLUSH_MS = 5_000;\r\nconst MAX_QUEUE = 1_000;\r\nconst MAX_BACKOFF_MS = 5 * 60_000;\r\n/** A normal request carries at most this much; the server takes 10 MB, but one huge upload holds everything behind it. */\r\nexport const MAX_BODY_BYTES = 1_000_000;\r\n/** Browsers allow 64 KB of keepalive bodies in flight per page. */\r\nexport const MAX_KEEPALIVE_BYTES = 60_000;\r\n\r\nexport interface Endpoint {\r\n url: string;\r\n key: string;\r\n /** The page's fetch as it was before instrumentation, so the SDK never traces itself. */\r\n fetch: typeof fetch;\r\n}\r\n\r\n/** Shared by every sender on a page: one rejected key stops all of them, with one warning. */\r\nlet keyRejected = false;\r\n\r\n/**\r\n * POST one body. Resolves to the HTTP status, or 0 when the request never completed.\r\n */\r\nexport async function post(endpoint: Endpoint, signal: Signal, body: string, keepalive = false): Promise<number> {\r\n if (keyRejected) return 401;\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n Authorization: `Bearer ${endpoint.key}`,\r\n };\r\n let payload: BodyInit = body;\r\n // gzip when it pays and the browser can; a keepalive request is sent as the page goes away,\r\n // with no time for an async compression step.\r\n if (!keepalive && body.length > 2_048 && typeof CompressionStream !== 'undefined') {\r\n try {\r\n payload = await new Response(new Blob([body]).stream().pipeThrough(new CompressionStream('gzip'))).arrayBuffer();\r\n headers['Content-Encoding'] = 'gzip';\r\n } catch {\r\n payload = body;\r\n }\r\n }\r\n try {\r\n const response = await endpoint.fetch.call(globalThis, `${endpoint.url}/v1/${signal}`, {\r\n method: 'POST',\r\n headers,\r\n body: payload,\r\n keepalive,\r\n credentials: 'omit',\r\n });\r\n if (response.status === 401 || response.status === 403) {\r\n if (!keyRejected) {\r\n keyRejected = true;\r\n console.warn(`[Zharp] The key was rejected (HTTP ${response.status}); nothing more will be sent from this page. Check Zharp.init({ key }) and the app's allowed origins.`);\r\n }\r\n }\r\n return response.status;\r\n } catch {\r\n return 0;\r\n }\r\n}\r\n\r\n/** 429, 5xx and network failures are worth another try; anything else will fail the same way again. */\r\nexport const retryable = (status: number) => status === 0 || status === 429 || status >= 500;\r\n\r\n/**\r\n * Cut `items` into JSON bodies of at most `limit` bytes. An item too large for any body still\r\n * gets one of its own: whether it goes through is for the network to decide.\r\n */\r\nexport function split<T>(items: T[], limit: number, wrap: (group: T[]) => unknown): Array<{ body: string; items: T[] }> {\r\n const overhead = byteLength(JSON.stringify(wrap([])));\r\n const out: Array<{ body: string; items: T[] }> = [];\r\n let group: T[] = [];\r\n let size = overhead;\r\n for (const item of items) {\r\n const itemSize = byteLength(JSON.stringify(item)) + 1;\r\n if (group.length && size + itemSize > limit) {\r\n out.push({ body: JSON.stringify(wrap(group)), items: group });\r\n group = [];\r\n size = overhead;\r\n }\r\n group.push(item);\r\n size += itemSize;\r\n }\r\n if (group.length) out.push({ body: JSON.stringify(wrap(group)), items: group });\r\n return out;\r\n}\r\n\r\n/** A queue of OTLP records of one signal, and the timer that sends them. */\r\nexport class Exporter<T> {\r\n private queue: T[] = [];\r\n private timer: ReturnType<typeof setTimeout> | null = null;\r\n private sending: Promise<void> | null = null;\r\n private failures = 0;\r\n\r\n constructor(\r\n private readonly endpoint: Endpoint,\r\n private readonly signal: 'traces' | 'logs',\r\n /** Wraps records in the export request: resource, scope, and the signal's envelope. */\r\n private readonly wrap: (records: T[]) => unknown,\r\n ) {}\r\n\r\n add(record: T): void {\r\n this.queue.push(record);\r\n if (this.queue.length > MAX_QUEUE) this.queue.splice(0, this.queue.length - MAX_QUEUE);\r\n if (this.queue.length >= BATCH_SIZE && !this.failures) this.flush();\r\n else this.schedule(FLUSH_MS);\r\n }\r\n\r\n /** Send everything queued. One send at a time; a call during one waits and then sends what is left. */\r\n async flush(): Promise<void> {\r\n while (this.sending) await this.sending;\r\n if (!this.queue.length) return;\r\n this.sending = this.send().finally(() => (this.sending = null));\r\n return this.sending;\r\n }\r\n\r\n /** The page is going away: everything left, now, with keepalive. Nothing to wait for. */\r\n flushOnHide(): void {\r\n if (!this.queue.length) return;\r\n const records = this.queue;\r\n this.queue = [];\r\n for (const { body } of split(records, MAX_KEEPALIVE_BYTES, this.wrap)) {\r\n // Too large for keepalive's budget: an ordinary request may still finish before the page dies.\r\n const keepalive = byteLength(body) <= MAX_KEEPALIVE_BYTES;\r\n post(this.endpoint, this.signal, body, keepalive).then((status) => {\r\n if (keepalive && status === 0) post(this.endpoint, this.signal, body);\r\n });\r\n }\r\n }\r\n\r\n private schedule(ms: number): void {\r\n if (this.timer) return;\r\n this.timer = setTimeout(() => {\r\n this.timer = null;\r\n this.flush();\r\n }, ms);\r\n }\r\n\r\n private async send(): Promise<void> {\r\n if (this.timer) {\r\n clearTimeout(this.timer);\r\n this.timer = null;\r\n }\r\n const records = this.queue;\r\n this.queue = [];\r\n const batches = split(records, MAX_BODY_BYTES, this.wrap);\r\n for (let i = 0; i < batches.length; i++) {\r\n const status = await post(this.endpoint, this.signal, batches[i].body);\r\n if (retryable(status)) {\r\n // This batch and the ones after it go back to the front, oldest first, and wait.\r\n const unsent = batches.slice(i).flatMap((b) => b.items);\r\n this.queue = unsent.concat(this.queue).slice(-MAX_QUEUE);\r\n this.failures++;\r\n this.schedule(Math.min(FLUSH_MS * 2 ** this.failures, MAX_BACKOFF_MS));\r\n return;\r\n }\r\n // Delivered, or refused for good (400, 401/403): either way it is done.\r\n this.failures = 0;\r\n }\r\n if (this.queue.length) this.schedule(FLUSH_MS);\r\n }\r\n}\r\n"]}
@@ -0,0 +1,159 @@
1
+ /** Anything JSON can hold. Objects and arrays are sent as they are and shown as JSON. */
2
+ type Attributes = Record<string, unknown>;
3
+ interface ZharpUser {
4
+ /** Sent as `enduser.id`. An ID or an email — whatever identifies the person in your app. */
5
+ id: string | number;
6
+ /** Sent as `enduser.email`. */
7
+ email?: string;
8
+ /** Sent as `enduser.name`. */
9
+ name?: string;
10
+ }
11
+ /** One recorded rrweb event, as `replay.beforeSend` sees it. Declared here so the main types never load rrweb. */
12
+ interface ReplayEvent {
13
+ type: number;
14
+ data: unknown;
15
+ timestamp: number;
16
+ [key: string]: unknown;
17
+ }
18
+ interface ReplayOptions {
19
+ /**
20
+ * Fraction of sessions recorded from start to end, 0 to 1. Default 0. Decided once per
21
+ * session; a session that is not sampled never downloads the recorder.
22
+ */
23
+ sampleRate?: number;
24
+ /**
25
+ * Fraction of the remaining sessions that keep the last minute in memory and upload it —
26
+ * with the rest of the session — only when an error happens. Default 0. `1.0` means every
27
+ * session with an error comes with a replay, at no upload cost for the ones without.
28
+ */
29
+ onErrorSampleRate?: number;
30
+ /**
31
+ * Mask what is typed into every input, textarea and select. Default **true**. Turning it off
32
+ * sends passwords and card numbers unless `blockSelector` covers them.
33
+ */
34
+ maskAllInputs?: boolean;
35
+ /** Mask all text on the page, not just inputs. Default false. */
36
+ maskAllText?: boolean;
37
+ /** Elements left out of the recording entirely (a payment iframe, a private document). */
38
+ blockSelector?: string;
39
+ /** Elements whose text is masked. */
40
+ maskTextSelector?: string;
41
+ /** Called with each batch before it is sent. Return false to drop it. */
42
+ beforeSend?: (events: ReplayEvent[]) => boolean;
43
+ /**
44
+ * Where the script-tag build loads the recorder from. Default: `zharp-replay.min.js` next to
45
+ * `zharp.min.js`. Bundled builds ignore it — the recorder is a lazily imported chunk there.
46
+ */
47
+ recorderUrl?: string;
48
+ }
49
+ interface BrowserOptions {
50
+ /** The app's key from the Zharp dashboard (`zpk_…`). Safe to ship in a page: it can only send. */
51
+ key: string;
52
+ /** `service.name`, e.g. `checkout-web`. */
53
+ service?: string;
54
+ /** `deployment.environment.name`, e.g. `production`. */
55
+ environment?: string;
56
+ /** `service.version`, e.g. `web@2.3.0` or a commit SHA. */
57
+ release?: string;
58
+ /** Default `https://api.zharp.io/api/v1/otlp`. */
59
+ endpoint?: string;
60
+ /**
61
+ * Hosts (or URL prefixes, or patterns) that get `traceparent` and `baggage` headers, so their
62
+ * spans join the page's trace. Same-origin requests always do. Nothing else does: an unknown
63
+ * header on a third-party request can make it fail CORS.
64
+ *
65
+ * `"api.example.com"` matches that host on any port and scheme; `"https://api.example.com/v2"`
66
+ * matches URLs starting with it; a RegExp is tested against the full URL.
67
+ */
68
+ tracingOrigins?: Array<string | RegExp>;
69
+ /**
70
+ * Traces. Default **true**. `false` is logs only: logs, errors, the user, the session and tags
71
+ * are sent as usual, but no spans are made (no page load, navigation, click or request spans),
72
+ * `fetch` and `XMLHttpRequest` are not patched, and no `traceparent` or `baggage` header is
73
+ * added to any request. `startSpan` just runs its callback. Session replay is independent —
74
+ * it follows `replay` either way; without traces a replay simply has no trace ids linked to it.
75
+ */
76
+ tracing?: boolean;
77
+ /** Fraction of traces to keep, 0 to 1. Default 1. Errors, logs and replays are not affected. */
78
+ sampleRate?: number;
79
+ /** Session replay. Off unless a sample rate is set. */
80
+ replay?: ReplayOptions;
81
+ /**
82
+ * Copy `console.*` calls into Zharp as logs: `true` for all levels, or a list such as
83
+ * `["warn", "error"]`. Default **off** — a page's console is mostly other people's libraries.
84
+ */
85
+ captureConsole?: boolean | Array<'debug' | 'log' | 'info' | 'warn' | 'error'>;
86
+ /** Spans for clicks that lead to requests. Default true. */
87
+ interactions?: boolean;
88
+ /** URLs never traced (no span, no headers), e.g. analytics beacons. */
89
+ ignoreUrls?: Array<string | RegExp>;
90
+ /** Minutes without activity before a new session starts. Default 30. */
91
+ sessionTimeoutMinutes?: number;
92
+ }
93
+
94
+ declare const SEVERITY: {
95
+ readonly trace: 1;
96
+ readonly debug: 5;
97
+ readonly info: 9;
98
+ readonly warn: 13;
99
+ readonly error: 17;
100
+ readonly fatal: 21;
101
+ };
102
+ type Level = keyof typeof SEVERITY;
103
+ type LogFn = (message: unknown, attributes?: Attributes) => void;
104
+ /** A manual span, as `Zharp.startSpan` hands it to its callback. */
105
+ interface ZharpSpan {
106
+ readonly traceId: string;
107
+ readonly spanId: string;
108
+ setAttribute(key: string, value: unknown): ZharpSpan;
109
+ setAttributes(attributes: Attributes): ZharpSpan;
110
+ }
111
+ declare class ZharpBrowser {
112
+ private running;
113
+ private user;
114
+ private readonly tags;
115
+ /** Errors already reported, so one caught by the app and then rethrown is sent once. */
116
+ private readonly reported;
117
+ readonly log: Record<Level, LogFn>;
118
+ get isActive(): boolean;
119
+ /** The current session's ID, e.g. to show on a support page. */
120
+ get sessionId(): string | undefined;
121
+ init(options: BrowserOptions): void;
122
+ private start;
123
+ /** The page load span: from navigation start until the load event, its requests, and a quiet second. */
124
+ private startPageLoad;
125
+ /**
126
+ * Who is using the page: `enduser.id` (and `enduser.email`, `enduser.name`) on everything sent
127
+ * from now on, and in the `baggage` header so the back end's spans and logs carry it too.
128
+ * Call it after login and on every page load while logged in.
129
+ */
130
+ setUser(user: ZharpUser | null): void;
131
+ /** Forget the user and start a new session — call it on logout. */
132
+ clearUser(): void;
133
+ /** An attribute on every span and log from now on. `null` removes it. */
134
+ setTag(key: string, value: string | number | boolean | null | undefined): void;
135
+ /** Report an error you caught. Sent as handled, and grouped into an issue with its other occurrences. */
136
+ captureError(error: unknown, attributes?: Attributes): void;
137
+ /**
138
+ * Run `fn` in a span named `name`. Requests and logs from its synchronous part belong to it;
139
+ * a promise it returns ends the span when it settles. Errors pass through unchanged.
140
+ */
141
+ startSpan<T>(name: string, fn: (span: ZharpSpan) => T, attributes?: Attributes): T;
142
+ /** Send everything waiting now. */
143
+ flush(): Promise<void>;
144
+ /** Flush and stop: every patch is undone and nothing more is recorded. */
145
+ close(): Promise<void>;
146
+ /** Session, user and tags: on every span and log, as they are when it is sent. */
147
+ private contextAttributes;
148
+ /** The `baggage` header value: session and user, for the back end to copy onto its spans. */
149
+ private baggage;
150
+ private emit;
151
+ private report;
152
+ }
153
+
154
+ /** Sent as `telemetry.sdk.version`. Bumped with package.json. */
155
+ declare const VERSION = "0.1.0";
156
+
157
+ declare const Zharp: ZharpBrowser;
158
+
159
+ export { type Attributes, type BrowserOptions, type ReplayEvent, type ReplayOptions, VERSION, Zharp, ZharpBrowser, type ZharpSpan, type ZharpUser, Zharp as default };
@@ -0,0 +1,159 @@
1
+ /** Anything JSON can hold. Objects and arrays are sent as they are and shown as JSON. */
2
+ type Attributes = Record<string, unknown>;
3
+ interface ZharpUser {
4
+ /** Sent as `enduser.id`. An ID or an email — whatever identifies the person in your app. */
5
+ id: string | number;
6
+ /** Sent as `enduser.email`. */
7
+ email?: string;
8
+ /** Sent as `enduser.name`. */
9
+ name?: string;
10
+ }
11
+ /** One recorded rrweb event, as `replay.beforeSend` sees it. Declared here so the main types never load rrweb. */
12
+ interface ReplayEvent {
13
+ type: number;
14
+ data: unknown;
15
+ timestamp: number;
16
+ [key: string]: unknown;
17
+ }
18
+ interface ReplayOptions {
19
+ /**
20
+ * Fraction of sessions recorded from start to end, 0 to 1. Default 0. Decided once per
21
+ * session; a session that is not sampled never downloads the recorder.
22
+ */
23
+ sampleRate?: number;
24
+ /**
25
+ * Fraction of the remaining sessions that keep the last minute in memory and upload it —
26
+ * with the rest of the session — only when an error happens. Default 0. `1.0` means every
27
+ * session with an error comes with a replay, at no upload cost for the ones without.
28
+ */
29
+ onErrorSampleRate?: number;
30
+ /**
31
+ * Mask what is typed into every input, textarea and select. Default **true**. Turning it off
32
+ * sends passwords and card numbers unless `blockSelector` covers them.
33
+ */
34
+ maskAllInputs?: boolean;
35
+ /** Mask all text on the page, not just inputs. Default false. */
36
+ maskAllText?: boolean;
37
+ /** Elements left out of the recording entirely (a payment iframe, a private document). */
38
+ blockSelector?: string;
39
+ /** Elements whose text is masked. */
40
+ maskTextSelector?: string;
41
+ /** Called with each batch before it is sent. Return false to drop it. */
42
+ beforeSend?: (events: ReplayEvent[]) => boolean;
43
+ /**
44
+ * Where the script-tag build loads the recorder from. Default: `zharp-replay.min.js` next to
45
+ * `zharp.min.js`. Bundled builds ignore it — the recorder is a lazily imported chunk there.
46
+ */
47
+ recorderUrl?: string;
48
+ }
49
+ interface BrowserOptions {
50
+ /** The app's key from the Zharp dashboard (`zpk_…`). Safe to ship in a page: it can only send. */
51
+ key: string;
52
+ /** `service.name`, e.g. `checkout-web`. */
53
+ service?: string;
54
+ /** `deployment.environment.name`, e.g. `production`. */
55
+ environment?: string;
56
+ /** `service.version`, e.g. `web@2.3.0` or a commit SHA. */
57
+ release?: string;
58
+ /** Default `https://api.zharp.io/api/v1/otlp`. */
59
+ endpoint?: string;
60
+ /**
61
+ * Hosts (or URL prefixes, or patterns) that get `traceparent` and `baggage` headers, so their
62
+ * spans join the page's trace. Same-origin requests always do. Nothing else does: an unknown
63
+ * header on a third-party request can make it fail CORS.
64
+ *
65
+ * `"api.example.com"` matches that host on any port and scheme; `"https://api.example.com/v2"`
66
+ * matches URLs starting with it; a RegExp is tested against the full URL.
67
+ */
68
+ tracingOrigins?: Array<string | RegExp>;
69
+ /**
70
+ * Traces. Default **true**. `false` is logs only: logs, errors, the user, the session and tags
71
+ * are sent as usual, but no spans are made (no page load, navigation, click or request spans),
72
+ * `fetch` and `XMLHttpRequest` are not patched, and no `traceparent` or `baggage` header is
73
+ * added to any request. `startSpan` just runs its callback. Session replay is independent —
74
+ * it follows `replay` either way; without traces a replay simply has no trace ids linked to it.
75
+ */
76
+ tracing?: boolean;
77
+ /** Fraction of traces to keep, 0 to 1. Default 1. Errors, logs and replays are not affected. */
78
+ sampleRate?: number;
79
+ /** Session replay. Off unless a sample rate is set. */
80
+ replay?: ReplayOptions;
81
+ /**
82
+ * Copy `console.*` calls into Zharp as logs: `true` for all levels, or a list such as
83
+ * `["warn", "error"]`. Default **off** — a page's console is mostly other people's libraries.
84
+ */
85
+ captureConsole?: boolean | Array<'debug' | 'log' | 'info' | 'warn' | 'error'>;
86
+ /** Spans for clicks that lead to requests. Default true. */
87
+ interactions?: boolean;
88
+ /** URLs never traced (no span, no headers), e.g. analytics beacons. */
89
+ ignoreUrls?: Array<string | RegExp>;
90
+ /** Minutes without activity before a new session starts. Default 30. */
91
+ sessionTimeoutMinutes?: number;
92
+ }
93
+
94
+ declare const SEVERITY: {
95
+ readonly trace: 1;
96
+ readonly debug: 5;
97
+ readonly info: 9;
98
+ readonly warn: 13;
99
+ readonly error: 17;
100
+ readonly fatal: 21;
101
+ };
102
+ type Level = keyof typeof SEVERITY;
103
+ type LogFn = (message: unknown, attributes?: Attributes) => void;
104
+ /** A manual span, as `Zharp.startSpan` hands it to its callback. */
105
+ interface ZharpSpan {
106
+ readonly traceId: string;
107
+ readonly spanId: string;
108
+ setAttribute(key: string, value: unknown): ZharpSpan;
109
+ setAttributes(attributes: Attributes): ZharpSpan;
110
+ }
111
+ declare class ZharpBrowser {
112
+ private running;
113
+ private user;
114
+ private readonly tags;
115
+ /** Errors already reported, so one caught by the app and then rethrown is sent once. */
116
+ private readonly reported;
117
+ readonly log: Record<Level, LogFn>;
118
+ get isActive(): boolean;
119
+ /** The current session's ID, e.g. to show on a support page. */
120
+ get sessionId(): string | undefined;
121
+ init(options: BrowserOptions): void;
122
+ private start;
123
+ /** The page load span: from navigation start until the load event, its requests, and a quiet second. */
124
+ private startPageLoad;
125
+ /**
126
+ * Who is using the page: `enduser.id` (and `enduser.email`, `enduser.name`) on everything sent
127
+ * from now on, and in the `baggage` header so the back end's spans and logs carry it too.
128
+ * Call it after login and on every page load while logged in.
129
+ */
130
+ setUser(user: ZharpUser | null): void;
131
+ /** Forget the user and start a new session — call it on logout. */
132
+ clearUser(): void;
133
+ /** An attribute on every span and log from now on. `null` removes it. */
134
+ setTag(key: string, value: string | number | boolean | null | undefined): void;
135
+ /** Report an error you caught. Sent as handled, and grouped into an issue with its other occurrences. */
136
+ captureError(error: unknown, attributes?: Attributes): void;
137
+ /**
138
+ * Run `fn` in a span named `name`. Requests and logs from its synchronous part belong to it;
139
+ * a promise it returns ends the span when it settles. Errors pass through unchanged.
140
+ */
141
+ startSpan<T>(name: string, fn: (span: ZharpSpan) => T, attributes?: Attributes): T;
142
+ /** Send everything waiting now. */
143
+ flush(): Promise<void>;
144
+ /** Flush and stop: every patch is undone and nothing more is recorded. */
145
+ close(): Promise<void>;
146
+ /** Session, user and tags: on every span and log, as they are when it is sent. */
147
+ private contextAttributes;
148
+ /** The `baggage` header value: session and user, for the back end to copy onto its spans. */
149
+ private baggage;
150
+ private emit;
151
+ private report;
152
+ }
153
+
154
+ /** Sent as `telemetry.sdk.version`. Bumped with package.json. */
155
+ declare const VERSION = "0.1.0";
156
+
157
+ declare const Zharp: ZharpBrowser;
158
+
159
+ export { type Attributes, type BrowserOptions, type ReplayEvent, type ReplayOptions, VERSION, Zharp, ZharpBrowser, type ZharpSpan, type ZharpUser, Zharp as default };