@orbitai/orbitrace 1.2.1 → 1.3.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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @orbitai/orbitrace
2
2
 
3
- Error tracking and monitoring for backend Node.js applications.
3
+ Error tracking and monitoring for Node.js services and browser apps.
4
4
 
5
- Orbitrace is server-side only. Do not initialize this package in browser or frontend code, because the Orbitrace API key is a backend secret and must not be exposed in public bundles.
5
+ The package has two entries. `@orbitai/orbitrace` runs on servers with the project's API key, which is a secret. `@orbitai/orbitrace/browser` runs in browsers with the project's public key, which is accepted only from the origins allowed in the Orbitrace dashboard. Never put the API key in a browser bundle; the server entry throws if it is loaded in a browser.
6
6
 
7
7
  ## Installation
8
8
 
@@ -131,6 +131,43 @@ exports.checkout = onRequest(orbitrace.wrap(async (req, res) => {
131
131
  }));
132
132
  ```
133
133
 
134
+ ## Browser Setup
135
+
136
+ Install the same package and import the browser entry. It needs a browser with `fetch`, so every current browser.
137
+
138
+ ```javascript
139
+ import Orbitrace from "@orbitai/orbitrace/browser";
140
+
141
+ Orbitrace.init({
142
+ publicKey: "the project's public key",
143
+ orgId: "your-org-id",
144
+ projectId: "your-project-id",
145
+ endpoint: "your-endpoint",
146
+ environment: import.meta.env.MODE,
147
+ version: "1.0.0",
148
+ service: "webapp",
149
+ });
150
+ ```
151
+
152
+ The public key can be read by anyone who opens your site. That is fine: it only creates events, the API accepts it only from the origins allowed for the project (dashboard, Documentation, Browser apps), and the project's rate limit and your organization's quota bound what it can do. `init` throws if it is given an `apiKey`.
153
+
154
+ Automatic capture is on unless `autoCapture: false`. Uncaught errors and unhandled promise rejections are reported; breadcrumbs are recorded from `console` calls, `fetch` requests, clicks (the element, never its text) and navigation. Switch parts off with `autoCapture: { console: false, clicks: false }`; the parts are `exceptions`, `console`, `fetch`, `clicks` and `navigation`.
155
+
156
+ `captureException`, `captureMessage`, `addBreadcrumb`, `flush` and `close` work as on servers. There are no scopes: one tab is one scope.
157
+
158
+ ```javascript
159
+ // After sign-in, so issues show who was affected. Send a stable id, never personal data.
160
+ Orbitrace.setUser({ id: user.uid });
161
+ // On sign-out:
162
+ Orbitrace.setUser(null);
163
+
164
+ // Vue 3: errors Vue catches inside components are reported with the component name,
165
+ // then passed to the app's own errorHandler, or logged as Vue would.
166
+ app.use(Orbitrace.vue);
167
+ ```
168
+
169
+ Every browser event carries `page`: the URL (without query string or fragment), referrer, viewport and language. Requests use `keepalive`, so an error reported just before the page closes still arrives. Production stacks are minified; issues still group correctly across deploys because file hashes and line numbers are ignored, so keep `version` set to your release to see which build introduced a problem.
170
+
134
171
  ## Data Collected
135
172
 
136
173
  Each event stores:
@@ -138,7 +175,7 @@ Each event stores:
138
175
  - The error message (truncated to 200 characters) and stack trace, or the message text.
139
176
  - The `metadata` object you pass, plus `env`, `version` and `service` from your configuration.
140
177
  - Breadcrumbs, when there are any: the last 30 recorded before the event.
141
- - The IP address and user agent of the server that sent the request, with an approximate location derived from that IP.
178
+ - The IP address and user agent of the server that sent the request, with an approximate location derived from that IP. For browser events, the user agent and approximate location of the user; their IP address is not stored.
142
179
 
143
180
  Before sending, metadata values whose key contains `password`, `passwd`, `secret`, `token`, `authorization`, `cookie`, `apikey`, `privatekey`, `credential` or `session` (ignoring case, `-` and `_`) are replaced with `[Filtered]`, at any depth. Error messages and stack traces are sent as they are.
144
181
 
@@ -158,6 +195,6 @@ ORBITRACE_DISABLED=false
158
195
 
159
196
  ## Security Model
160
197
 
161
- Orbitrace authenticates ingestion requests with `ORBITRACE_API_KEY`. Keep this key only in backend environments such as servers, workers, functions, and CI jobs. Do not use `VITE_`, `NEXT_PUBLIC_`, or other public frontend environment variables for Orbitrace credentials.
198
+ Each project has two keys. The API key authenticates servers: keep it only in backend environments such as servers, workers, functions, and CI jobs, and never in `VITE_`, `NEXT_PUBLIC_` or other public frontend environment variables. The public key authenticates browsers: it may ship in bundles, and the API accepts it only from the project's allowed origins. Both keys can only create events.
162
199
 
163
- If this package is initialized in a browser runtime, it throws an error before sending events.
200
+ The server entry throws if it is initialized in a browser, and the browser entry throws if it is given an API key.
@@ -0,0 +1,520 @@
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __typeError = (msg) => {
3
+ throw TypeError(msg);
4
+ };
5
+ var __commonJS = (cb, mod) => function __require() {
6
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
7
+ };
8
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
9
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
10
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
11
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
12
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
13
+
14
+ // src/shared.js
15
+ var require_shared = __commonJS({
16
+ "src/shared.js"(exports2, module2) {
17
+ var REQUEST_TIMEOUT_MS = 5e3;
18
+ var DEDUPE_WINDOW_MS = 1e4;
19
+ var MAX_IN_FLIGHT = 10;
20
+ var DEFAULT_RETRY_AFTER_SECONDS = 60;
21
+ var MAX_BREADCRUMBS = 30;
22
+ var MAX_BREADCRUMB_MESSAGE_LENGTH = 200;
23
+ var BREADCRUMB_LEVELS = ["debug", "info", "warning", "error"];
24
+ var CONSOLE_LEVELS = { debug: "debug", log: "info", info: "info", warn: "warning", error: "error" };
25
+ var SENSITIVE_KEY_PARTS = [
26
+ "password",
27
+ "passwd",
28
+ "secret",
29
+ "token",
30
+ "authorization",
31
+ "cookie",
32
+ "apikey",
33
+ "privatekey",
34
+ "credential",
35
+ "session"
36
+ ];
37
+ var FILTERED_VALUE = "[Filtered]";
38
+ function isSensitiveKey(key) {
39
+ const normalized = key.toLowerCase().replace(/[-_]/g, "");
40
+ return SENSITIVE_KEY_PARTS.some((part) => normalized.includes(part));
41
+ }
42
+ function filterSensitive2(value, ancestors = /* @__PURE__ */ new WeakSet()) {
43
+ if (value === null || typeof value !== "object") {
44
+ return value;
45
+ }
46
+ if (ancestors.has(value)) {
47
+ return "[Circular]";
48
+ }
49
+ const isArray = Array.isArray(value);
50
+ const prototype = Object.getPrototypeOf(value);
51
+ if (!isArray && prototype !== Object.prototype && prototype !== null) {
52
+ return value;
53
+ }
54
+ ancestors.add(value);
55
+ const filtered = isArray ? value.map((item) => filterSensitive2(item, ancestors)) : Object.fromEntries(
56
+ Object.entries(value).map(([key, item]) => [
57
+ key,
58
+ isSensitiveKey(key) ? FILTERED_VALUE : filterSensitive2(item, ancestors)
59
+ ])
60
+ );
61
+ ancestors.delete(value);
62
+ return filtered;
63
+ }
64
+ function truncate2(value, maxLength) {
65
+ return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
66
+ }
67
+ function cleanUrl2(url) {
68
+ try {
69
+ const parsed = new URL(url);
70
+ return `${parsed.origin}${parsed.pathname}`;
71
+ } catch {
72
+ return String(url).split(/[?#]/)[0];
73
+ }
74
+ }
75
+ function pushBreadcrumb2(breadcrumbs, { category = "manual", message = "", level = "info", data } = {}) {
76
+ try {
77
+ breadcrumbs.push({
78
+ timestamp: Date.now(),
79
+ category: String(category).slice(0, 64),
80
+ level: BREADCRUMB_LEVELS.includes(level) ? level : "info",
81
+ message: truncate2(String(message), MAX_BREADCRUMB_MESSAGE_LENGTH),
82
+ ...data !== void 0 && { data: filterSensitive2(data) }
83
+ });
84
+ if (breadcrumbs.length > MAX_BREADCRUMBS) {
85
+ breadcrumbs.shift();
86
+ }
87
+ } catch {
88
+ }
89
+ }
90
+ function httpBreadcrumb({ method, url, status, error, started }) {
91
+ const data = { method, url: cleanUrl2(url), durationMs: Date.now() - started };
92
+ if (error) {
93
+ return { category: "http", level: "error", message: `${method} ${data.url} failed`, data: { ...data, error } };
94
+ }
95
+ return {
96
+ category: "http",
97
+ level: status >= 500 ? "error" : status >= 400 ? "warning" : "info",
98
+ message: `${method} ${data.url} ${status}`,
99
+ data: { ...data, status }
100
+ };
101
+ }
102
+ function recordFetch2({ endpoint, addBreadcrumb, restore }) {
103
+ if (typeof globalThis.fetch !== "function") {
104
+ return null;
105
+ }
106
+ const originalFetch = globalThis.fetch;
107
+ globalThis.fetch = async function orbitraceFetch(input, init) {
108
+ const url = typeof input === "string" || input instanceof URL ? String(input) : input?.url;
109
+ if (!url || url.startsWith(endpoint)) {
110
+ return originalFetch.call(this, input, init);
111
+ }
112
+ const method = String(init?.method || input?.method || "GET").toUpperCase();
113
+ const started = Date.now();
114
+ try {
115
+ const response = await originalFetch.call(this, input, init);
116
+ addBreadcrumb(httpBreadcrumb({ method, url, status: response.status, started }));
117
+ return response;
118
+ } catch (error) {
119
+ addBreadcrumb(httpBreadcrumb({ method, url, error: error?.message, started }));
120
+ throw error;
121
+ }
122
+ };
123
+ restore.push(() => {
124
+ globalThis.fetch = originalFetch;
125
+ });
126
+ return originalFetch;
127
+ }
128
+ function recordConsole2({ format, addBreadcrumb, restore }) {
129
+ let recording = false;
130
+ for (const [method, level] of Object.entries(CONSOLE_LEVELS)) {
131
+ const original = console[method];
132
+ if (typeof original !== "function") {
133
+ continue;
134
+ }
135
+ console[method] = function orbitraceConsole(...args) {
136
+ if (!recording) {
137
+ recording = true;
138
+ try {
139
+ const message = format(args);
140
+ if (!message.startsWith("Orbitrace:")) {
141
+ addBreadcrumb({ category: "console", level, message });
142
+ }
143
+ } catch {
144
+ } finally {
145
+ recording = false;
146
+ }
147
+ }
148
+ return original.apply(this, args);
149
+ };
150
+ restore.push(() => {
151
+ console[method] = original;
152
+ });
153
+ }
154
+ }
155
+ var _endpoint, _headers, _getFetch, _requestOptions, _pending, _inFlight, _pausedUntil, _Sender_instances, flushPending_fn, canSend_fn, send_fn, post_fn;
156
+ var Sender2 = class {
157
+ // `getFetch` returns the fetch to use at send time, so a wrapped global fetch is never recorded.
158
+ // `requestOptions(body)` adds fetch options for one request, such as keepalive in browsers.
159
+ constructor({ endpoint, headers, getFetch = () => globalThis.fetch, requestOptions = () => ({}) }) {
160
+ __privateAdd(this, _Sender_instances);
161
+ __privateAdd(this, _endpoint);
162
+ __privateAdd(this, _headers);
163
+ __privateAdd(this, _getFetch);
164
+ __privateAdd(this, _requestOptions);
165
+ __privateAdd(this, _pending, /* @__PURE__ */ new Map());
166
+ __privateAdd(this, _inFlight, /* @__PURE__ */ new Set());
167
+ __privateAdd(this, _pausedUntil, 0);
168
+ __privateSet(this, _endpoint, endpoint);
169
+ __privateSet(this, _headers, headers);
170
+ __privateSet(this, _getFetch, getFetch);
171
+ __privateSet(this, _requestOptions, requestOptions);
172
+ }
173
+ // Queues the event and returns at once; it is sent in the background. flush() waits for delivery.
174
+ capture(event, payload) {
175
+ const key = JSON.stringify([event, payload.message, payload.stack ?? null]);
176
+ const pending = __privateGet(this, _pending).get(key);
177
+ if (pending) {
178
+ pending.count++;
179
+ return;
180
+ }
181
+ if (!__privateMethod(this, _Sender_instances, canSend_fn).call(this)) {
182
+ return;
183
+ }
184
+ const entry = { event, payload, count: 0 };
185
+ entry.timer = setTimeout(() => __privateMethod(this, _Sender_instances, flushPending_fn).call(this, key), DEDUPE_WINDOW_MS);
186
+ entry.timer.unref?.();
187
+ __privateGet(this, _pending).set(key, entry);
188
+ __privateMethod(this, _Sender_instances, send_fn).call(this, event, payload, 1);
189
+ }
190
+ // Sends counts still held by the dedupe window and waits for in-flight requests.
191
+ async flush() {
192
+ await Promise.all([...__privateGet(this, _pending).keys()].map((key) => __privateMethod(this, _Sender_instances, flushPending_fn).call(this, key)));
193
+ await Promise.all(__privateGet(this, _inFlight));
194
+ }
195
+ };
196
+ _endpoint = new WeakMap();
197
+ _headers = new WeakMap();
198
+ _getFetch = new WeakMap();
199
+ _requestOptions = new WeakMap();
200
+ _pending = new WeakMap();
201
+ _inFlight = new WeakMap();
202
+ _pausedUntil = new WeakMap();
203
+ _Sender_instances = new WeakSet();
204
+ flushPending_fn = async function(key) {
205
+ const entry = __privateGet(this, _pending).get(key);
206
+ if (!entry) {
207
+ return;
208
+ }
209
+ __privateGet(this, _pending).delete(key);
210
+ clearTimeout(entry.timer);
211
+ if (entry.count > 0 && __privateMethod(this, _Sender_instances, canSend_fn).call(this)) {
212
+ await __privateMethod(this, _Sender_instances, send_fn).call(this, entry.event, entry.payload, entry.count);
213
+ }
214
+ };
215
+ canSend_fn = function() {
216
+ return Date.now() >= __privateGet(this, _pausedUntil) && __privateGet(this, _inFlight).size < MAX_IN_FLIGHT;
217
+ };
218
+ send_fn = function(event, payload, count) {
219
+ const request = __privateMethod(this, _Sender_instances, post_fn).call(this, event, payload, count).finally(() => __privateGet(this, _inFlight).delete(request));
220
+ __privateGet(this, _inFlight).add(request);
221
+ return request;
222
+ };
223
+ post_fn = async function(event, payload, count) {
224
+ try {
225
+ const body = JSON.stringify({ event, ...count > 1 && { count }, payload });
226
+ const response = await __privateGet(this, _getFetch).call(this)(__privateGet(this, _endpoint), {
227
+ method: "POST",
228
+ headers: { "Content-Type": "application/json", ...__privateGet(this, _headers) },
229
+ body,
230
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
231
+ ...__privateGet(this, _requestOptions).call(this, body)
232
+ });
233
+ if (response.status === 429) {
234
+ const retryAfterSeconds = Number(response.headers.get("retry-after")) || DEFAULT_RETRY_AFTER_SECONDS;
235
+ __privateSet(this, _pausedUntil, Date.now() + retryAfterSeconds * 1e3);
236
+ console.warn(`Orbitrace: rate limited, dropping events for ${retryAfterSeconds}s`);
237
+ return;
238
+ }
239
+ if (!response.ok) {
240
+ console.error(`Orbitrace: API responded with status ${response.status}`);
241
+ return;
242
+ }
243
+ return await response.json();
244
+ } catch (error) {
245
+ console.error("Orbitrace: Error sending data to API", error.message);
246
+ }
247
+ };
248
+ module2.exports = {
249
+ MAX_BREADCRUMB_MESSAGE_LENGTH,
250
+ Sender: Sender2,
251
+ cleanUrl: cleanUrl2,
252
+ filterSensitive: filterSensitive2,
253
+ pushBreadcrumb: pushBreadcrumb2,
254
+ recordConsole: recordConsole2,
255
+ recordFetch: recordFetch2,
256
+ truncate: truncate2
257
+ };
258
+ }
259
+ });
260
+
261
+ // src/browser.js
262
+ var { Sender, cleanUrl, filterSensitive, pushBreadcrumb, recordConsole, recordFetch, truncate } = require_shared();
263
+ var MAX_KEEPALIVE_BODY_BYTES = 60 * 1024;
264
+ var MAX_CONSOLE_ARG_LENGTH = 200;
265
+ function formatConsoleArgs(args) {
266
+ return args.map((arg) => {
267
+ if (typeof arg === "string") {
268
+ return arg;
269
+ }
270
+ if (arg instanceof Error) {
271
+ return arg.stack || arg.message;
272
+ }
273
+ try {
274
+ return truncate(JSON.stringify(arg), MAX_CONSOLE_ARG_LENGTH);
275
+ } catch {
276
+ return String(arg);
277
+ }
278
+ }).join(" ");
279
+ }
280
+ function describeElement(element) {
281
+ if (!element || typeof element.tagName !== "string") {
282
+ return "unknown";
283
+ }
284
+ const tag = element.tagName.toLowerCase();
285
+ const id = element.id ? `#${element.id}` : "";
286
+ const classes = typeof element.className === "string" && element.className.trim() ? `.${element.className.trim().split(/\s+/).slice(0, 3).join(".")}` : "";
287
+ return `${tag}${id}${classes}`;
288
+ }
289
+ var _config, _sender, _breadcrumbs, _user, _restore, _fetch, _Orbitrace_static, autoCaptureOptions_fn, installAutoCapture_fn, capture_fn;
290
+ var _Orbitrace = class _Orbitrace {
291
+ // Automatic capture is on unless autoCapture is false or parts of it are switched off.
292
+ static init(config) {
293
+ this.assertBrowserRuntime();
294
+ this.validateConfig(config);
295
+ this.close();
296
+ __privateSet(this, _config, {
297
+ orgId: config.orgId,
298
+ projectId: config.projectId,
299
+ endpoint: config.endpoint,
300
+ environment: config.environment || "production",
301
+ version: config.version || "1.0.0",
302
+ service: config.service || "browser",
303
+ disabled: config.disabled || false
304
+ });
305
+ __privateSet(this, _sender, new Sender({
306
+ endpoint: config.endpoint,
307
+ headers: {
308
+ "x-orbitrace-api-key": config.publicKey,
309
+ "x-orbitrace-org-id": config.orgId,
310
+ "x-orbitrace-project-id": config.projectId
311
+ },
312
+ // Orbitrace's own requests use the original fetch, so they are never recorded.
313
+ getFetch: () => __privateGet(this, _fetch) || window.fetch.bind(window),
314
+ requestOptions: (body) => ({ keepalive: body.length <= MAX_KEEPALIVE_BODY_BYTES })
315
+ }));
316
+ const autoCapture = __privateMethod(this, _Orbitrace_static, autoCaptureOptions_fn).call(this, config.autoCapture);
317
+ if (autoCapture && !__privateGet(this, _config).disabled) {
318
+ __privateMethod(this, _Orbitrace_static, installAutoCapture_fn).call(this, autoCapture);
319
+ }
320
+ }
321
+ static validateConfig(config) {
322
+ if (config.apiKey) {
323
+ throw new Error(
324
+ "Orbitrace: apiKey is a server secret and must not ship in a browser bundle; use the project's publicKey"
325
+ );
326
+ }
327
+ const requiredFields = ["publicKey", "orgId", "projectId", "endpoint"];
328
+ const missingFields = requiredFields.filter((field) => !config[field]);
329
+ if (missingFields.length > 0) {
330
+ throw new Error(`Missing required configuration fields: ${missingFields.join(", ")}`);
331
+ }
332
+ }
333
+ static assertBrowserRuntime() {
334
+ if (typeof window === "undefined" || typeof document === "undefined") {
335
+ throw new Error("Orbitrace: @orbitai/orbitrace/browser runs in browsers; use @orbitai/orbitrace on servers");
336
+ }
337
+ }
338
+ // Queues the event and returns at once; it is sent in the background. flush() waits for delivery.
339
+ static captureException(error, metadata = {}) {
340
+ const message = error?.message || error?.name || String(error);
341
+ __privateMethod(this, _Orbitrace_static, capture_fn).call(this, "capture_exception", {
342
+ message,
343
+ stack: error?.stack || message,
344
+ metadata
345
+ });
346
+ }
347
+ static captureMessage(message, metadata = {}) {
348
+ __privateMethod(this, _Orbitrace_static, capture_fn).call(this, "capture_message", { message, metadata });
349
+ }
350
+ // Records something that happened, sent with the next event.
351
+ static addBreadcrumb(breadcrumb) {
352
+ pushBreadcrumb(__privateGet(this, _breadcrumbs), breadcrumb);
353
+ }
354
+ // Identifies the signed-in user on every following event. Send a stable id, not personal data.
355
+ // Pass null when the user signs out.
356
+ static setUser(user) {
357
+ __privateSet(this, _user, user ? filterSensitive(user) : null);
358
+ }
359
+ // Sends counts still held by the dedupe window and waits for in-flight requests.
360
+ static async flush() {
361
+ await __privateGet(this, _sender)?.flush();
362
+ }
363
+ // Removes the listeners and wrappers automatic capture installed and forgets breadcrumbs and the user.
364
+ static close() {
365
+ for (const undo of __privateGet(this, _restore).splice(0).reverse()) {
366
+ try {
367
+ undo();
368
+ } catch {
369
+ }
370
+ }
371
+ __privateSet(this, _fetch, null);
372
+ __privateSet(this, _breadcrumbs, []);
373
+ __privateSet(this, _user, null);
374
+ }
375
+ // Vue 3 plugin: app.use(Orbitrace.vue). Errors Vue catches in components are captured with the component
376
+ // name and where Vue caught them, then passed to the handler the app had, or logged as Vue would.
377
+ static get vue() {
378
+ return {
379
+ install(app) {
380
+ const previous = app.config.errorHandler;
381
+ app.config.errorHandler = (error, instance, info) => {
382
+ _Orbitrace.captureException(error, {
383
+ mechanism: "vue",
384
+ component: instance?.$options?.name || instance?.$?.type?.__name || "anonymous",
385
+ info
386
+ });
387
+ if (previous) {
388
+ previous(error, instance, info);
389
+ } else {
390
+ console.error(error);
391
+ }
392
+ };
393
+ }
394
+ };
395
+ }
396
+ };
397
+ _config = new WeakMap();
398
+ _sender = new WeakMap();
399
+ _breadcrumbs = new WeakMap();
400
+ _user = new WeakMap();
401
+ _restore = new WeakMap();
402
+ _fetch = new WeakMap();
403
+ _Orbitrace_static = new WeakSet();
404
+ autoCaptureOptions_fn = function(value) {
405
+ if (value === false) {
406
+ return null;
407
+ }
408
+ const options = value === true || value === void 0 ? {} : value;
409
+ return {
410
+ exceptions: options.exceptions !== false,
411
+ console: options.console !== false,
412
+ fetch: options.fetch !== false,
413
+ clicks: options.clicks !== false,
414
+ navigation: options.navigation !== false
415
+ };
416
+ };
417
+ installAutoCapture_fn = function(options) {
418
+ const addBreadcrumb = (breadcrumb) => this.addBreadcrumb(breadcrumb);
419
+ const listen = (target, type, listener, capture = false) => {
420
+ target.addEventListener(type, listener, capture);
421
+ __privateGet(this, _restore).push(() => target.removeEventListener(type, listener, capture));
422
+ };
423
+ if (options.exceptions) {
424
+ listen(window, "error", (event) => {
425
+ const error = event.error ?? new Error(event.message || "Unknown error");
426
+ this.captureException(error, {
427
+ mechanism: "onerror",
428
+ ...event.filename && { source: `${cleanUrl(event.filename)}:${event.lineno}:${event.colno}` }
429
+ });
430
+ });
431
+ listen(window, "unhandledrejection", (event) => {
432
+ const reason = event.reason;
433
+ const error = reason instanceof Error ? reason : new Error(`Unhandled rejection: ${formatConsoleArgs([reason])}`);
434
+ this.captureException(error, { mechanism: "unhandledrejection" });
435
+ });
436
+ }
437
+ if (options.console) {
438
+ recordConsole({ addBreadcrumb, restore: __privateGet(this, _restore), format: formatConsoleArgs });
439
+ }
440
+ if (options.fetch) {
441
+ __privateSet(this, _fetch, recordFetch({ endpoint: __privateGet(this, _config).endpoint, addBreadcrumb, restore: __privateGet(this, _restore) }));
442
+ }
443
+ if (options.clicks) {
444
+ listen(
445
+ document,
446
+ "click",
447
+ (event) => addBreadcrumb({ category: "ui.click", message: describeElement(event.target) }),
448
+ true
449
+ );
450
+ }
451
+ if (options.navigation) {
452
+ let from = cleanUrl(window.location.href);
453
+ const navigated = () => {
454
+ const to = cleanUrl(window.location.href);
455
+ if (to !== from) {
456
+ addBreadcrumb({ category: "navigation", message: `${from} -> ${to}`, data: { from, to } });
457
+ from = to;
458
+ }
459
+ };
460
+ for (const method of ["pushState", "replaceState"]) {
461
+ const original = window.history[method];
462
+ if (typeof original !== "function") {
463
+ continue;
464
+ }
465
+ window.history[method] = function orbitraceHistory(...args) {
466
+ const result = original.apply(this, args);
467
+ navigated();
468
+ return result;
469
+ };
470
+ __privateGet(this, _restore).push(() => {
471
+ window.history[method] = original;
472
+ });
473
+ }
474
+ listen(window, "popstate", navigated);
475
+ listen(window, "hashchange", navigated);
476
+ }
477
+ listen(window, "pagehide", () => __privateGet(this, _sender).flush());
478
+ };
479
+ capture_fn = function(event, { metadata, ...fields }) {
480
+ if (!__privateGet(this, _config)) {
481
+ console.error("Orbitrace: call Orbitrace.init() before capturing events");
482
+ return;
483
+ }
484
+ if (__privateGet(this, _config).disabled) {
485
+ return;
486
+ }
487
+ try {
488
+ const breadcrumbs = [...__privateGet(this, _breadcrumbs)];
489
+ __privateGet(this, _sender).capture(event, {
490
+ ...fields,
491
+ metadata: {
492
+ ...filterSensitive(metadata),
493
+ env: __privateGet(this, _config).environment,
494
+ version: __privateGet(this, _config).version,
495
+ service: __privateGet(this, _config).service,
496
+ ...__privateGet(this, _user) && { user: __privateGet(this, _user) },
497
+ // Where it happened. URLs lose their query string and fragment.
498
+ page: {
499
+ url: cleanUrl(window.location.href),
500
+ ...document.referrer && { referrer: cleanUrl(document.referrer) },
501
+ viewport: `${window.innerWidth}x${window.innerHeight}`,
502
+ language: window.navigator?.language
503
+ }
504
+ },
505
+ ...breadcrumbs.length > 0 && { breadcrumbs }
506
+ });
507
+ } catch (error) {
508
+ console.error("Orbitrace: could not capture the event", error?.message);
509
+ }
510
+ };
511
+ __privateAdd(_Orbitrace, _Orbitrace_static);
512
+ __privateAdd(_Orbitrace, _config, null);
513
+ __privateAdd(_Orbitrace, _sender, null);
514
+ __privateAdd(_Orbitrace, _breadcrumbs, []);
515
+ __privateAdd(_Orbitrace, _user, null);
516
+ // Undo functions for everything automatic capture installed; close() runs them.
517
+ __privateAdd(_Orbitrace, _restore, []);
518
+ __privateAdd(_Orbitrace, _fetch, null);
519
+ var Orbitrace = _Orbitrace;
520
+ module.exports = Orbitrace;