@vigilon/browser 0.0.0-stage → 0.2.9-beta.pr-1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,703 @@
1
+ // src/core/limits.ts
2
+ var LIMITS = {
3
+ eventsPerRequest: 20,
4
+ serviceNameLength: 100,
5
+ serviceVersionLength: 200,
6
+ exceptionTypeLength: 256,
7
+ exceptionMessageLength: 8 * 1024,
8
+ exceptionStackLength: 64 * 1024,
9
+ exceptionStackLines: 250,
10
+ componentStackLength: 16 * 1024,
11
+ causeDepth: 5,
12
+ debugIdEntries: 100,
13
+ debugIdKeyLength: 2 * 1024,
14
+ pageUrlLength: 2 * 1024,
15
+ pageRouteLength: 200,
16
+ identifierLength: 128,
17
+ tagEntries: 20,
18
+ tagKeyLength: 64,
19
+ tagValueLength: 256
20
+ };
21
+ var SERVICE_TOKEN_PATTERN = /^[A-Za-z0-9._:/-]+$/;
22
+ var SCHEMA_ID = "vigilon.browser.event@1";
23
+
24
+ // src/core/text.ts
25
+ function truncate(value, maxLength) {
26
+ if (value.length <= maxLength) {
27
+ return value;
28
+ }
29
+ const code = value.charCodeAt(maxLength - 1);
30
+ const end = code >= 55296 && code <= 56319 ? maxLength - 1 : maxLength;
31
+ return value.slice(0, end);
32
+ }
33
+ function truncateStack(stack) {
34
+ const lines = stack.split("\n");
35
+ const kept = lines.length > LIMITS.exceptionStackLines ? lines.slice(0, LIMITS.exceptionStackLines).join("\n") : stack;
36
+ return truncate(kept, LIMITS.exceptionStackLength);
37
+ }
38
+ var USERINFO = /^([a-z][a-z0-9+.-]*:\/\/)[^/?#@\s]*@/i;
39
+ var URL_IN_TEXT = /\b[a-z][a-z0-9+.-]*:\/\/\S+/gi;
40
+ var TRAILER = /(:\d+:\d+)?([)\]}>"',;.]*)$/;
41
+ function scrubUrl(value) {
42
+ const url = value.replace(USERINFO, "$1");
43
+ const query = url.indexOf("?");
44
+ const fragment = url.indexOf("#");
45
+ const cut = query === -1 ? fragment : fragment === -1 ? query : Math.min(query, fragment);
46
+ return cut === -1 ? url : url.slice(0, cut);
47
+ }
48
+ function scrubUrlsInText(text) {
49
+ return text.replace(URL_IN_TEXT, (token) => {
50
+ var _a, _b;
51
+ const trailer = (_b = (_a = TRAILER.exec(token)) == null ? void 0 : _a[0]) != null ? _b : "";
52
+ return scrubUrl(token.slice(0, token.length - trailer.length)) + trailer;
53
+ });
54
+ }
55
+
56
+ // src/core/exception.ts
57
+ function toException(error, mechanism, handled) {
58
+ const parts = toErrorParts(error);
59
+ const exception = { ...parts, mechanism, handled };
60
+ const cause = toCauseChain(error);
61
+ if (cause.length > 0) {
62
+ exception.cause = cause;
63
+ }
64
+ return exception;
65
+ }
66
+ function limitException(exception) {
67
+ const limited = {
68
+ ...exception,
69
+ ...limitParts(exception)
70
+ };
71
+ if (exception.cause) {
72
+ limited.cause = exception.cause.slice(0, LIMITS.causeDepth).map((cause) => limitParts(cause));
73
+ }
74
+ return limited;
75
+ }
76
+ function limitParts(parts) {
77
+ return {
78
+ type: truncate(String(parts.type), LIMITS.exceptionTypeLength),
79
+ message: truncate(String(parts.message), LIMITS.exceptionMessageLength),
80
+ stack: typeof parts.stack === "string" && parts.stack.length > 0 ? truncateStack(scrubUrlsInText(parts.stack)) : null
81
+ };
82
+ }
83
+ function toErrorParts(error) {
84
+ if (isErrorLike(error)) {
85
+ return {
86
+ type: typeof error.name === "string" && error.name ? error.name : "Error",
87
+ message: typeof error.message === "string" ? error.message : "",
88
+ stack: typeof error.stack === "string" ? error.stack : null
89
+ };
90
+ }
91
+ return { type: "Error", message: formatValue(error), stack: null };
92
+ }
93
+ function toCauseChain(error) {
94
+ const chain = [];
95
+ const seen = /* @__PURE__ */ new Set([error]);
96
+ let cause = isErrorLike(error) ? error.cause : void 0;
97
+ while (cause !== void 0 && cause !== null && chain.length < LIMITS.causeDepth) {
98
+ if (seen.has(cause)) {
99
+ break;
100
+ }
101
+ seen.add(cause);
102
+ chain.push(toErrorParts(cause));
103
+ cause = isErrorLike(cause) ? cause.cause : void 0;
104
+ }
105
+ return chain;
106
+ }
107
+ function isErrorLike(value) {
108
+ return value instanceof Error || typeof value === "object" && value !== null && typeof value.message === "string" && typeof value.stack === "string";
109
+ }
110
+ function formatValue(value) {
111
+ if (typeof value === "string") {
112
+ return value;
113
+ }
114
+ try {
115
+ const json = JSON.stringify(value);
116
+ return json === void 0 ? String(value) : json;
117
+ } catch {
118
+ return String(value);
119
+ }
120
+ }
121
+
122
+ // src/core/filters.ts
123
+ var DEFAULT_IGNORE_ERRORS = [
124
+ /^Script error\.?$/,
125
+ /^(TypeError: )?Failed to fetch$/,
126
+ /^(TypeError: )?NetworkError when attempting to fetch resource\.?$/,
127
+ /^(TypeError: )?Load failed$/,
128
+ /^AbortError\b/,
129
+ /The (operation|user) aborted a request/,
130
+ /The operation was aborted/,
131
+ /^ResizeObserver loop/
132
+ ];
133
+ var DEFAULT_DENY_URLS = [
134
+ /^(chrome|moz|safari|safari-web|ms-browser)-extension:\/\//,
135
+ /^webkit-masked-url:\/\//
136
+ ];
137
+ var FIRST_URL = /\b[a-z][a-z0-9+.-]*:\/\/[^\s)]+/i;
138
+ function matchesAny(value, patterns) {
139
+ return patterns.some(
140
+ (pattern) => typeof pattern === "string" ? value.includes(pattern) : pattern.test(value)
141
+ );
142
+ }
143
+ function isIgnoredError(event, patterns) {
144
+ const { type, message } = event.exception;
145
+ return matchesAny(message, patterns) || matchesAny(`${type}: ${message}`, patterns) || type === "AbortError" && matchesAny("AbortError", patterns);
146
+ }
147
+ function isDeniedUrl(event, sourceUrl, patterns) {
148
+ var _a, _b;
149
+ const url = sourceUrl || ((_b = FIRST_URL.exec((_a = event.exception.stack) != null ? _a : "")) == null ? void 0 : _b[0]);
150
+ return url !== void 0 && matchesAny(url, patterns);
151
+ }
152
+
153
+ // src/core/flood.ts
154
+ var FloodGuard = class {
155
+ constructor(maxEventsPerPage, duplicateWindowMs) {
156
+ this.maxEventsPerPage = maxEventsPerPage;
157
+ this.duplicateWindowMs = duplicateWindowMs;
158
+ this.lastSeen = /* @__PURE__ */ new Map();
159
+ this.accepted = 0;
160
+ }
161
+ admit(event, now) {
162
+ if (this.accepted >= this.maxEventsPerPage) {
163
+ return false;
164
+ }
165
+ const { type, message, stack } = event.exception;
166
+ const key = `${type}
167
+ ${message}
168
+ ${(stack != null ? stack : "").split("\n", 3).join("\n")}`;
169
+ const previous = this.lastSeen.get(key);
170
+ if (previous !== void 0 && now - previous < this.duplicateWindowMs) {
171
+ return false;
172
+ }
173
+ this.lastSeen.set(key, now);
174
+ this.accepted += 1;
175
+ return true;
176
+ }
177
+ };
178
+
179
+ // src/core/privacy.ts
180
+ var DENIED_TAG_KEY_FRAGMENTS = [
181
+ "password",
182
+ "passwd",
183
+ "secret",
184
+ "token",
185
+ "authorization",
186
+ "cookie",
187
+ "apikey",
188
+ "api_key",
189
+ "api-key",
190
+ "credential",
191
+ "private_key"
192
+ ];
193
+ var EMAIL_LIKE = /[^\s@]+@[^\s@]+\.[^\s@]+/;
194
+ function sanitizeTags(tags) {
195
+ const result = {};
196
+ let count = 0;
197
+ for (const key of Object.keys(tags)) {
198
+ if (count === LIMITS.tagEntries) {
199
+ break;
200
+ }
201
+ const lowered = key.toLowerCase();
202
+ if (key.length === 0 || key.length > LIMITS.tagKeyLength || DENIED_TAG_KEY_FRAGMENTS.some((fragment) => lowered.includes(fragment))) {
203
+ continue;
204
+ }
205
+ result[key] = truncate(String(tags[key]), LIMITS.tagValueLength);
206
+ count += 1;
207
+ }
208
+ return result;
209
+ }
210
+ function sanitizeIdentifier(value) {
211
+ return typeof value === "string" && value.length > 0 && value.length <= LIMITS.identifierLength ? value : null;
212
+ }
213
+ function sanitizeUserId(value) {
214
+ const id = sanitizeIdentifier(value);
215
+ return id !== null && EMAIL_LIKE.test(id) ? null : id;
216
+ }
217
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
218
+ function sanitizeDebugIds(debugIds) {
219
+ const result = {};
220
+ let count = 0;
221
+ for (const [url, id] of Object.entries(debugIds)) {
222
+ const key = scrubUrl(url);
223
+ if (count === LIMITS.debugIdEntries) {
224
+ break;
225
+ }
226
+ if (key.length > 0 && key.length <= LIMITS.debugIdKeyLength && UUID.test(String(id))) {
227
+ result[key] = String(id);
228
+ count += 1;
229
+ }
230
+ }
231
+ return result;
232
+ }
233
+
234
+ // src/core/transport.ts
235
+ var BATCH_DELAY_MS = 1e3;
236
+ var MAX_BODY_BYTES = 240 * 1024;
237
+ var KEEPALIVE_BUDGET_BYTES = 60 * 1024;
238
+ var ENVELOPE_OVERHEAD_BYTES = 512;
239
+ var MAX_QUEUE = 100;
240
+ var MAX_ATTEMPTS = 5;
241
+ var MAX_RETRY_AGE_MS = 30 * 60 * 1e3;
242
+ var DEFAULT_RETRY_AFTER_MS = { 429: 6e4, 503: 1e4 };
243
+ var MAX_BACKOFF_MS = 6e4;
244
+ var Transport = class {
245
+ constructor(options) {
246
+ this.options = options;
247
+ this.queue = [];
248
+ this.pausedUntil = 0;
249
+ this.timer = null;
250
+ this.loop = null;
251
+ this.inFlight = /* @__PURE__ */ new Set();
252
+ }
253
+ enqueue(event) {
254
+ if (this.queue.length >= MAX_QUEUE) {
255
+ this.options.debug("queue full; event dropped");
256
+ return;
257
+ }
258
+ this.queue.push({
259
+ event,
260
+ bytes: byteLength(JSON.stringify(event)),
261
+ attempts: 0,
262
+ firstAttemptAt: null
263
+ });
264
+ this.schedule(BATCH_DELAY_MS);
265
+ }
266
+ /** Sends everything queued. Resolves true when nothing is left. */
267
+ async flush(timeoutMs = 2e3) {
268
+ this.cancelTimer();
269
+ let timeout;
270
+ await Promise.race([
271
+ this.drain().then(() => Promise.all(this.inFlight)),
272
+ new Promise((resolve) => {
273
+ timeout = setTimeout(resolve, timeoutMs);
274
+ })
275
+ ]);
276
+ clearTimeout(timeout);
277
+ return this.queue.length === 0 && this.inFlight.size === 0;
278
+ }
279
+ /**
280
+ * Called when the page is hidden or unloading: sends what fits in the
281
+ * keepalive budget synchronously, so the requests outlive the page.
282
+ */
283
+ flushOnUnload() {
284
+ this.cancelTimer();
285
+ if (this.options.now() < this.pausedUntil) {
286
+ return;
287
+ }
288
+ let budget = KEEPALIVE_BUDGET_BYTES;
289
+ while (this.queue.length > 0) {
290
+ const batch = this.takeBatch(budget);
291
+ if (batch.length === 0) {
292
+ break;
293
+ }
294
+ budget -= batchBytes(batch);
295
+ this.track(this.post(batch, true));
296
+ }
297
+ }
298
+ close() {
299
+ this.cancelTimer();
300
+ this.queue = [];
301
+ }
302
+ schedule(delayMs) {
303
+ if (this.timer !== null || this.queue.length === 0) {
304
+ return;
305
+ }
306
+ const wait = Math.max(delayMs, this.pausedUntil - this.options.now());
307
+ this.timer = setTimeout(() => {
308
+ this.timer = null;
309
+ void this.drain();
310
+ }, wait);
311
+ }
312
+ cancelTimer() {
313
+ if (this.timer !== null) {
314
+ clearTimeout(this.timer);
315
+ this.timer = null;
316
+ }
317
+ }
318
+ drain() {
319
+ if (this.loop === null) {
320
+ this.loop = this.run().finally(() => {
321
+ this.loop = null;
322
+ });
323
+ }
324
+ return this.loop;
325
+ }
326
+ async run() {
327
+ while (this.queue.length > 0 && this.options.now() >= this.pausedUntil) {
328
+ const batch = this.takeBatch(MAX_BODY_BYTES);
329
+ if (batch.length === 0) {
330
+ break;
331
+ }
332
+ await this.track(this.post(batch, false));
333
+ }
334
+ this.schedule(0);
335
+ }
336
+ track(request) {
337
+ this.inFlight.add(request);
338
+ return request.finally(() => this.inFlight.delete(request));
339
+ }
340
+ /** Takes up to 20 events that fit maxBytes, dropping expired ones first. */
341
+ takeBatch(maxBytes) {
342
+ const now = this.options.now();
343
+ this.queue = this.queue.filter((pending) => {
344
+ const expired = pending.attempts >= MAX_ATTEMPTS || pending.firstAttemptAt !== null && now - pending.firstAttemptAt > MAX_RETRY_AGE_MS;
345
+ if (expired) {
346
+ this.options.debug(`event ${pending.event.eventId} dropped after retries`);
347
+ }
348
+ return !expired;
349
+ });
350
+ const batch = [];
351
+ let bytes = ENVELOPE_OVERHEAD_BYTES;
352
+ while (this.queue.length > 0 && batch.length < LIMITS.eventsPerRequest && bytes + this.queue[0].bytes <= maxBytes) {
353
+ const pending = this.queue.shift();
354
+ bytes += pending.bytes;
355
+ batch.push(pending);
356
+ }
357
+ if (batch.length === 0 && this.queue.length > 0 && maxBytes === MAX_BODY_BYTES) {
358
+ batch.push(this.queue.shift());
359
+ }
360
+ return batch;
361
+ }
362
+ async post(batch, keepalive) {
363
+ var _a, _b;
364
+ const now = this.options.now();
365
+ for (const pending of batch) {
366
+ pending.attempts += 1;
367
+ (_a = pending.firstAttemptAt) != null ? _a : pending.firstAttemptAt = now;
368
+ }
369
+ const envelope = {
370
+ ...this.options.envelope,
371
+ sentAt: new Date(now).toISOString(),
372
+ events: batch.map((pending) => pending.event)
373
+ };
374
+ let status;
375
+ let retryAfter = null;
376
+ try {
377
+ const response = await this.options.fetch(this.options.url, {
378
+ method: "POST",
379
+ headers: {
380
+ Authorization: `Bearer ${this.options.apiKey}`,
381
+ "Content-Type": "application/json"
382
+ },
383
+ body: JSON.stringify(envelope),
384
+ keepalive
385
+ });
386
+ status = response.status;
387
+ retryAfter = response.headers.get("Retry-After");
388
+ } catch {
389
+ this.requeue(batch);
390
+ const attempts = Math.max(...batch.map((pending) => pending.attempts));
391
+ this.pause(Math.min(1e3 * 2 ** attempts, MAX_BACKOFF_MS));
392
+ return;
393
+ }
394
+ if (status === 429 || status === 503) {
395
+ this.requeue(batch);
396
+ this.pause((_b = parseRetryAfter(retryAfter, this.options.now())) != null ? _b : DEFAULT_RETRY_AFTER_MS[status]);
397
+ return;
398
+ }
399
+ if (status < 200 || status >= 300) {
400
+ this.options.debug(`request rejected with ${status}; ${batch.length} events dropped`);
401
+ }
402
+ }
403
+ requeue(batch) {
404
+ this.queue.unshift(...batch);
405
+ this.queue.length = Math.min(this.queue.length, MAX_QUEUE);
406
+ }
407
+ pause(ms) {
408
+ this.pausedUntil = Math.max(this.pausedUntil, this.options.now() + ms);
409
+ this.cancelTimer();
410
+ this.schedule(0);
411
+ }
412
+ };
413
+ function parseRetryAfter(value, now) {
414
+ if (value === null || value.trim() === "") {
415
+ return null;
416
+ }
417
+ const seconds = Number(value);
418
+ if (Number.isFinite(seconds)) {
419
+ return Math.max(0, seconds * 1e3);
420
+ }
421
+ const date = Date.parse(value);
422
+ return Number.isNaN(date) ? null : Math.max(0, date - now);
423
+ }
424
+ function batchBytes(batch) {
425
+ return batch.reduce((total, pending) => total + pending.bytes, ENVELOPE_OVERHEAD_BYTES);
426
+ }
427
+ function byteLength(value) {
428
+ return typeof TextEncoder === "undefined" ? value.length * 3 : new TextEncoder().encode(value).length;
429
+ }
430
+
431
+ // src/core/uuid.ts
432
+ function uuid() {
433
+ const bytes = new Uint8Array(16);
434
+ const crypto = globalThis.crypto;
435
+ if (crypto == null ? void 0 : crypto.getRandomValues) {
436
+ crypto.getRandomValues(bytes);
437
+ } else {
438
+ for (let i = 0; i < 16; i += 1) {
439
+ bytes[i] = Math.floor(Math.random() * 256);
440
+ }
441
+ }
442
+ bytes[6] = bytes[6] & 15 | 64;
443
+ bytes[8] = bytes[8] & 63 | 128;
444
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0"));
445
+ return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`;
446
+ }
447
+
448
+ // src/browser/session.ts
449
+ var SESSION_KEY = "vigilon.session";
450
+ function sessionId() {
451
+ try {
452
+ const existing = window.sessionStorage.getItem(SESSION_KEY);
453
+ if (existing) {
454
+ return existing;
455
+ }
456
+ const created = uuid();
457
+ window.sessionStorage.setItem(SESSION_KEY, created);
458
+ return created;
459
+ } catch {
460
+ return null;
461
+ }
462
+ }
463
+
464
+ // src/browser/client.ts
465
+ var SDK_NAME = "@vigilon/browser";
466
+ var SDK_VERSION = "0.2.9-beta.pr-1.1";
467
+ var DEFAULT_ENDPOINT = "https://ingest.vigilon.io";
468
+ var EVENTS_PATH = "/v1/browser/events";
469
+ var DUPLICATE_WINDOW_MS = 6e4;
470
+ var BrowserClient = class {
471
+ constructor(options) {
472
+ this.options = options;
473
+ this.tags = {};
474
+ this.userId = null;
475
+ this.removeListeners = [];
476
+ var _a, _b, _c, _d;
477
+ const endpoint = ((_a = options.endpoint) != null ? _a : DEFAULT_ENDPOINT).replace(/\/+$/, "");
478
+ this.transport = new Transport({
479
+ url: `${endpoint}${EVENTS_PATH}`,
480
+ apiKey: options.apiKey,
481
+ envelope: {
482
+ schema: SCHEMA_ID,
483
+ sdk: { name: SDK_NAME, version: SDK_VERSION },
484
+ service: {
485
+ name: options.serviceName,
486
+ environment: options.environment,
487
+ ...options.serviceVersion ? { version: truncate(options.serviceVersion, LIMITS.serviceVersionLength) } : {}
488
+ }
489
+ },
490
+ fetch: (input, init2) => window.fetch(input, init2),
491
+ now: () => Date.now(),
492
+ debug: (message) => this.debug(message)
493
+ });
494
+ this.floodGuard = new FloodGuard(
495
+ (_b = options.maxEventsPerPage) != null ? _b : 100,
496
+ DUPLICATE_WINDOW_MS
497
+ );
498
+ this.ignoreErrors = [...DEFAULT_IGNORE_ERRORS, ...(_c = options.ignoreErrors) != null ? _c : []];
499
+ this.denyUrls = [...DEFAULT_DENY_URLS, ...(_d = options.denyUrls) != null ? _d : []];
500
+ }
501
+ install() {
502
+ this.listen(window, "error", (event) => {
503
+ const { error, message, filename } = event;
504
+ this.capture(error != null ? error : message, { mechanism: "onerror", handled: false }, filename);
505
+ });
506
+ this.listen(window, "unhandledrejection", (event) => {
507
+ this.capture(event.reason, {
508
+ mechanism: "unhandledrejection",
509
+ handled: false
510
+ });
511
+ });
512
+ this.listen(document, "visibilitychange", () => {
513
+ if (document.visibilityState === "hidden") {
514
+ this.transport.flushOnUnload();
515
+ }
516
+ });
517
+ this.listen(window, "pagehide", () => this.transport.flushOnUnload());
518
+ }
519
+ capture(error, context = {}, sourceUrl) {
520
+ try {
521
+ return this.captureUnsafe(error, context, sourceUrl);
522
+ } catch (failure) {
523
+ this.debug(`capture failed: ${String(failure)}`);
524
+ return null;
525
+ }
526
+ }
527
+ setUser(user) {
528
+ this.userId = user === null ? null : sanitizeUserId(user.id);
529
+ if (user !== null && this.userId === null) {
530
+ this.debug("user.id dropped: it must be an opaque ID of at most 128 characters, not an email address");
531
+ }
532
+ }
533
+ setTag(key, value) {
534
+ this.tags[key] = String(value);
535
+ }
536
+ flush(timeoutMs) {
537
+ return this.transport.flush(timeoutMs);
538
+ }
539
+ async close(timeoutMs) {
540
+ for (const remove of this.removeListeners.splice(0)) {
541
+ remove();
542
+ }
543
+ const flushed = await this.transport.flush(timeoutMs);
544
+ this.transport.close();
545
+ return flushed;
546
+ }
547
+ captureUnsafe(error, context, sourceUrl) {
548
+ var _a, _b, _c;
549
+ const sampleRate = (_a = this.options.sampleRate) != null ? _a : 1;
550
+ if (sampleRate < 1 && Math.random() >= sampleRate) {
551
+ return null;
552
+ }
553
+ const exception = toException(
554
+ error,
555
+ (_b = context.mechanism) != null ? _b : "manual",
556
+ (_c = context.handled) != null ? _c : true
557
+ );
558
+ if (context.componentStack) {
559
+ exception.componentStack = context.componentStack;
560
+ }
561
+ const session = sessionId();
562
+ let event = {
563
+ type: "exception",
564
+ eventId: uuid(),
565
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
566
+ exception,
567
+ page: {
568
+ url: window.location.href,
569
+ ...context.route ? { route: context.route } : {}
570
+ },
571
+ session: session === null ? null : { id: session },
572
+ user: this.userId === null ? null : { id: this.userId },
573
+ traceId: null,
574
+ tags: { ...this.tags }
575
+ };
576
+ if (isIgnoredError(event, this.ignoreErrors) || isDeniedUrl(event, sourceUrl, this.denyUrls)) {
577
+ return null;
578
+ }
579
+ if (this.options.beforeSend) {
580
+ try {
581
+ event = this.options.beforeSend(event);
582
+ } catch (failure) {
583
+ this.debug(`beforeSend threw; event dropped: ${String(failure)}`);
584
+ return null;
585
+ }
586
+ if (event === null) {
587
+ return null;
588
+ }
589
+ }
590
+ const limited = limitEvent(event);
591
+ if (!this.floodGuard.admit(limited, Date.now())) {
592
+ return null;
593
+ }
594
+ this.transport.enqueue(limited);
595
+ return limited.eventId;
596
+ }
597
+ listen(target, type, handler) {
598
+ target.addEventListener(type, handler);
599
+ this.removeListeners.push(() => target.removeEventListener(type, handler));
600
+ }
601
+ debug(message) {
602
+ if (this.options.debug) {
603
+ console.warn(`[Vigilon] ${message}`);
604
+ }
605
+ }
606
+ };
607
+ function limitEvent(event) {
608
+ var _a, _b, _c;
609
+ const limited = {
610
+ type: "exception",
611
+ eventId: event.eventId,
612
+ timestamp: event.timestamp,
613
+ exception: limitException(event.exception),
614
+ page: { url: truncate(scrubUrl(String(event.page.url)), LIMITS.pageUrlLength) }
615
+ };
616
+ if (event.exception.componentStack) {
617
+ limited.exception.componentStack = truncate(
618
+ event.exception.componentStack,
619
+ LIMITS.componentStackLength
620
+ );
621
+ }
622
+ if (event.page.route) {
623
+ limited.page.route = truncate(event.page.route, LIMITS.pageRouteLength);
624
+ }
625
+ const session = sanitizeIdentifier((_a = event.session) == null ? void 0 : _a.id);
626
+ limited.session = session === null ? null : { id: session };
627
+ const user = sanitizeUserId((_b = event.user) == null ? void 0 : _b.id);
628
+ limited.user = user === null ? null : { id: user };
629
+ limited.traceId = sanitizeIdentifier(event.traceId);
630
+ limited.tags = sanitizeTags((_c = event.tags) != null ? _c : {});
631
+ if (event.debugIds) {
632
+ limited.debugIds = sanitizeDebugIds(event.debugIds);
633
+ }
634
+ return limited;
635
+ }
636
+ function invalidOptions(options) {
637
+ if (!options || typeof options.apiKey !== "string" || options.apiKey.length === 0) {
638
+ return "apiKey is required";
639
+ }
640
+ for (const [name, value] of [
641
+ ["serviceName", options.serviceName],
642
+ ["environment", options.environment]
643
+ ]) {
644
+ if (typeof value !== "string" || value.length === 0 || value.length > LIMITS.serviceNameLength || !SERVICE_TOKEN_PATTERN.test(value)) {
645
+ return `${name} must be 1 to 100 characters of letters, digits, and . _ : / -`;
646
+ }
647
+ }
648
+ return null;
649
+ }
650
+
651
+ // src/index.ts
652
+ var client = null;
653
+ function init(options) {
654
+ if (typeof window === "undefined" || typeof document === "undefined") {
655
+ return false;
656
+ }
657
+ const problem = invalidOptions(options);
658
+ if (problem !== null) {
659
+ console.error(`[Vigilon] not started: ${problem}`);
660
+ return false;
661
+ }
662
+ if (!options.serviceVersion) {
663
+ console.warn(
664
+ "[Vigilon] serviceVersion is not set; errors will not be linked to a release"
665
+ );
666
+ }
667
+ if (client !== null) {
668
+ void client.close(0);
669
+ }
670
+ client = new BrowserClient(options);
671
+ client.install();
672
+ return true;
673
+ }
674
+ function captureException(error, context) {
675
+ return client === null ? null : client.capture(error, context);
676
+ }
677
+ function setUser(user) {
678
+ client == null ? void 0 : client.setUser(user);
679
+ }
680
+ function setTag(key, value) {
681
+ client == null ? void 0 : client.setTag(key, value);
682
+ }
683
+ function flush(timeoutMs) {
684
+ return client === null ? Promise.resolve(true) : client.flush(timeoutMs);
685
+ }
686
+ async function close(timeoutMs) {
687
+ if (client === null) {
688
+ return true;
689
+ }
690
+ const closing = client;
691
+ client = null;
692
+ return closing.close(timeoutMs);
693
+ }
694
+ export {
695
+ SDK_VERSION,
696
+ captureException,
697
+ close,
698
+ flush,
699
+ init,
700
+ setTag,
701
+ setUser
702
+ };
703
+ //# sourceMappingURL=index.mjs.map