@edraj/sauron-browser 1.0.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/dist/index.js ADDED
@@ -0,0 +1,1922 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/utils.ts
6
+ var SDK_NAME = "sauron.javascript";
7
+ var SDK_VERSION = "1.0.0";
8
+ function getGlobal() {
9
+ return globalThis;
10
+ }
11
+ function getCrypto() {
12
+ const g2 = getGlobal();
13
+ return g2.crypto;
14
+ }
15
+ function uuidv4() {
16
+ const c = getCrypto();
17
+ if (c && typeof c.randomUUID === "function") {
18
+ return c.randomUUID();
19
+ }
20
+ const bytes = new Uint8Array(16);
21
+ if (c && typeof c.getRandomValues === "function") {
22
+ c.getRandomValues(bytes);
23
+ } else {
24
+ for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);
25
+ }
26
+ bytes[6] = bytes[6] & 15 | 64;
27
+ bytes[8] = bytes[8] & 63 | 128;
28
+ const hex = [];
29
+ for (let i = 0; i < 16; i++) hex.push(bytes[i].toString(16).padStart(2, "0"));
30
+ const s = hex.join("");
31
+ return `${s.slice(0, 8)}-${s.slice(8, 12)}-${s.slice(12, 16)}-${s.slice(16, 20)}-${s.slice(20)}`;
32
+ }
33
+ function nowIso() {
34
+ return (/* @__PURE__ */ new Date()).toISOString();
35
+ }
36
+ function safeStringify(value) {
37
+ const seen = /* @__PURE__ */ new WeakSet();
38
+ try {
39
+ return JSON.stringify(value, (_key, val) => {
40
+ if (typeof val === "bigint") return val.toString();
41
+ if (typeof val === "function") return void 0;
42
+ if (typeof val === "object" && val !== null) {
43
+ if (seen.has(val)) return "[Circular]";
44
+ seen.add(val);
45
+ }
46
+ return val;
47
+ });
48
+ } catch {
49
+ return "{}";
50
+ }
51
+ }
52
+ function byteLength(s) {
53
+ if (typeof TextEncoder !== "undefined") {
54
+ return new TextEncoder().encode(s).length;
55
+ }
56
+ let len = 0;
57
+ for (let i = 0; i < s.length; i++) {
58
+ const c = s.charCodeAt(i);
59
+ if (c < 128) len += 1;
60
+ else if (c < 2048) len += 2;
61
+ else if (c >= 55296 && c <= 56319) {
62
+ len += 4;
63
+ i++;
64
+ } else len += 3;
65
+ }
66
+ return len;
67
+ }
68
+ function computeBackoff(attempt, baseMs = 1e3, capMs = 3e4) {
69
+ const ceiling = Math.min(capMs, baseMs * Math.pow(2, Math.max(0, attempt)));
70
+ return Math.round(Math.random() * ceiling);
71
+ }
72
+ function clamp(value, min, max) {
73
+ return Math.min(max, Math.max(min, value));
74
+ }
75
+ function makeLogger(debug) {
76
+ const noop = () => {
77
+ };
78
+ if (!debug || typeof console === "undefined") {
79
+ return { log: noop, warn: noop };
80
+ }
81
+ return {
82
+ log: (...args) => console.log("[sauron]", ...args),
83
+ warn: (...args) => console.warn("[sauron]", ...args)
84
+ };
85
+ }
86
+
87
+ // src/identity.ts
88
+ var DEVICE_ID_KEY = "sauron.device_id";
89
+ var SESSION_ID_KEY = "sauron.session_id";
90
+ function webStorage(name) {
91
+ try {
92
+ const s = globalThis[name];
93
+ if (!s) return null;
94
+ const probe = "__sauron_probe__";
95
+ s.setItem(probe, "1");
96
+ s.removeItem(probe);
97
+ return s;
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+ function persistentId(cached, storage, key) {
103
+ if (cached) return cached;
104
+ if (storage) {
105
+ try {
106
+ const existing = storage.getItem(key);
107
+ if (existing) return existing;
108
+ } catch {
109
+ }
110
+ }
111
+ const fresh = uuidv4();
112
+ if (storage) {
113
+ try {
114
+ storage.setItem(key, fresh);
115
+ } catch {
116
+ }
117
+ }
118
+ return fresh;
119
+ }
120
+ var deviceId = null;
121
+ var sessionId = null;
122
+ function getDeviceId() {
123
+ deviceId = persistentId(deviceId, webStorage("localStorage"), DEVICE_ID_KEY);
124
+ return deviceId;
125
+ }
126
+ function getSessionId() {
127
+ sessionId = persistentId(sessionId, webStorage("sessionStorage"), SESSION_ID_KEY);
128
+ return sessionId;
129
+ }
130
+
131
+ // src/context.ts
132
+ function getNavigator() {
133
+ const g2 = globalThis;
134
+ return g2.navigator;
135
+ }
136
+ function detectOs(ua) {
137
+ let m = ua.match(/Windows NT ([\d.]+)/);
138
+ if (m) return { name: "Windows", version: m[1] };
139
+ m = ua.match(/iPhone OS (\d+[_\d]*)/) || ua.match(/CPU OS (\d+[_\d]*) like Mac/);
140
+ if (m) return { name: "iOS", version: m[1].replace(/_/g, ".") };
141
+ m = ua.match(/Mac OS X (\d+[_\d]*)/);
142
+ if (m) return { name: "macOS", version: m[1].replace(/_/g, ".") };
143
+ m = ua.match(/Android ([\d.]+)/);
144
+ if (m) return { name: "Android", version: m[1] };
145
+ if (/Linux/.test(ua)) return { name: "Linux", version: null };
146
+ if (/CrOS/.test(ua)) return { name: "Chrome OS", version: null };
147
+ return { name: null, version: null };
148
+ }
149
+ function detectRuntime(ua) {
150
+ let m = ua.match(/Edg(?:e|A|iOS)?\/([\d.]+)/);
151
+ if (m) return { name: "Edge", version: major(m[1]) };
152
+ m = ua.match(/OPR\/([\d.]+)/) || ua.match(/Opera\/([\d.]+)/);
153
+ if (m) return { name: "Opera", version: major(m[1]) };
154
+ m = ua.match(/Firefox\/([\d.]+)/);
155
+ if (m) return { name: "Firefox", version: major(m[1]) };
156
+ m = ua.match(/Chrome\/([\d.]+)/);
157
+ if (m) return { name: "Chrome", version: major(m[1]) };
158
+ m = ua.match(/Version\/([\d.]+).*Safari/);
159
+ if (m) return { name: "Safari", version: major(m[1]) };
160
+ if (/Safari/.test(ua)) return { name: "Safari", version: null };
161
+ return { name: null, version: null };
162
+ }
163
+ function detectDevice(nav, ua) {
164
+ const platform = nav?.userAgentData?.platform ?? nav?.platform ?? "";
165
+ let family = null;
166
+ if (/Mac|iPhone|iPad|iPod/.test(platform) || /iPhone|iPad|Macintosh/.test(ua)) {
167
+ family = "Apple";
168
+ } else if (/Win/.test(platform) || /Windows/.test(ua)) {
169
+ family = "Microsoft";
170
+ } else if (/Android/.test(ua)) {
171
+ family = "Google";
172
+ } else if (/Linux/.test(platform) || /Linux/.test(ua)) {
173
+ family = "Linux";
174
+ }
175
+ return { device_id: getDeviceId(), family, model: null, arch: null };
176
+ }
177
+ function major(version) {
178
+ const dot = version.indexOf(".");
179
+ return dot === -1 ? version : version.slice(0, dot);
180
+ }
181
+ function appFromRelease(release) {
182
+ if (!release) return { version: null, build: null };
183
+ const at = release.lastIndexOf("@");
184
+ const version = at === -1 ? release : release.slice(at + 1);
185
+ return { version: version || null, build: null };
186
+ }
187
+ function detectContext(release) {
188
+ const nav = getNavigator();
189
+ const ua = nav?.userAgent ?? "";
190
+ return {
191
+ device: detectDevice(nav, ua),
192
+ os: detectOs(ua),
193
+ app: appFromRelease(release),
194
+ runtime: detectRuntime(ua)
195
+ };
196
+ }
197
+ function buildContext(release, user) {
198
+ return { ...detectContext(release), user };
199
+ }
200
+
201
+ // src/dsn.ts
202
+ var DsnError = class extends Error {
203
+ constructor(message) {
204
+ super(`[sauron] invalid DSN: ${message}`);
205
+ this.name = "DsnError";
206
+ }
207
+ };
208
+ function parseDsn(dsn) {
209
+ if (typeof dsn !== "string" || dsn.length === 0) {
210
+ throw new DsnError("DSN must be a non-empty string");
211
+ }
212
+ let url;
213
+ try {
214
+ url = new URL(dsn);
215
+ } catch {
216
+ throw new DsnError(`could not parse "${dsn}"`);
217
+ }
218
+ const protocol = url.protocol.replace(/:$/, "");
219
+ if (protocol !== "http" && protocol !== "https") {
220
+ throw new DsnError(`unsupported protocol "${protocol}"`);
221
+ }
222
+ const publicKey = url.username;
223
+ if (!publicKey) {
224
+ throw new DsnError('missing public key (the "user" part of the URL)');
225
+ }
226
+ if (url.password) {
227
+ throw new DsnError("DSN must not contain a secret (password component)");
228
+ }
229
+ const host = url.host;
230
+ const hostname = url.hostname;
231
+ if (!host) {
232
+ throw new DsnError("missing host");
233
+ }
234
+ const projectId = url.pathname.replace(/^\/+/, "").replace(/\/+$/, "");
235
+ if (!projectId) {
236
+ throw new DsnError("missing project id (the path segment)");
237
+ }
238
+ const base = `${protocol}://${host}/api/${projectId}/envelope`;
239
+ const beaconUrl = `${base}?k=${encodeURIComponent(publicKey)}`;
240
+ return {
241
+ raw: dsn,
242
+ publicKey,
243
+ host,
244
+ hostname,
245
+ protocol,
246
+ projectId,
247
+ envelopeUrl: base,
248
+ beaconUrl
249
+ };
250
+ }
251
+
252
+ // src/envelope.ts
253
+ function buildEnvelope(header, context, items) {
254
+ return {
255
+ header,
256
+ context,
257
+ items
258
+ };
259
+ }
260
+
261
+ // src/integrations/instrument.ts
262
+ var WRAPPED = "__sauron_wrapped__";
263
+ var internalDepth = 0;
264
+ function beginInternal() {
265
+ internalDepth++;
266
+ }
267
+ function endInternal() {
268
+ internalDepth = Math.max(0, internalDepth - 1);
269
+ }
270
+ function withInternal(fn) {
271
+ beginInternal();
272
+ try {
273
+ return fn();
274
+ } finally {
275
+ endInternal();
276
+ }
277
+ }
278
+ function isInternal() {
279
+ return internalDepth > 0;
280
+ }
281
+ var dsnHost = null;
282
+ function setDsnHost(host) {
283
+ dsnHost = host;
284
+ }
285
+ function isDsnRequest(rawUrl) {
286
+ if (!dsnHost || !rawUrl) return false;
287
+ try {
288
+ const base = globalThis.location?.href;
289
+ const u = new URL(rawUrl, base);
290
+ return u.host === dsnHost;
291
+ } catch {
292
+ return false;
293
+ }
294
+ }
295
+ function markWrapped(fn) {
296
+ try {
297
+ fn[WRAPPED] = true;
298
+ } catch {
299
+ }
300
+ return fn;
301
+ }
302
+ function isWrapped(fn) {
303
+ if (fn === null || typeof fn !== "function" && typeof fn !== "object") return false;
304
+ return fn[WRAPPED] === true;
305
+ }
306
+ var patches = [];
307
+ function registerPatch(name, unpatch) {
308
+ patches.push({ name, unpatch });
309
+ }
310
+ function unpatchAll() {
311
+ while (patches.length) {
312
+ const p = patches.pop();
313
+ try {
314
+ p?.unpatch();
315
+ } catch {
316
+ }
317
+ }
318
+ }
319
+
320
+ // src/integrations/console.ts
321
+ var METHODS = ["log", "info", "warn", "error", "debug"];
322
+ function toLevel(method) {
323
+ switch (method) {
324
+ case "warn":
325
+ return "warning";
326
+ case "error":
327
+ return "error";
328
+ case "debug":
329
+ return "debug";
330
+ default:
331
+ return "info";
332
+ }
333
+ }
334
+ function argToString(arg) {
335
+ if (typeof arg === "string") return arg;
336
+ if (arg instanceof Error) return `${arg.name}: ${arg.message}`;
337
+ try {
338
+ return JSON.stringify(arg);
339
+ } catch {
340
+ return String(arg);
341
+ }
342
+ }
343
+ function installConsole() {
344
+ const consoleObj = globalThis.console;
345
+ if (!consoleObj) return;
346
+ const c = consoleObj;
347
+ for (const method of METHODS) {
348
+ const original = c[method];
349
+ if (typeof original !== "function" || isWrapped(original)) continue;
350
+ const originalFn = original;
351
+ const wrapped = markWrapped(function sauronConsole(...args) {
352
+ if (!isInternal()) {
353
+ withInternal(() => {
354
+ try {
355
+ addBreadcrumb({
356
+ type: "default",
357
+ category: "console",
358
+ level: toLevel(method),
359
+ message: args.map(argToString).join(" ").slice(0, 512),
360
+ data: { arguments: args.length }
361
+ });
362
+ } catch {
363
+ }
364
+ });
365
+ }
366
+ return originalFn.apply(this, args);
367
+ });
368
+ c[method] = wrapped;
369
+ registerPatch(`console.${method}`, () => {
370
+ c[method] = originalFn;
371
+ });
372
+ }
373
+ }
374
+
375
+ // src/integrations/dom.ts
376
+ function domSelector(el) {
377
+ try {
378
+ const node = el;
379
+ if (!node || typeof node.tagName !== "string") return null;
380
+ let selector = node.tagName.toLowerCase();
381
+ if (node.id && typeof node.id === "string") {
382
+ selector += `#${node.id}`;
383
+ }
384
+ if (typeof node.className === "string" && node.className.trim()) {
385
+ const classes = node.className.trim().split(/\s+/).slice(0, 3);
386
+ if (classes.length) selector += `.${classes.join(".")}`;
387
+ }
388
+ return selector;
389
+ } catch {
390
+ return null;
391
+ }
392
+ }
393
+ function installDom() {
394
+ const doc = globalThis.document;
395
+ if (!doc || typeof doc.addEventListener !== "function") return;
396
+ const handler = (event) => {
397
+ if (isInternal()) return;
398
+ const selector = domSelector(event.target);
399
+ if (!selector) return;
400
+ withInternal(() => {
401
+ addBreadcrumb({
402
+ type: "default",
403
+ category: "ui.click",
404
+ level: "info",
405
+ message: selector,
406
+ data: null
407
+ });
408
+ });
409
+ };
410
+ const options = { capture: true, passive: true };
411
+ doc.addEventListener("click", handler, options);
412
+ registerPatch("dom.click", () => {
413
+ try {
414
+ doc.removeEventListener("click", handler, options);
415
+ } catch {
416
+ }
417
+ });
418
+ }
419
+
420
+ // src/integrations/fetch.ts
421
+ function resolveUrl(input) {
422
+ if (typeof input === "string") return input;
423
+ if (input && typeof input === "object") {
424
+ const withUrl = input;
425
+ if (typeof withUrl.url === "string") return withUrl.url;
426
+ if (typeof withUrl.href === "string") return withUrl.href;
427
+ }
428
+ return String(input);
429
+ }
430
+ function resolveMethod(input, init3) {
431
+ const fromInit = init3?.method;
432
+ const fromReq = input?.method;
433
+ return String(fromInit ?? fromReq ?? "GET").toUpperCase();
434
+ }
435
+ function addFetchBreadcrumb(method, url, status, startedAt) {
436
+ withInternal(() => {
437
+ addBreadcrumb({
438
+ type: "default",
439
+ category: "fetch",
440
+ level: status !== null && status >= 400 ? "warning" : "info",
441
+ message: `${method} ${url}`,
442
+ timestamp: startedAt,
443
+ data: { method, url, status_code: status }
444
+ });
445
+ });
446
+ }
447
+ function installFetch() {
448
+ const g2 = globalThis;
449
+ const original = g2.fetch;
450
+ if (typeof original !== "function" || isWrapped(original)) return;
451
+ const wrapped = markWrapped(function sauronFetch(input, init3) {
452
+ const url = resolveUrl(input);
453
+ const shouldRecord = !isInternal() && !isDsnRequest(url);
454
+ const method = resolveMethod(input, init3);
455
+ const startedAt = nowIso();
456
+ const promise = original.call(this, input, init3);
457
+ if (shouldRecord) {
458
+ promise.then(
459
+ (res) => addFetchBreadcrumb(method, url, res?.status ?? null, startedAt),
460
+ () => addFetchBreadcrumb(method, url, null, startedAt)
461
+ );
462
+ }
463
+ return promise;
464
+ });
465
+ g2.fetch = wrapped;
466
+ registerPatch("fetch", () => {
467
+ g2.fetch = original;
468
+ });
469
+ }
470
+
471
+ // src/screen.ts
472
+ var currentScreen = null;
473
+ function getScreen() {
474
+ return currentScreen;
475
+ }
476
+ function setScreenState(name) {
477
+ if (name === currentScreen) return false;
478
+ currentScreen = name;
479
+ return true;
480
+ }
481
+ function resetScreen() {
482
+ currentScreen = null;
483
+ }
484
+
485
+ // src/stacktrace/parse.ts
486
+ var MAX_FRAMES = 50;
487
+ var V8_RE = /^\s*at (?:(.+?) )?\(?((?:[a-z][\w.+-]*:\/\/)?[^\s()]+?):(\d+):(\d+)\)?\s*$/i;
488
+ var GECKO_RE = /^\s*(?:([^@]*?)@)?((?:[a-z][\w.+-]*:\/\/)?[^@\s]+?):(\d+):(\d+)\s*$/i;
489
+ function cleanFunction(fn) {
490
+ if (!fn) return null;
491
+ let name = fn.trim();
492
+ name = name.replace(/^async\s+/, "").replace(/^new\s+/, "new ");
493
+ name = name.replace(/\s+\[as .+\]$/, "");
494
+ if (name === "<anonymous>" || name === "") return null;
495
+ return name;
496
+ }
497
+ function isInAppFrame(filename) {
498
+ if (!filename) return false;
499
+ if (filename === "<anonymous>" || filename.startsWith("node:") || filename.startsWith("internal/")) {
500
+ return false;
501
+ }
502
+ const hasProtocol = /^[a-z][\w.+-]*:\/\//i.test(filename);
503
+ if (!hasProtocol) {
504
+ return true;
505
+ }
506
+ const origin = getOrigin();
507
+ if (origin && filename.startsWith(origin)) {
508
+ return true;
509
+ }
510
+ return false;
511
+ }
512
+ function getOrigin() {
513
+ const loc = globalThis.location;
514
+ return loc?.origin ?? "";
515
+ }
516
+ function parseLine(line) {
517
+ let m = V8_RE.exec(line);
518
+ if (!m) m = GECKO_RE.exec(line);
519
+ if (!m) return null;
520
+ const filename = m[2] || null;
521
+ const lineno = m[3] ? parseInt(m[3], 10) : null;
522
+ const colno = m[4] ? parseInt(m[4], 10) : null;
523
+ return {
524
+ function: cleanFunction(m[1]),
525
+ filename,
526
+ lineno: Number.isNaN(lineno) ? null : lineno,
527
+ colno: Number.isNaN(colno) ? null : colno,
528
+ in_app: isInAppFrame(filename)
529
+ };
530
+ }
531
+ function parseStackString(stack) {
532
+ if (!stack) return [];
533
+ const frames = [];
534
+ const lines = stack.split("\n");
535
+ for (const raw of lines) {
536
+ const line = raw.replace(/\r$/, "");
537
+ if (!line.trim()) continue;
538
+ const frame = parseLine(line);
539
+ if (frame) {
540
+ frames.push(frame);
541
+ if (frames.length >= MAX_FRAMES) break;
542
+ }
543
+ }
544
+ frames.reverse();
545
+ return frames;
546
+ }
547
+ function parseError(err) {
548
+ if (err && typeof err === "object" && "stack" in err) {
549
+ const stack = err.stack;
550
+ if (typeof stack === "string") return parseStackString(stack);
551
+ }
552
+ return [];
553
+ }
554
+
555
+ // src/api/capture.ts
556
+ var DEFAULT_MECHANISM = { type: "generic", handled: true };
557
+ function attachCallMeta(item, hint) {
558
+ if (hint?.tags && Object.keys(hint.tags).length > 0) item.tags = { ...hint.tags };
559
+ if (hint?.contexts && Object.keys(hint.contexts).length > 0) item.contexts = { ...hint.contexts };
560
+ if (hint?.extra && Object.keys(hint.extra).length > 0) item.extra = { ...hint.extra };
561
+ }
562
+ function isErrorLike(err) {
563
+ return err instanceof Error || typeof err === "object" && err !== null && "name" in err && "message" in err && typeof err.message === "string";
564
+ }
565
+ function extractError(err) {
566
+ if (isErrorLike(err)) {
567
+ const e = err;
568
+ return {
569
+ type: e.name || "Error",
570
+ value: e.message ?? "",
571
+ stacktrace: parseError(e)
572
+ };
573
+ }
574
+ if (typeof err === "string") {
575
+ return { type: "Error", value: err, stacktrace: [] };
576
+ }
577
+ if (typeof err === "object" && err !== null) {
578
+ const ctor = err.constructor;
579
+ return {
580
+ type: ctor?.name ?? "Object",
581
+ value: safeStringify(err),
582
+ stacktrace: []
583
+ };
584
+ }
585
+ return { type: typeof err, value: String(err), stacktrace: [] };
586
+ }
587
+ function buildErrorItem(err, breadcrumbs, hint) {
588
+ const extracted = extractError(err);
589
+ const mechanism = hint?.mechanism ?? DEFAULT_MECHANISM;
590
+ const level = hint?.level ?? "error";
591
+ const fingerprint = hint?.fingerprint ?? null;
592
+ const exception = {
593
+ type: extracted.type,
594
+ value: extracted.value,
595
+ mechanism,
596
+ stacktrace: extracted.stacktrace
597
+ };
598
+ const item = {
599
+ type: "error",
600
+ timestamp: nowIso(),
601
+ level,
602
+ exception,
603
+ breadcrumbs,
604
+ fingerprint,
605
+ session_id: getSessionId(),
606
+ screen: hint?.screen ?? getScreen()
607
+ };
608
+ attachCallMeta(item, hint);
609
+ return item;
610
+ }
611
+ function captureException(err, hint) {
612
+ const client = getClient();
613
+ if (!client) return;
614
+ const breadcrumbs = client.getScope().getBreadcrumbs();
615
+ const fullHint = { ...hint, originalException: err };
616
+ const item = buildErrorItem(err, breadcrumbs, fullHint);
617
+ client.captureItem(item, fullHint);
618
+ }
619
+ function captureMessage(message, level = "info", hint) {
620
+ const client = getClient();
621
+ if (!client) return;
622
+ const breadcrumbs = client.getScope().getBreadcrumbs();
623
+ const item = {
624
+ type: "error",
625
+ timestamp: nowIso(),
626
+ level,
627
+ exception: {
628
+ type: null,
629
+ value: message,
630
+ mechanism: { type: "message", handled: true },
631
+ stacktrace: []
632
+ },
633
+ breadcrumbs,
634
+ fingerprint: hint?.fingerprint ?? null,
635
+ session_id: getSessionId(),
636
+ screen: getScreen()
637
+ };
638
+ attachCallMeta(item, hint);
639
+ client.captureItem(item, hint);
640
+ }
641
+
642
+ // src/integrations/globalHandlers.ts
643
+ function installGlobalHandlers() {
644
+ const win = globalThis;
645
+ installOnError(win);
646
+ installOnUnhandledRejection(win);
647
+ }
648
+ function installOnError(win) {
649
+ const previous = win.onerror;
650
+ if (isWrapped(previous)) return;
651
+ const handler = markWrapped(function sauronOnError(message, source, lineno, colno, error) {
652
+ if (getClient()) {
653
+ const err = error ?? syntheticError(message, source, lineno, colno);
654
+ captureException(err, { mechanism: { type: "onerror", handled: false }, level: "error" });
655
+ }
656
+ if (typeof previous === "function") {
657
+ return Boolean(
658
+ previous.call(this, message, source, lineno, colno, error)
659
+ );
660
+ }
661
+ return false;
662
+ });
663
+ win.onerror = handler;
664
+ registerPatch("onerror", () => {
665
+ win.onerror = previous ?? null;
666
+ });
667
+ }
668
+ function installOnUnhandledRejection(win) {
669
+ const previous = win.onunhandledrejection;
670
+ if (isWrapped(previous)) return;
671
+ const handler = markWrapped(function sauronOnRejection(event) {
672
+ if (getClient()) {
673
+ const reason = event && typeof event === "object" && "reason" in event ? event.reason : event;
674
+ captureException(reason, {
675
+ mechanism: { type: "onunhandledrejection", handled: false },
676
+ level: "error"
677
+ });
678
+ }
679
+ if (typeof previous === "function") {
680
+ return previous.call(this, event);
681
+ }
682
+ return void 0;
683
+ });
684
+ win.onunhandledrejection = handler;
685
+ registerPatch("onunhandledrejection", () => {
686
+ win.onunhandledrejection = previous ?? null;
687
+ });
688
+ }
689
+ function syntheticError(message, source, lineno, colno) {
690
+ const msg = typeof message === "string" ? message : "Unknown error";
691
+ const err = new Error(msg);
692
+ if (source) {
693
+ err.stack = `Error: ${msg}
694
+ at ${source}:${lineno ?? 0}:${colno ?? 0}`;
695
+ }
696
+ return err;
697
+ }
698
+
699
+ // src/integrations/history.ts
700
+ var navHandler = null;
701
+ function onNavigation(cb) {
702
+ navHandler = cb;
703
+ }
704
+ function toPath(url, base) {
705
+ if (url === null || url === void 0) return url;
706
+ try {
707
+ const parsed = new URL(url, base);
708
+ return parsed.pathname + parsed.search + parsed.hash;
709
+ } catch {
710
+ return url;
711
+ }
712
+ }
713
+ function installHistory() {
714
+ const g2 = globalThis;
715
+ const hist = g2.history;
716
+ const loc = g2.location;
717
+ if (!hist) return;
718
+ let lastPath = toPath(loc?.href ?? null, loc?.href);
719
+ const emit = (toHref) => {
720
+ const to = toPath(toHref, loc?.href);
721
+ const from = lastPath;
722
+ lastPath = to;
723
+ if (from === to) return;
724
+ withInternal(() => addNavigationBreadcrumb(from, to));
725
+ if (to && navHandler) {
726
+ try {
727
+ navHandler(to);
728
+ } catch {
729
+ }
730
+ }
731
+ };
732
+ const wrap = (name) => {
733
+ const original = hist[name];
734
+ if (typeof original !== "function" || isWrapped(original)) return;
735
+ hist[name] = markWrapped(function sauronHistory(...args) {
736
+ const result = original.apply(this, args);
737
+ if (!isInternal()) {
738
+ const urlArg = args[2];
739
+ emit(urlArg != null ? String(urlArg) : loc?.href ?? null);
740
+ }
741
+ return result;
742
+ });
743
+ registerPatch(`history.${name}`, () => {
744
+ hist[name] = original;
745
+ });
746
+ };
747
+ wrap("pushState");
748
+ wrap("replaceState");
749
+ if (typeof g2.addEventListener === "function") {
750
+ const onPopState = () => {
751
+ if (!isInternal()) emit(loc?.href ?? null);
752
+ };
753
+ g2.addEventListener("popstate", onPopState);
754
+ registerPatch("popstate", () => {
755
+ g2.removeEventListener?.("popstate", onPopState);
756
+ });
757
+ }
758
+ }
759
+
760
+ // src/scope.ts
761
+ function emptyUser() {
762
+ return { id: null, email: null, traits: {} };
763
+ }
764
+ function mergeMeta(base, override) {
765
+ return override ? { ...base, ...override } : { ...base };
766
+ }
767
+ var Scope = class {
768
+ constructor(maxBreadcrumbs = 50) {
769
+ __publicField(this, "user", null);
770
+ __publicField(this, "breadcrumbs", []);
771
+ __publicField(this, "maxBreadcrumbs");
772
+ __publicField(this, "tags", {});
773
+ __publicField(this, "contexts", {});
774
+ __publicField(this, "extra", {});
775
+ this.maxBreadcrumbs = Math.max(0, maxBreadcrumbs);
776
+ }
777
+ setMaxBreadcrumbs(max) {
778
+ this.maxBreadcrumbs = Math.max(0, max);
779
+ this.trim();
780
+ }
781
+ setUser(user) {
782
+ if (user === null) {
783
+ this.user = null;
784
+ return;
785
+ }
786
+ this.user = {
787
+ id: user.id ?? null,
788
+ email: user.email ?? null,
789
+ traits: user.traits ?? {}
790
+ };
791
+ }
792
+ /** The user context for an envelope. Never null — defaults to an empty user. */
793
+ getUser() {
794
+ return this.user ? { ...this.user, traits: { ...this.user.traits } } : emptyUser();
795
+ }
796
+ /** True when an identifiable user has been set. */
797
+ hasUser() {
798
+ return this.user !== null;
799
+ }
800
+ setTag(key, value) {
801
+ this.tags[key] = value;
802
+ }
803
+ /** Merge a batch of tags into the scope (last-write-wins per key). */
804
+ setTags(tags) {
805
+ Object.assign(this.tags, tags);
806
+ }
807
+ /** Set (replace) a named context block on the scope. */
808
+ setContext(name, block) {
809
+ this.contexts[name] = block;
810
+ }
811
+ /** Set a single freeform extra value on the scope. */
812
+ setExtra(key, value) {
813
+ this.extra[key] = value;
814
+ }
815
+ addBreadcrumb(breadcrumb) {
816
+ if (this.maxBreadcrumbs <= 0) return;
817
+ this.breadcrumbs.push(breadcrumb);
818
+ this.trim();
819
+ }
820
+ /** A defensive copy of the current breadcrumb trail. */
821
+ getBreadcrumbs() {
822
+ return this.breadcrumbs.slice();
823
+ }
824
+ clearBreadcrumbs() {
825
+ this.breadcrumbs = [];
826
+ }
827
+ trim() {
828
+ const overflow = this.breadcrumbs.length - this.maxBreadcrumbs;
829
+ if (overflow > 0) {
830
+ this.breadcrumbs.splice(0, overflow);
831
+ }
832
+ }
833
+ };
834
+
835
+ // src/api/product.ts
836
+ function track(name, properties = {}, options = {}) {
837
+ const client = getClient();
838
+ if (!client) return;
839
+ const scope = client.getScope();
840
+ const item = {
841
+ type: "event",
842
+ name,
843
+ distinct_id: client.getDistinctId(),
844
+ session_id: getSessionId(),
845
+ screen: options.screen ?? getScreen(),
846
+ timestamp: nowIso(),
847
+ properties: properties ?? {}
848
+ };
849
+ const tags = mergeMeta(scope.tags, options.tags);
850
+ if (Object.keys(tags).length > 0) item.tags = tags;
851
+ const contexts = mergeMeta(scope.contexts, options.contexts);
852
+ if (Object.keys(contexts).length > 0) item.contexts = contexts;
853
+ const extra = mergeMeta(scope.extra, options.extra);
854
+ if (Object.keys(extra).length > 0) item.extra = extra;
855
+ client.captureItem(item);
856
+ }
857
+ function setScreen(name) {
858
+ if (!setScreenState(name)) return;
859
+ track("$screen", { screen: name });
860
+ }
861
+ function identify(id, traits = {}) {
862
+ const client = getClient();
863
+ if (!client) return;
864
+ const anonymousId = client.getAnonymousId();
865
+ client.getScope().setUser({ id, traits });
866
+ const item = {
867
+ type: "identify",
868
+ distinct_id: id,
869
+ anonymous_id: anonymousId,
870
+ traits: traits ?? {}
871
+ };
872
+ client.captureItem(item);
873
+ }
874
+ var TRANSACTION_OPS = [
875
+ "navigation",
876
+ "http",
877
+ "resource",
878
+ "screen_load",
879
+ "custom"
880
+ ];
881
+ function normalizeOp(op) {
882
+ return op && TRANSACTION_OPS.includes(op) ? op : "custom";
883
+ }
884
+ function buildTransactionItem(input, distinctId, sessionId2) {
885
+ return {
886
+ type: "transaction",
887
+ name: input.name,
888
+ op: normalizeOp(input.op),
889
+ duration_ms: input.durationMs,
890
+ status: input.status ?? null,
891
+ http_method: input.httpMethod ?? null,
892
+ http_status: input.httpStatus ?? null,
893
+ url: input.url ?? null,
894
+ distinct_id: distinctId,
895
+ session_id: sessionId2,
896
+ timestamp: nowIso()
897
+ };
898
+ }
899
+ function trackTransaction(input) {
900
+ const client = getClient();
901
+ if (!client) return;
902
+ const item = buildTransactionItem(input, client.getDistinctId(), getSessionId());
903
+ client.captureItem(item);
904
+ }
905
+
906
+ // src/integrations/performance.ts
907
+ var PERF_FETCH = "__sauron_perf_fetch__";
908
+ var PERF_HISTORY = "__sauron_perf_history__";
909
+ function g() {
910
+ return globalThis;
911
+ }
912
+ function mark(fn, key) {
913
+ fn[key] = true;
914
+ }
915
+ function isMarked(fn, key) {
916
+ return typeof fn === "function" && fn[key] === true;
917
+ }
918
+ function clock() {
919
+ const perf = g().performance;
920
+ if (perf && typeof perf.now === "function") return () => perf.now();
921
+ return () => Date.now();
922
+ }
923
+ function resolveUrl2(input) {
924
+ if (typeof input === "string") return input;
925
+ if (input && typeof input === "object") {
926
+ const withUrl = input;
927
+ if (typeof withUrl.url === "string") return withUrl.url;
928
+ if (typeof withUrl.href === "string") return withUrl.href;
929
+ }
930
+ return String(input);
931
+ }
932
+ function resolveMethod2(input, init3) {
933
+ const fromInit = init3?.method;
934
+ const fromReq = input?.method;
935
+ return String(fromInit ?? fromReq ?? "GET").toUpperCase();
936
+ }
937
+ function pathOf(url) {
938
+ if (!url) return "/";
939
+ try {
940
+ const base = g().location?.href;
941
+ return new URL(url, base).pathname;
942
+ } catch {
943
+ return url;
944
+ }
945
+ }
946
+ function navigationDuration(perf) {
947
+ try {
948
+ const entries = perf.getEntriesByType?.("navigation");
949
+ if (entries && entries.length > 0) {
950
+ const d = entries[0].duration;
951
+ if (typeof d === "number" && d > 0) return d;
952
+ }
953
+ } catch {
954
+ }
955
+ const timing = perf.timing;
956
+ if (timing && timing.loadEventEnd && timing.navigationStart) {
957
+ const d = timing.loadEventEnd - timing.navigationStart;
958
+ if (d > 0) return d;
959
+ }
960
+ return null;
961
+ }
962
+ function installNavigationTiming() {
963
+ const perf = g().performance;
964
+ if (!perf) return;
965
+ const capture = () => {
966
+ try {
967
+ const durationMs = navigationDuration(perf);
968
+ if (durationMs === null) return;
969
+ trackTransaction({ name: pathOf(g().location?.pathname), op: "navigation", durationMs });
970
+ } catch {
971
+ }
972
+ };
973
+ const doc = g().document;
974
+ if (doc && doc.readyState === "complete") {
975
+ capture();
976
+ } else if (typeof g().addEventListener === "function") {
977
+ const onLoad = () => {
978
+ capture();
979
+ g().removeEventListener?.("load", onLoad);
980
+ };
981
+ g().addEventListener?.("load", onLoad);
982
+ registerPatch("perf.load", () => g().removeEventListener?.("load", onLoad));
983
+ } else {
984
+ capture();
985
+ }
986
+ }
987
+ function emitHttp(method, url, start, end, status, ok) {
988
+ try {
989
+ trackTransaction({
990
+ name: `${method} ${pathOf(url)}`,
991
+ op: "http",
992
+ durationMs: Math.max(0, end - start),
993
+ status: ok ? "ok" : "error",
994
+ httpMethod: method,
995
+ httpStatus: status ?? void 0,
996
+ url
997
+ });
998
+ } catch {
999
+ }
1000
+ }
1001
+ function installFetchTiming() {
1002
+ const original = g().fetch;
1003
+ if (typeof original !== "function") return;
1004
+ if (isMarked(original, PERF_FETCH)) return;
1005
+ const now = clock();
1006
+ const wrapped = function sauronPerfFetch(input, init3) {
1007
+ let url = "";
1008
+ let method = "GET";
1009
+ let record = false;
1010
+ try {
1011
+ url = resolveUrl2(input);
1012
+ method = resolveMethod2(input, init3);
1013
+ record = !isInternal() && !isDsnRequest(url);
1014
+ } catch {
1015
+ record = false;
1016
+ }
1017
+ const start = record ? now() : 0;
1018
+ const promise = original.call(this, input, init3);
1019
+ if (record) {
1020
+ promise.then(
1021
+ (res) => emitHttp(method, url, start, now(), res?.status ?? null, res?.ok ?? false),
1022
+ () => emitHttp(method, url, start, now(), null, false)
1023
+ );
1024
+ }
1025
+ return promise;
1026
+ };
1027
+ mark(wrapped, PERF_FETCH);
1028
+ g().fetch = wrapped;
1029
+ registerPatch("perf.fetch", () => {
1030
+ g().fetch = original;
1031
+ });
1032
+ }
1033
+ function installHistoryTiming() {
1034
+ const hist = g().history;
1035
+ if (!hist) return;
1036
+ const now = clock();
1037
+ const emit = () => {
1038
+ try {
1039
+ const path = pathOf(g().location?.pathname);
1040
+ const start = now();
1041
+ const finish = () => {
1042
+ try {
1043
+ trackTransaction({ name: path, op: "navigation", durationMs: Math.max(0, now() - start) });
1044
+ } catch {
1045
+ }
1046
+ };
1047
+ const raf = g().requestAnimationFrame;
1048
+ if (typeof raf === "function") raf(finish);
1049
+ else finish();
1050
+ } catch {
1051
+ }
1052
+ };
1053
+ const wrap = (name) => {
1054
+ const original = hist[name];
1055
+ if (typeof original !== "function") return;
1056
+ if (isMarked(original, PERF_HISTORY)) return;
1057
+ const patched = function sauronPerfHistory(...args) {
1058
+ const result = original.apply(this, args);
1059
+ if (!isInternal()) emit();
1060
+ return result;
1061
+ };
1062
+ mark(patched, PERF_HISTORY);
1063
+ hist[name] = patched;
1064
+ registerPatch(`perf.history.${name}`, () => {
1065
+ hist[name] = original;
1066
+ });
1067
+ };
1068
+ wrap("pushState");
1069
+ wrap("replaceState");
1070
+ if (typeof g().addEventListener === "function") {
1071
+ const onPopState = () => {
1072
+ if (!isInternal()) emit();
1073
+ };
1074
+ g().addEventListener?.("popstate", onPopState);
1075
+ registerPatch("perf.popstate", () => g().removeEventListener?.("popstate", onPopState));
1076
+ }
1077
+ }
1078
+ function installPerformance() {
1079
+ if (typeof g().document === "undefined") return;
1080
+ installNavigationTiming();
1081
+ installFetchTiming();
1082
+ installHistoryTiming();
1083
+ }
1084
+
1085
+ // src/integrations/xhr.ts
1086
+ function addXhrBreadcrumb(meta, status) {
1087
+ withInternal(() => {
1088
+ addBreadcrumb({
1089
+ type: "default",
1090
+ category: "xhr",
1091
+ level: status >= 400 ? "warning" : "info",
1092
+ message: `${meta.method} ${meta.url}`,
1093
+ timestamp: meta.startedAt,
1094
+ data: { method: meta.method, url: meta.url, status_code: status || null }
1095
+ });
1096
+ });
1097
+ }
1098
+ function installXhr() {
1099
+ const XHR = globalThis.XMLHttpRequest;
1100
+ if (typeof XHR !== "function" || !XHR.prototype) return;
1101
+ const proto = XHR.prototype;
1102
+ const originalOpen = proto.open;
1103
+ const originalSend = proto.send;
1104
+ if (isWrapped(originalOpen) || isWrapped(originalSend)) return;
1105
+ proto.open = markWrapped(function sauronXhrOpen(method, url, ...rest) {
1106
+ this.__sauron_xhr__ = {
1107
+ method: String(method ?? "GET").toUpperCase(),
1108
+ url: String(url),
1109
+ startedAt: nowIso()
1110
+ };
1111
+ return originalOpen.apply(this, [method, url, ...rest]);
1112
+ });
1113
+ proto.send = markWrapped(function sauronXhrSend(...args) {
1114
+ const meta = this.__sauron_xhr__;
1115
+ if (meta && !isInternal() && !isDsnRequest(meta.url)) {
1116
+ const onLoadEnd = () => {
1117
+ try {
1118
+ addXhrBreadcrumb(meta, this.status);
1119
+ } catch {
1120
+ } finally {
1121
+ try {
1122
+ this.removeEventListener("loadend", onLoadEnd);
1123
+ } catch {
1124
+ }
1125
+ }
1126
+ };
1127
+ try {
1128
+ this.addEventListener("loadend", onLoadEnd);
1129
+ } catch {
1130
+ }
1131
+ }
1132
+ return originalSend.apply(this, args);
1133
+ });
1134
+ registerPatch("xhr", () => {
1135
+ proto.open = originalOpen;
1136
+ proto.send = originalSend;
1137
+ });
1138
+ }
1139
+
1140
+ // src/transport/beacon.ts
1141
+ function installBeacon(transport) {
1142
+ const win = globalThis;
1143
+ const doc = globalThis.document;
1144
+ const flush2 = () => {
1145
+ try {
1146
+ transport.flushToBeacon();
1147
+ } catch {
1148
+ }
1149
+ };
1150
+ const onVisibility = () => {
1151
+ if (doc && doc.visibilityState === "hidden") flush2();
1152
+ };
1153
+ if (doc && typeof doc.addEventListener === "function") {
1154
+ doc.addEventListener("visibilitychange", onVisibility);
1155
+ }
1156
+ if (typeof win.addEventListener === "function") {
1157
+ win.addEventListener("pagehide", flush2);
1158
+ }
1159
+ return () => {
1160
+ if (doc && typeof doc.removeEventListener === "function") {
1161
+ doc.removeEventListener("visibilitychange", onVisibility);
1162
+ }
1163
+ if (typeof win.removeEventListener === "function") {
1164
+ win.removeEventListener("pagehide", flush2);
1165
+ }
1166
+ };
1167
+ }
1168
+
1169
+ // src/transport/compress.ts
1170
+ var MIN_COMPRESS_BYTES = 1024;
1171
+ function getCompressionStream() {
1172
+ const g2 = globalThis;
1173
+ return typeof g2.CompressionStream === "function" ? g2.CompressionStream : void 0;
1174
+ }
1175
+ async function gzipViaStream(bytes, CS) {
1176
+ const cs = new CS("gzip");
1177
+ const writer = cs.writable.getWriter();
1178
+ void writer.write(bytes);
1179
+ void writer.close();
1180
+ const reader = cs.readable.getReader();
1181
+ const chunks = [];
1182
+ let total = 0;
1183
+ for (; ; ) {
1184
+ const { done, value } = await reader.read();
1185
+ if (done) break;
1186
+ if (value) {
1187
+ chunks.push(value);
1188
+ total += value.length;
1189
+ }
1190
+ }
1191
+ const out = new Uint8Array(total);
1192
+ let offset = 0;
1193
+ for (const chunk of chunks) {
1194
+ out.set(chunk, offset);
1195
+ offset += chunk.length;
1196
+ }
1197
+ return out;
1198
+ }
1199
+ async function maybeCompress(json) {
1200
+ const raw = new TextEncoder().encode(json);
1201
+ if (raw.length < MIN_COMPRESS_BYTES) {
1202
+ return { body: json, encoding: null };
1203
+ }
1204
+ const CS = getCompressionStream();
1205
+ if (CS) {
1206
+ try {
1207
+ const gz = await gzipViaStream(raw, CS);
1208
+ return { body: gz, encoding: "gzip" };
1209
+ } catch {
1210
+ }
1211
+ }
1212
+ try {
1213
+ const { gzipSync } = await import('fflate');
1214
+ return { body: gzipSync(raw), encoding: "gzip" };
1215
+ } catch {
1216
+ return { body: json, encoding: null };
1217
+ }
1218
+ }
1219
+
1220
+ // src/transport/queue.ts
1221
+ var QUEUE_KEY = "sauron:queue:v1";
1222
+ function defaultStorage() {
1223
+ try {
1224
+ const ls = globalThis.localStorage;
1225
+ if (!ls) return null;
1226
+ const probe = "__sauron_probe__";
1227
+ ls.setItem(probe, "1");
1228
+ ls.removeItem(probe);
1229
+ return ls;
1230
+ } catch {
1231
+ return null;
1232
+ }
1233
+ }
1234
+ var OfflineQueue = class {
1235
+ constructor(maxBytes, storage, key = QUEUE_KEY) {
1236
+ __publicField(this, "maxBytes");
1237
+ __publicField(this, "storage");
1238
+ __publicField(this, "key");
1239
+ this.maxBytes = Math.max(0, maxBytes);
1240
+ this.storage = storage;
1241
+ this.key = key;
1242
+ }
1243
+ /** True when a real backing store is available. */
1244
+ get available() {
1245
+ return this.storage !== null;
1246
+ }
1247
+ read() {
1248
+ if (!this.storage) return [];
1249
+ try {
1250
+ const raw = this.storage.getItem(this.key);
1251
+ if (!raw) return [];
1252
+ const parsed = JSON.parse(raw);
1253
+ if (!Array.isArray(parsed)) return [];
1254
+ return parsed.filter((x) => typeof x === "string");
1255
+ } catch {
1256
+ return [];
1257
+ }
1258
+ }
1259
+ write(entries) {
1260
+ if (!this.storage) return;
1261
+ try {
1262
+ if (entries.length === 0) {
1263
+ this.storage.removeItem(this.key);
1264
+ } else {
1265
+ this.storage.setItem(this.key, JSON.stringify(entries));
1266
+ }
1267
+ } catch {
1268
+ }
1269
+ }
1270
+ /** Drop oldest entries until the total fits under `maxBytes`. Keeps >= 1. */
1271
+ evict(entries) {
1272
+ let total = 0;
1273
+ for (const e of entries) total += byteLength(e);
1274
+ while (entries.length > 1 && total > this.maxBytes) {
1275
+ const removed = entries.shift();
1276
+ if (removed === void 0) break;
1277
+ total -= byteLength(removed);
1278
+ }
1279
+ }
1280
+ /** Append a payload to the tail (newest), evicting from the head if needed. */
1281
+ enqueue(payload) {
1282
+ if (!this.storage) return;
1283
+ const entries = this.read();
1284
+ entries.push(payload);
1285
+ this.evict(entries);
1286
+ this.write(entries);
1287
+ }
1288
+ /** Remove and return every entry, oldest first. */
1289
+ drain() {
1290
+ const entries = this.read();
1291
+ if (entries.length) this.write([]);
1292
+ return entries;
1293
+ }
1294
+ /** Non-destructive read of the current entries, oldest first. */
1295
+ peek() {
1296
+ return this.read();
1297
+ }
1298
+ /** Number of queued entries. */
1299
+ size() {
1300
+ return this.read().length;
1301
+ }
1302
+ /** Total UTF-8 byte size of the queue. */
1303
+ byteSize() {
1304
+ let total = 0;
1305
+ for (const e of this.read()) total += byteLength(e);
1306
+ return total;
1307
+ }
1308
+ clear() {
1309
+ this.write([]);
1310
+ }
1311
+ };
1312
+
1313
+ // src/transport/transport.ts
1314
+ var BEACON_MAX_BYTES = 64 * 1024;
1315
+ var MAX_ITEMS_PER_ENVELOPE = 1e3;
1316
+ var MAX_RETRIES = 5;
1317
+ var RETRY_AFTER_CAP_MS = 3e4;
1318
+ function delay(ms) {
1319
+ return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
1320
+ }
1321
+ function parseRetryAfter(header) {
1322
+ if (!header) return 1e3;
1323
+ const seconds = Number(header);
1324
+ if (!Number.isNaN(seconds)) {
1325
+ return Math.min(RETRY_AFTER_CAP_MS, Math.max(0, seconds * 1e3));
1326
+ }
1327
+ const date = Date.parse(header);
1328
+ if (!Number.isNaN(date)) {
1329
+ return Math.min(RETRY_AFTER_CAP_MS, Math.max(0, date - Date.now()));
1330
+ }
1331
+ return 1e3;
1332
+ }
1333
+ function classifyStatus(status, retryAfter) {
1334
+ if (status === 200 || status === 202) return { action: "drop" };
1335
+ if (status === 400) return { action: "drop" };
1336
+ if (status === 401 || status === 403) return { action: "disable" };
1337
+ if (status === 408) return { action: "retry_backoff" };
1338
+ if (status === 413) return { action: "split" };
1339
+ if (status === 429) return { action: "retry_after", retryAfterMs: parseRetryAfter(retryAfter) };
1340
+ if (status >= 500) return { action: "retry_backoff" };
1341
+ if (status >= 400) return { action: "drop" };
1342
+ return { action: "drop" };
1343
+ }
1344
+ var Transport = class {
1345
+ constructor(config) {
1346
+ __publicField(this, "dsn");
1347
+ __publicField(this, "makeEnvelope");
1348
+ __publicField(this, "fetchImpl");
1349
+ __publicField(this, "logger");
1350
+ __publicField(this, "onDisable");
1351
+ __publicField(this, "flushIntervalMs");
1352
+ __publicField(this, "maxBatch");
1353
+ __publicField(this, "offline");
1354
+ __publicField(this, "pending", []);
1355
+ __publicField(this, "timer", null);
1356
+ __publicField(this, "onlineHandler", null);
1357
+ __publicField(this, "disabled", false);
1358
+ this.dsn = config.dsn;
1359
+ this.makeEnvelope = config.makeEnvelope;
1360
+ this.fetchImpl = config.fetchImpl;
1361
+ this.logger = config.logger;
1362
+ this.onDisable = config.onDisable;
1363
+ this.flushIntervalMs = config.options.flushIntervalMs;
1364
+ this.maxBatch = Math.min(
1365
+ MAX_ITEMS_PER_ENVELOPE,
1366
+ Math.max(1, config.options.maxBatch)
1367
+ );
1368
+ this.offline = new OfflineQueue(config.options.maxQueueBytes, defaultStorage());
1369
+ }
1370
+ /** Begin periodic flushing and listen for connectivity restoration. */
1371
+ start() {
1372
+ if (this.timer === null && this.flushIntervalMs > 0) {
1373
+ this.timer = setInterval(() => {
1374
+ void this.flush();
1375
+ }, this.flushIntervalMs);
1376
+ this.timer.unref?.();
1377
+ }
1378
+ const g2 = globalThis;
1379
+ if (typeof g2.addEventListener === "function" && !this.onlineHandler) {
1380
+ this.onlineHandler = () => {
1381
+ void this.drainOfflineQueue();
1382
+ };
1383
+ g2.addEventListener("online", this.onlineHandler);
1384
+ }
1385
+ }
1386
+ /** Stop timers and listeners (does not flush). */
1387
+ stop() {
1388
+ if (this.timer !== null) {
1389
+ clearInterval(this.timer);
1390
+ this.timer = null;
1391
+ }
1392
+ const g2 = globalThis;
1393
+ if (this.onlineHandler && typeof g2.removeEventListener === "function") {
1394
+ g2.removeEventListener("online", this.onlineHandler);
1395
+ this.onlineHandler = null;
1396
+ }
1397
+ }
1398
+ /** Permanently disable the transport (401/403). Drops pending work. */
1399
+ disable() {
1400
+ this.disabled = true;
1401
+ this.pending = [];
1402
+ this.stop();
1403
+ }
1404
+ /** Queue an item for the next batch; flush eagerly once the batch is full. */
1405
+ send(item) {
1406
+ if (this.disabled) return;
1407
+ this.pending.push(item);
1408
+ if (this.pending.length >= this.maxBatch) {
1409
+ void this.flush();
1410
+ }
1411
+ }
1412
+ /**
1413
+ * Flush all pending items (plus a drain of the offline queue). Resolves to
1414
+ * `true` on completion, or `false` if `timeoutMs` elapsed first.
1415
+ */
1416
+ async flush(timeoutMs) {
1417
+ if (this.disabled) return true;
1418
+ const items = this.pending.splice(0, this.pending.length);
1419
+ const work = (async () => {
1420
+ await this.drainOfflineQueue();
1421
+ for (let i = 0; i < items.length; i += this.maxBatch) {
1422
+ await this.deliver(items.slice(i, i + this.maxBatch));
1423
+ }
1424
+ return true;
1425
+ })();
1426
+ if (typeof timeoutMs === "number" && timeoutMs >= 0) {
1427
+ return Promise.race([work, delay(timeoutMs).then(() => false)]);
1428
+ }
1429
+ return work;
1430
+ }
1431
+ /** Send an already-serialized batch, applying the retry/backoff policy. */
1432
+ async deliver(items, attempt = 0) {
1433
+ if (this.disabled || items.length === 0) return;
1434
+ const json = safeStringify(this.makeEnvelope(items));
1435
+ let outcome;
1436
+ try {
1437
+ outcome = await this.post(json);
1438
+ } catch {
1439
+ outcome = { action: "retry_backoff" };
1440
+ }
1441
+ switch (outcome.action) {
1442
+ case "drop":
1443
+ return;
1444
+ case "disable":
1445
+ this.logger.warn("server rejected credentials; disabling client");
1446
+ this.onDisable();
1447
+ return;
1448
+ case "split": {
1449
+ if (items.length <= 1) {
1450
+ this.logger.warn("single item too large (413); parking offline");
1451
+ this.offline.enqueue(json);
1452
+ return;
1453
+ }
1454
+ const mid = Math.ceil(items.length / 2);
1455
+ await this.deliver(items.slice(0, mid), attempt);
1456
+ await this.deliver(items.slice(mid), attempt);
1457
+ return;
1458
+ }
1459
+ case "retry_after":
1460
+ case "retry_backoff": {
1461
+ if (attempt >= MAX_RETRIES) {
1462
+ this.offline.enqueue(json);
1463
+ return;
1464
+ }
1465
+ const wait = outcome.action === "retry_after" ? outcome.retryAfterMs ?? 1e3 : computeBackoff(attempt);
1466
+ await delay(wait);
1467
+ return this.deliver(items, attempt + 1);
1468
+ }
1469
+ }
1470
+ }
1471
+ /** Re-attempt any envelopes that were parked while offline. */
1472
+ async drainOfflineQueue() {
1473
+ if (this.disabled || !this.offline.available) return;
1474
+ const payloads = this.offline.drain();
1475
+ for (const json of payloads) {
1476
+ let outcome;
1477
+ try {
1478
+ outcome = await this.post(json);
1479
+ } catch {
1480
+ outcome = { action: "retry_backoff" };
1481
+ }
1482
+ if (outcome.action === "disable") {
1483
+ this.onDisable();
1484
+ this.offline.enqueue(json);
1485
+ return;
1486
+ }
1487
+ if (outcome.action === "retry_after" || outcome.action === "retry_backoff") {
1488
+ this.offline.enqueue(json);
1489
+ return;
1490
+ }
1491
+ }
1492
+ }
1493
+ /** Best-effort synchronous-ish flush for page unload via `sendBeacon`. */
1494
+ flushToBeacon() {
1495
+ if (this.disabled) return;
1496
+ const all = this.pending.splice(0, this.pending.length);
1497
+ if (all.length === 0) return;
1498
+ for (let i = 0; i < all.length; i += MAX_ITEMS_PER_ENVELOPE) {
1499
+ this.beaconChunk(all.slice(i, i + MAX_ITEMS_PER_ENVELOPE));
1500
+ }
1501
+ }
1502
+ beaconChunk(items) {
1503
+ const json = safeStringify(this.makeEnvelope(items));
1504
+ const nav = globalThis.navigator;
1505
+ const size = byteLength(json);
1506
+ if (nav && typeof nav.sendBeacon === "function" && size <= BEACON_MAX_BYTES) {
1507
+ try {
1508
+ const blob = new Blob([json], { type: "application/json" });
1509
+ if (nav.sendBeacon(this.dsn.beaconUrl, blob)) return;
1510
+ } catch {
1511
+ }
1512
+ }
1513
+ this.offline.enqueue(json);
1514
+ }
1515
+ /** Compress (when large) and POST one serialized envelope. */
1516
+ async post(json) {
1517
+ const { body, encoding } = await maybeCompress(json);
1518
+ const headers = {
1519
+ "Content-Type": "application/json",
1520
+ "X-Sauron-Key": this.dsn.publicKey
1521
+ };
1522
+ if (encoding) headers["Content-Encoding"] = encoding;
1523
+ const size = typeof body === "string" ? byteLength(body) : body.byteLength;
1524
+ const result = await this.doRequest(headers, body, size <= BEACON_MAX_BYTES);
1525
+ return classifyStatus(result.status, result.retryAfter);
1526
+ }
1527
+ async doRequest(headers, body, keepalive) {
1528
+ if (this.fetchImpl) {
1529
+ beginInternal();
1530
+ let promise;
1531
+ try {
1532
+ promise = this.fetchImpl(this.dsn.envelopeUrl, {
1533
+ method: "POST",
1534
+ headers,
1535
+ body,
1536
+ keepalive
1537
+ });
1538
+ } finally {
1539
+ endInternal();
1540
+ }
1541
+ const res = await promise;
1542
+ return { status: res.status, retryAfter: res.headers?.get?.("Retry-After") ?? null };
1543
+ }
1544
+ return this.xhrRequest(headers, body);
1545
+ }
1546
+ xhrRequest(headers, body) {
1547
+ return new Promise((resolve, reject) => {
1548
+ const XHR = globalThis.XMLHttpRequest;
1549
+ if (typeof XHR !== "function") {
1550
+ reject(new Error("no transport available"));
1551
+ return;
1552
+ }
1553
+ beginInternal();
1554
+ try {
1555
+ const xhr = new XHR();
1556
+ xhr.open("POST", this.dsn.envelopeUrl, true);
1557
+ for (const key of Object.keys(headers)) {
1558
+ try {
1559
+ xhr.setRequestHeader(key, headers[key]);
1560
+ } catch {
1561
+ }
1562
+ }
1563
+ xhr.onload = () => resolve({ status: xhr.status, retryAfter: safeHeader(xhr, "Retry-After") });
1564
+ xhr.onerror = () => reject(new Error("xhr network error"));
1565
+ xhr.ontimeout = () => reject(new Error("xhr timeout"));
1566
+ xhr.send(body);
1567
+ } catch (err) {
1568
+ reject(err instanceof Error ? err : new Error(String(err)));
1569
+ } finally {
1570
+ endInternal();
1571
+ }
1572
+ });
1573
+ }
1574
+ /** Offline queue accessor (used by tests / diagnostics). */
1575
+ get offlineQueue() {
1576
+ return this.offline;
1577
+ }
1578
+ };
1579
+ function safeHeader(xhr, name) {
1580
+ try {
1581
+ return xhr.getResponseHeader(name);
1582
+ } catch {
1583
+ return null;
1584
+ }
1585
+ }
1586
+
1587
+ // src/client.ts
1588
+ var SauronClient = class {
1589
+ constructor(options) {
1590
+ __publicField(this, "options");
1591
+ __publicField(this, "dsn");
1592
+ __publicField(this, "scope");
1593
+ __publicField(this, "transport");
1594
+ __publicField(this, "logger");
1595
+ __publicField(this, "nativeFetch");
1596
+ __publicField(this, "enabled", true);
1597
+ __publicField(this, "installed", false);
1598
+ __publicField(this, "anonymousId", null);
1599
+ __publicField(this, "beaconCleanup", null);
1600
+ this.options = options;
1601
+ this.dsn = parseDsn(options.dsn);
1602
+ this.logger = makeLogger(options.debug);
1603
+ this.scope = new Scope(options.maxBreadcrumbs);
1604
+ this.scope.setTags(options.tags);
1605
+ for (const [name, block] of Object.entries(options.contexts)) {
1606
+ this.scope.setContext(name, block);
1607
+ }
1608
+ for (const [key, value] of Object.entries(options.extra)) {
1609
+ this.scope.setExtra(key, value);
1610
+ }
1611
+ const g2 = globalThis;
1612
+ this.nativeFetch = typeof g2.fetch === "function" ? g2.fetch.bind(globalThis) : void 0;
1613
+ setDsnHost(this.dsn.host);
1614
+ this.transport = new Transport({
1615
+ dsn: this.dsn,
1616
+ options: options.transport,
1617
+ makeEnvelope: (items) => this.makeEnvelope(items),
1618
+ fetchImpl: this.nativeFetch,
1619
+ logger: this.logger,
1620
+ onDisable: () => this.disable()
1621
+ });
1622
+ }
1623
+ /** Install global handlers + auto-instrumentation and start the transport. */
1624
+ install() {
1625
+ if (this.installed) return;
1626
+ this.installed = true;
1627
+ getDeviceId();
1628
+ getSessionId();
1629
+ installGlobalHandlers();
1630
+ installConsole();
1631
+ installDom();
1632
+ installHistory();
1633
+ installFetch();
1634
+ installXhr();
1635
+ if (this.options.performance) installPerformance();
1636
+ if (this.options.screen) setScreenState(this.options.screen);
1637
+ if (this.options.screenTracking) {
1638
+ onNavigation((path) => setScreen(path));
1639
+ }
1640
+ this.beaconCleanup = installBeacon(this.transport);
1641
+ this.transport.start();
1642
+ void this.transport.drainOfflineQueue();
1643
+ this.logger.log("initialized", { dsn: this.dsn.host, project: this.dsn.projectId });
1644
+ }
1645
+ getScope() {
1646
+ return this.scope;
1647
+ }
1648
+ isEnabled() {
1649
+ return this.enabled;
1650
+ }
1651
+ /** The current distinct id: the user id when identified, else an anon id. */
1652
+ getDistinctId() {
1653
+ const user = this.scope.getUser();
1654
+ if (user.id) return user.id;
1655
+ return this.ensureAnonymousId();
1656
+ }
1657
+ /** The anonymous id, or null if one was never needed. */
1658
+ getAnonymousId() {
1659
+ return this.anonymousId;
1660
+ }
1661
+ ensureAnonymousId() {
1662
+ if (!this.anonymousId) this.anonymousId = `anon_${uuidv4()}`;
1663
+ return this.anonymousId;
1664
+ }
1665
+ /** Stamp a fresh envelope (new `sent_at`, current context) around `items`. */
1666
+ makeEnvelope(items) {
1667
+ const header = {
1668
+ dsn: this.dsn.raw,
1669
+ sdk: { name: SDK_NAME, version: SDK_VERSION },
1670
+ sent_at: nowIso(),
1671
+ environment: this.options.environment,
1672
+ release: this.options.release
1673
+ };
1674
+ const context = buildContext(this.options.release, this.scope.getUser());
1675
+ return buildEnvelope(header, context, items);
1676
+ }
1677
+ /** Add a breadcrumb, running it through `beforeBreadcrumb` first. */
1678
+ addBreadcrumb(breadcrumb, hint) {
1679
+ if (!this.enabled) return;
1680
+ let processed = breadcrumb;
1681
+ if (this.options.beforeBreadcrumb) {
1682
+ try {
1683
+ processed = this.options.beforeBreadcrumb(breadcrumb, hint);
1684
+ } catch (err) {
1685
+ this.logger.warn("beforeBreadcrumb threw", err);
1686
+ processed = breadcrumb;
1687
+ }
1688
+ }
1689
+ if (!processed) return;
1690
+ this.scope.addBreadcrumb(processed);
1691
+ }
1692
+ /**
1693
+ * Reconcile an error item to the shared wire shape by filling the optional
1694
+ * `event_id`/`message`/`tags`/`user` fields from the current scope and hint.
1695
+ * Each field is left untouched when the item already sets it, and omitted
1696
+ * entirely when there is nothing to attach (the backend defaults it) — only
1697
+ * `event_id` is always minted so callers can correlate the report.
1698
+ */
1699
+ enrichErrorItem(item, hint) {
1700
+ if (item.event_id === void 0) {
1701
+ const hinted = hint?.event_id;
1702
+ item.event_id = typeof hinted === "string" ? hinted : uuidv4();
1703
+ }
1704
+ if (item.message === void 0 && typeof hint?.message === "string") {
1705
+ item.message = hint.message;
1706
+ }
1707
+ const tags = mergeMeta(this.scope.tags, item.tags);
1708
+ if (Object.keys(tags).length > 0) item.tags = tags;
1709
+ const contexts = mergeMeta(this.scope.contexts, item.contexts);
1710
+ if (Object.keys(contexts).length > 0) item.contexts = contexts;
1711
+ const extra = mergeMeta(this.scope.extra, item.extra);
1712
+ if (Object.keys(extra).length > 0) item.extra = extra;
1713
+ if (item.user === void 0 && this.scope.hasUser()) {
1714
+ item.user = this.scope.getUser();
1715
+ }
1716
+ }
1717
+ /**
1718
+ * Run an item through sampling (errors only) and `beforeSend`, then hand it to
1719
+ * the transport. Returns silently when dropped.
1720
+ */
1721
+ captureItem(item, hint) {
1722
+ if (!this.enabled) return;
1723
+ if (item.type === "error") {
1724
+ if (Math.random() >= this.options.sampleRate) {
1725
+ this.logger.log("dropped error by sampleRate");
1726
+ return;
1727
+ }
1728
+ this.enrichErrorItem(item, hint);
1729
+ }
1730
+ let processed = item;
1731
+ if (this.options.beforeSend) {
1732
+ try {
1733
+ processed = this.options.beforeSend(item, hint);
1734
+ } catch (err) {
1735
+ this.logger.warn("beforeSend threw", err);
1736
+ processed = item;
1737
+ }
1738
+ }
1739
+ if (!processed) {
1740
+ this.logger.log("dropped by beforeSend");
1741
+ return;
1742
+ }
1743
+ this.transport.send(processed);
1744
+ }
1745
+ /** Flush pending events. Resolves false if `timeoutMs` elapses first. */
1746
+ flush(timeoutMs) {
1747
+ return this.transport.flush(timeoutMs);
1748
+ }
1749
+ /** Disable the client (called on 401/403). Stops accepting/sending events. */
1750
+ disable() {
1751
+ if (!this.enabled) return;
1752
+ this.enabled = false;
1753
+ this.transport.disable();
1754
+ this.logger.warn("client disabled");
1755
+ }
1756
+ /** Restore all patched globals and stop timers/listeners. */
1757
+ teardown() {
1758
+ this.enabled = false;
1759
+ this.transport.stop();
1760
+ if (this.beaconCleanup) {
1761
+ this.beaconCleanup();
1762
+ this.beaconCleanup = null;
1763
+ }
1764
+ onNavigation(null);
1765
+ resetScreen();
1766
+ unpatchAll();
1767
+ setDsnHost(null);
1768
+ this.installed = false;
1769
+ }
1770
+ /** Flush then tear down. Resolves to the flush result. */
1771
+ async close(timeoutMs) {
1772
+ const flushed = await this.transport.flush(timeoutMs);
1773
+ this.teardown();
1774
+ return flushed;
1775
+ }
1776
+ };
1777
+ var currentClient = null;
1778
+ function getClient() {
1779
+ return currentClient;
1780
+ }
1781
+ function resolveOptions(options) {
1782
+ if (!options || typeof options.dsn !== "string" || options.dsn.length === 0) {
1783
+ throw new Error("[sauron] init() requires a `dsn`");
1784
+ }
1785
+ const t = options.transport ?? {};
1786
+ return {
1787
+ dsn: options.dsn,
1788
+ environment: options.environment ?? "production",
1789
+ release: options.release ?? null,
1790
+ sampleRate: clamp(options.sampleRate ?? 1, 0, 1),
1791
+ maxBreadcrumbs: options.maxBreadcrumbs ?? 50,
1792
+ tags: options.tags ?? {},
1793
+ contexts: options.contexts ?? {},
1794
+ extra: options.extra ?? {},
1795
+ beforeSend: options.beforeSend,
1796
+ beforeBreadcrumb: options.beforeBreadcrumb,
1797
+ transport: {
1798
+ flushIntervalMs: t.flushIntervalMs ?? 5e3,
1799
+ maxBatch: t.maxBatch ?? 30,
1800
+ maxQueueBytes: t.maxQueueBytes ?? 1048576
1801
+ },
1802
+ performance: options.performance ?? false,
1803
+ screen: options.screen,
1804
+ screenTracking: options.screenTracking ?? false,
1805
+ debug: options.debug ?? false
1806
+ };
1807
+ }
1808
+ function init(options) {
1809
+ if (currentClient) {
1810
+ try {
1811
+ currentClient.teardown();
1812
+ } catch {
1813
+ }
1814
+ }
1815
+ const resolved = resolveOptions(options);
1816
+ const client = new SauronClient(resolved);
1817
+ currentClient = client;
1818
+ client.install();
1819
+ return client;
1820
+ }
1821
+
1822
+ // src/api/breadcrumbs.ts
1823
+ function normalizeBreadcrumb(input) {
1824
+ return {
1825
+ type: input.type ?? "default",
1826
+ category: input.category ?? "default",
1827
+ message: input.message ?? null,
1828
+ level: input.level ?? "info",
1829
+ timestamp: input.timestamp ?? nowIso(),
1830
+ data: input.data ?? null
1831
+ };
1832
+ }
1833
+ function addBreadcrumb(input, hint) {
1834
+ const client = getClient();
1835
+ if (!client) return;
1836
+ client.addBreadcrumb(normalizeBreadcrumb(input), hint);
1837
+ }
1838
+ function addNavigationBreadcrumb(from, to) {
1839
+ addBreadcrumb({
1840
+ type: "navigation",
1841
+ category: "history",
1842
+ level: "info",
1843
+ message: null,
1844
+ data: { from, to }
1845
+ });
1846
+ }
1847
+
1848
+ // src/index.ts
1849
+ function init2(options) {
1850
+ return init(options);
1851
+ }
1852
+ function captureException2(err, hint) {
1853
+ captureException(err, hint);
1854
+ }
1855
+ function captureMessage2(message, level = "info", hint) {
1856
+ captureMessage(message, level, hint);
1857
+ }
1858
+ function track2(name, properties, options) {
1859
+ track(name, properties, options);
1860
+ }
1861
+ function identify2(id, traits) {
1862
+ identify(id, traits);
1863
+ }
1864
+ function trackTransaction2(input) {
1865
+ trackTransaction(input);
1866
+ }
1867
+ function setScreen2(name) {
1868
+ setScreen(name);
1869
+ }
1870
+ function getScreen2() {
1871
+ return getScreen();
1872
+ }
1873
+ function addBreadcrumb2(breadcrumb, hint) {
1874
+ addBreadcrumb(breadcrumb, hint);
1875
+ }
1876
+ function setUser(user) {
1877
+ getClient()?.getScope().setUser(user);
1878
+ }
1879
+ function setTag(key, value) {
1880
+ getClient()?.getScope().setTag(key, value);
1881
+ }
1882
+ function setTags(tags) {
1883
+ getClient()?.getScope().setTags(tags);
1884
+ }
1885
+ function setContext(name, block) {
1886
+ getClient()?.getScope().setContext(name, block);
1887
+ }
1888
+ function setExtra(key, value) {
1889
+ getClient()?.getScope().setExtra(key, value);
1890
+ }
1891
+ function flush(timeoutMs) {
1892
+ const client = getClient();
1893
+ return client ? client.flush(timeoutMs) : Promise.resolve(false);
1894
+ }
1895
+ function close(timeoutMs) {
1896
+ const client = getClient();
1897
+ return client ? client.close(timeoutMs) : Promise.resolve(false);
1898
+ }
1899
+ var Sauron = {
1900
+ init: init2,
1901
+ captureException: captureException2,
1902
+ captureMessage: captureMessage2,
1903
+ track: track2,
1904
+ trackTransaction: trackTransaction2,
1905
+ identify: identify2,
1906
+ addBreadcrumb: addBreadcrumb2,
1907
+ setUser,
1908
+ setTag,
1909
+ setTags,
1910
+ setContext,
1911
+ setExtra,
1912
+ setScreen: setScreen2,
1913
+ getScreen: getScreen2,
1914
+ flush,
1915
+ close,
1916
+ getClient
1917
+ };
1918
+ var index_default = Sauron;
1919
+
1920
+ export { DsnError, SDK_NAME, SDK_VERSION, Sauron, SauronClient, addBreadcrumb2 as addBreadcrumb, buildEnvelope, captureException2 as captureException, captureMessage2 as captureMessage, close, index_default as default, flush, getClient, getScreen2 as getScreen, identify2 as identify, init2 as init, isInAppFrame, parseDsn, parseError, parseStackString, setContext, setExtra, setScreen2 as setScreen, setTag, setTags, setUser, track2 as track, trackTransaction2 as trackTransaction };
1921
+ //# sourceMappingURL=index.js.map
1922
+ //# sourceMappingURL=index.js.map