1 |
|
2 | var Sentry = (function (exports) {
|
3 |
|
4 | |
5 |
|
6 |
|
7 | exports.Severity = void 0;
|
8 | (function (Severity) {
|
9 |
|
10 | Severity["Fatal"] = "fatal";
|
11 |
|
12 | Severity["Error"] = "error";
|
13 |
|
14 | Severity["Warning"] = "warning";
|
15 |
|
16 | Severity["Log"] = "log";
|
17 |
|
18 | Severity["Info"] = "info";
|
19 |
|
20 | Severity["Debug"] = "debug";
|
21 |
|
22 | Severity["Critical"] = "critical";
|
23 | })(exports.Severity || (exports.Severity = {}));
|
24 |
|
25 | |
26 |
|
27 |
|
28 |
|
29 |
|
30 | function forget(promise) {
|
31 | void promise.then(null, e => {
|
32 |
|
33 |
|
34 | console.error(e);
|
35 | });
|
36 | }
|
37 |
|
38 | |
39 |
|
40 |
|
41 |
|
42 | const fallbackGlobalObject = {};
|
43 | |
44 |
|
45 |
|
46 |
|
47 |
|
48 | function getGlobalObject() {
|
49 | return (typeof window !== 'undefined'
|
50 | ? window
|
51 | : typeof self !== 'undefined'
|
52 | ? self
|
53 | : fallbackGlobalObject);
|
54 | }
|
55 | |
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 | function getGlobalSingleton(name, creator, obj) {
|
67 | const global = (obj || getGlobalObject());
|
68 | const __SENTRY__ = (global.__SENTRY__ = global.__SENTRY__ || {});
|
69 | const singleton = __SENTRY__[name] || (__SENTRY__[name] = creator());
|
70 | return singleton;
|
71 | }
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | const objectToString = Object.prototype.toString;
|
77 | |
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 | function isError(wat) {
|
85 | switch (objectToString.call(wat)) {
|
86 | case '[object Error]':
|
87 | case '[object Exception]':
|
88 | case '[object DOMException]':
|
89 | return true;
|
90 | default:
|
91 | return isInstanceOf(wat, Error);
|
92 | }
|
93 | }
|
94 | function isBuiltin(wat, ty) {
|
95 | return objectToString.call(wat) === `[object ${ty}]`;
|
96 | }
|
97 | |
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | function isErrorEvent(wat) {
|
105 | return isBuiltin(wat, 'ErrorEvent');
|
106 | }
|
107 | |
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 | function isDOMError(wat) {
|
115 | return isBuiltin(wat, 'DOMError');
|
116 | }
|
117 | |
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 | function isDOMException(wat) {
|
125 | return isBuiltin(wat, 'DOMException');
|
126 | }
|
127 | |
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 | function isString(wat) {
|
135 | return isBuiltin(wat, 'String');
|
136 | }
|
137 | |
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 | function isPrimitive(wat) {
|
145 | return wat === null || (typeof wat !== 'object' && typeof wat !== 'function');
|
146 | }
|
147 | |
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 | function isPlainObject(wat) {
|
155 | return isBuiltin(wat, 'Object');
|
156 | }
|
157 | |
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 | function isEvent(wat) {
|
165 | return typeof Event !== 'undefined' && isInstanceOf(wat, Event);
|
166 | }
|
167 | |
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 | function isElement(wat) {
|
175 | return typeof Element !== 'undefined' && isInstanceOf(wat, Element);
|
176 | }
|
177 | |
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 | function isRegExp(wat) {
|
185 | return isBuiltin(wat, 'RegExp');
|
186 | }
|
187 | |
188 |
|
189 |
|
190 |
|
191 | function isThenable(wat) {
|
192 |
|
193 | return Boolean(wat && wat.then && typeof wat.then === 'function');
|
194 | }
|
195 | |
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 | function isSyntheticEvent(wat) {
|
203 | return isPlainObject(wat) && 'nativeEvent' in wat && 'preventDefault' in wat && 'stopPropagation' in wat;
|
204 | }
|
205 | |
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 | function isNaN$1(wat) {
|
213 | return typeof wat === 'number' && wat !== wat;
|
214 | }
|
215 | |
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 | function isInstanceOf(wat, base) {
|
224 | try {
|
225 | return wat instanceof base;
|
226 | }
|
227 | catch (_e) {
|
228 | return false;
|
229 | }
|
230 | }
|
231 |
|
232 | |
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 | function htmlTreeAsString(elem, keyAttrs) {
|
239 |
|
240 |
|
241 |
|
242 |
|
243 | try {
|
244 | let currentElem = elem;
|
245 | const MAX_TRAVERSE_HEIGHT = 5;
|
246 | const MAX_OUTPUT_LEN = 80;
|
247 | const out = [];
|
248 | let height = 0;
|
249 | let len = 0;
|
250 | const separator = ' > ';
|
251 | const sepLength = separator.length;
|
252 | let nextStr;
|
253 |
|
254 | while (currentElem && height++ < MAX_TRAVERSE_HEIGHT) {
|
255 | nextStr = _htmlElementAsString(currentElem, keyAttrs);
|
256 | // bail out if
|
257 | // - nextStr is the 'html' element
|
258 | // - the length of the string that would be created exceeds MAX_OUTPUT_LEN
|
259 | // (ignore this limit if we are on the first iteration)
|
260 | if (nextStr === 'html' || (height > 1 && len + out.length * sepLength + nextStr.length >= MAX_OUTPUT_LEN)) {
|
261 | break;
|
262 | }
|
263 | out.push(nextStr);
|
264 | len += nextStr.length;
|
265 | currentElem = currentElem.parentNode;
|
266 | }
|
267 | return out.reverse().join(separator);
|
268 | }
|
269 | catch (_oO) {
|
270 | return '<unknown>';
|
271 | }
|
272 | }
|
273 | /**
|
274 | * Returns a simple, query-selector representation of a DOM element
|
275 | * e.g. [HTMLElement] => input#foo.btn[name=baz]
|
276 | * @returns generated DOM path
|
277 | */
|
278 | function _htmlElementAsString(el, keyAttrs) {
|
279 | const elem = el;
|
280 | const out = [];
|
281 | let className;
|
282 | let classes;
|
283 | let key;
|
284 | let attr;
|
285 | let i;
|
286 | if (!elem || !elem.tagName) {
|
287 | return '';
|
288 | }
|
289 | out.push(elem.tagName.toLowerCase());
|
290 | // Pairs of attribute keys defined in `serializeAttribute` and their values on element.
|
291 | const keyAttrPairs = keyAttrs && keyAttrs.length
|
292 | ? keyAttrs.filter(keyAttr => elem.getAttribute(keyAttr)).map(keyAttr => [keyAttr, elem.getAttribute(keyAttr)])
|
293 | : null;
|
294 | if (keyAttrPairs && keyAttrPairs.length) {
|
295 | keyAttrPairs.forEach(keyAttrPair => {
|
296 | out.push(`[${keyAttrPair[0]}="${keyAttrPair[1]}"]`);
|
297 | });
|
298 | }
|
299 | else {
|
300 | if (elem.id) {
|
301 | out.push(`#${elem.id}`);
|
302 | }
|
303 | // eslint-disable-next-line prefer-const
|
304 | className = elem.className;
|
305 | if (className && isString(className)) {
|
306 | classes = className.split(/\s+/);
|
307 | for (i = 0; i < classes.length; i++) {
|
308 | out.push(`.${classes[i]}`);
|
309 | }
|
310 | }
|
311 | }
|
312 | const allowedAttrs = ['type', 'name', 'title', 'alt'];
|
313 | for (i = 0; i < allowedAttrs.length; i++) {
|
314 | key = allowedAttrs[i];
|
315 | attr = elem.getAttribute(key);
|
316 | if (attr) {
|
317 | out.push(`[${key}="${attr}"]`);
|
318 | }
|
319 | }
|
320 | return out.join('');
|
321 | }
|
322 | /**
|
323 | * A safe form of location.href
|
324 | */
|
325 | function getLocationHref() {
|
326 | const global = getGlobalObject();
|
327 | try {
|
328 | return global.document.location.href;
|
329 | }
|
330 | catch (oO) {
|
331 | return '';
|
332 | }
|
333 | }
|
334 |
|
335 | const setPrototypeOf = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array ? setProtoOf : mixinProperties);
|
336 | /**
|
337 | * setPrototypeOf polyfill using __proto__
|
338 | */
|
339 | // eslint-disable-next-line @typescript-eslint/ban-types
|
340 | function setProtoOf(obj, proto) {
|
341 | // @ts-ignore __proto__ does not exist on obj
|
342 | obj.__proto__ = proto;
|
343 | return obj;
|
344 | }
|
345 | /**
|
346 | * setPrototypeOf polyfill using mixin
|
347 | */
|
348 | // eslint-disable-next-line @typescript-eslint/ban-types
|
349 | function mixinProperties(obj, proto) {
|
350 | for (const prop in proto) {
|
351 | if (!Object.prototype.hasOwnProperty.call(obj, prop)) {
|
352 | // @ts-ignore typescript complains about indexing so we remove
|
353 | obj[prop] = proto[prop];
|
354 | }
|
355 | }
|
356 | return obj;
|
357 | }
|
358 |
|
359 | /** An error emitted by Sentry SDKs and related utilities. */
|
360 | class SentryError extends Error {
|
361 | constructor(message) {
|
362 | super(message);
|
363 | this.message = message;
|
364 | this.name = new.target.prototype.constructor.name;
|
365 | setPrototypeOf(this, new.target.prototype);
|
366 | }
|
367 | }
|
368 |
|
369 | /*
|
370 | * This file defines flags and constants that can be modified during compile time in order to facilitate tree shaking
|
371 | * for users.
|
372 | *
|
373 | * Debug flags need to be declared in each package individually and must not be imported across package boundaries,
|
374 | * because some build tools have trouble tree-shaking imported guards.
|
375 | *
|
376 | * As a convention, we define debug flags in a `flags.ts` file in the root of a package's `src` folder.
|
377 | *
|
378 | * Debug flag files will contain "magic strings" like `true` that may get replaced with actual values during
|
379 | * our, or the user's build process. Take care when introducing new flags - they must not throw if they are not
|
380 | * replaced.
|
381 | */
|
382 | /** Flag that is true for debug builds, false otherwise. */
|
383 | const IS_DEBUG_BUILD$3 = true;
|
384 |
|
385 | /** Regular expression used to parse a Dsn. */
|
386 | const DSN_REGEX = /^(?:(\w+):)\/\/(?:(\w+)(?::(\w+))?@)([\w.-]+)(?::(\d+))?\/(.+)/;
|
387 | function isValidProtocol(protocol) {
|
388 | return protocol === 'http' || protocol === 'https';
|
389 | }
|
390 | /**
|
391 | * Renders the string representation of this Dsn.
|
392 | *
|
393 | * By default, this will render the public representation without the password
|
394 | * component. To get the deprecated private representation, set `withPassword`
|
395 | * to true.
|
396 | *
|
397 | * @param withPassword When set to true, the password will be included.
|
398 | */
|
399 | function dsnToString(dsn, withPassword = false) {
|
400 | const { host, path, pass, port, projectId, protocol, publicKey } = dsn;
|
401 | return (`${protocol}://${publicKey}${withPassword && pass ? `:${pass}` : ''}` +
|
402 | `@${host}${port ? `:${port}` : ''}/${path ? `${path}/` : path}${projectId}`);
|
403 | }
|
404 | function dsnFromString(str) {
|
405 | const match = DSN_REGEX.exec(str);
|
406 | if (!match) {
|
407 | throw new SentryError(`Invalid Sentry Dsn: ${str}`);
|
408 | }
|
409 | const [protocol, publicKey, pass = '', host, port = '', lastPath] = match.slice(1);
|
410 | let path = '';
|
411 | let projectId = lastPath;
|
412 | const split = projectId.split('/');
|
413 | if (split.length > 1) {
|
414 | path = split.slice(0, -1).join('/');
|
415 | projectId = split.pop();
|
416 | }
|
417 | if (projectId) {
|
418 | const projectMatch = projectId.match(/^\d+/);
|
419 | if (projectMatch) {
|
420 | projectId = projectMatch[0];
|
421 | }
|
422 | }
|
423 | return dsnFromComponents({ host, pass, path, projectId, port, protocol: protocol, publicKey });
|
424 | }
|
425 | function dsnFromComponents(components) {
|
426 | // TODO this is for backwards compatibility, and can be removed in a future version
|
427 | if ('user' in components && !('publicKey' in components)) {
|
428 | components.publicKey = components.user;
|
429 | }
|
430 | return {
|
431 | user: components.publicKey || '',
|
432 | protocol: components.protocol,
|
433 | publicKey: components.publicKey || '',
|
434 | pass: components.pass || '',
|
435 | host: components.host,
|
436 | port: components.port || '',
|
437 | path: components.path || '',
|
438 | projectId: components.projectId,
|
439 | };
|
440 | }
|
441 | function validateDsn(dsn) {
|
442 | if (!IS_DEBUG_BUILD$3) {
|
443 | return;
|
444 | }
|
445 | const { port, projectId, protocol } = dsn;
|
446 | const requiredComponents = ['protocol', 'publicKey', 'host', 'projectId'];
|
447 | requiredComponents.forEach(component => {
|
448 | if (!dsn[component]) {
|
449 | throw new SentryError(`Invalid Sentry Dsn: ${component} missing`);
|
450 | }
|
451 | });
|
452 | if (!projectId.match(/^\d+$/)) {
|
453 | throw new SentryError(`Invalid Sentry Dsn: Invalid projectId ${projectId}`);
|
454 | }
|
455 | if (!isValidProtocol(protocol)) {
|
456 | throw new SentryError(`Invalid Sentry Dsn: Invalid protocol ${protocol}`);
|
457 | }
|
458 | if (port && isNaN(parseInt(port, 10))) {
|
459 | throw new SentryError(`Invalid Sentry Dsn: Invalid port ${port}`);
|
460 | }
|
461 | return true;
|
462 | }
|
463 | /** The Sentry Dsn, identifying a Sentry instance and project. */
|
464 | function makeDsn(from) {
|
465 | const components = typeof from === 'string' ? dsnFromString(from) : dsnFromComponents(from);
|
466 | validateDsn(components);
|
467 | return components;
|
468 | }
|
469 |
|
470 | const SeverityLevels = ['fatal', 'error', 'warning', 'log', 'info', 'debug', 'critical'];
|
471 |
|
472 | // TODO: Implement different loggers for different environments
|
473 | const global$6 = getGlobalObject();
|
474 | /** Prefix for logging strings */
|
475 | const PREFIX = 'Sentry Logger ';
|
476 | const CONSOLE_LEVELS = ['debug', 'info', 'warn', 'error', 'log', 'assert'];
|
477 | /**
|
478 | * Temporarily disable sentry console instrumentations.
|
479 | *
|
480 | * @param callback The function to run against the original `console` messages
|
481 | * @returns The results of the callback
|
482 | */
|
483 | function consoleSandbox(callback) {
|
484 | const global = getGlobalObject();
|
485 | if (!('console' in global)) {
|
486 | return callback();
|
487 | }
|
488 | const originalConsole = global.console;
|
489 | const wrappedLevels = {};
|
490 | // Restore all wrapped console methods
|
491 | CONSOLE_LEVELS.forEach(level => {
|
492 | // TODO(v7): Remove this check as it's only needed for Node 6
|
493 | const originalWrappedFunc = originalConsole[level] && originalConsole[level].__sentry_original__;
|
494 | if (level in global.console && originalWrappedFunc) {
|
495 | wrappedLevels[level] = originalConsole[level];
|
496 | originalConsole[level] = originalWrappedFunc;
|
497 | }
|
498 | });
|
499 | try {
|
500 | return callback();
|
501 | }
|
502 | finally {
|
503 | // Revert restoration to wrapped state
|
504 | Object.keys(wrappedLevels).forEach(level => {
|
505 | originalConsole[level] = wrappedLevels[level];
|
506 | });
|
507 | }
|
508 | }
|
509 | function makeLogger() {
|
510 | let enabled = false;
|
511 | const logger = {
|
512 | enable: () => {
|
513 | enabled = true;
|
514 | },
|
515 | disable: () => {
|
516 | enabled = false;
|
517 | },
|
518 | };
|
519 | if (IS_DEBUG_BUILD$3) {
|
520 | CONSOLE_LEVELS.forEach(name => {
|
521 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
522 | logger[name] = (...args) => {
|
523 | if (enabled) {
|
524 | consoleSandbox(() => {
|
525 | global$6.console[name](`${PREFIX}[${name}]:`, ...args);
|
526 | });
|
527 | }
|
528 | };
|
529 | });
|
530 | }
|
531 | else {
|
532 | CONSOLE_LEVELS.forEach(name => {
|
533 | logger[name] = () => undefined;
|
534 | });
|
535 | }
|
536 | return logger;
|
537 | }
|
538 | // Ensure we only have a single logger instance, even if multiple versions of @sentry/utils are being used
|
539 | let logger;
|
540 | if (IS_DEBUG_BUILD$3) {
|
541 | logger = getGlobalSingleton('logger', makeLogger);
|
542 | }
|
543 | else {
|
544 | logger = makeLogger();
|
545 | }
|
546 |
|
547 | /**
|
548 | * Truncates given string to the maximum characters count
|
549 | *
|
550 | * @param str An object that contains serializable values
|
551 | * @param max Maximum number of characters in truncated string (0 = unlimited)
|
552 | * @returns string Encoded
|
553 | */
|
554 | function truncate(str, max = 0) {
|
555 | if (typeof str !== 'string' || max === 0) {
|
556 | return str;
|
557 | }
|
558 | return str.length <= max ? str : `${str.substr(0, max)}...`;
|
559 | }
|
560 | /**
|
561 | * Join values in array
|
562 | * @param input array of values to be joined together
|
563 | * @param delimiter string to be placed in-between values
|
564 | * @returns Joined values
|
565 | */
|
566 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
567 | function safeJoin(input, delimiter) {
|
568 | if (!Array.isArray(input)) {
|
569 | return '';
|
570 | }
|
571 | const output = [];
|
572 | // eslint-disable-next-line @typescript-eslint/prefer-for-of
|
573 | for (let i = 0; i < input.length; i++) {
|
574 | const value = input[i];
|
575 | try {
|
576 | output.push(String(value));
|
577 | }
|
578 | catch (e) {
|
579 | output.push('[value cannot be serialized]');
|
580 | }
|
581 | }
|
582 | return output.join(delimiter);
|
583 | }
|
584 | /**
|
585 | * Checks if the value matches a regex or includes the string
|
586 | * @param value The string value to be checked against
|
587 | * @param pattern Either a regex or a string that must be contained in value
|
588 | */
|
589 | function isMatchingPattern(value, pattern) {
|
590 | if (!isString(value)) {
|
591 | return false;
|
592 | }
|
593 | if (isRegExp(pattern)) {
|
594 | return pattern.test(value);
|
595 | }
|
596 | if (typeof pattern === 'string') {
|
597 | return value.indexOf(pattern) !== -1;
|
598 | }
|
599 | return false;
|
600 | }
|
601 |
|
602 | /**
|
603 | * Replace a method in an object with a wrapped version of itself.
|
604 | *
|
605 | * @param source An object that contains a method to be wrapped.
|
606 | * @param name The name of the method to be wrapped.
|
607 | * @param replacementFactory A higher-order function that takes the original version of the given method and returns a
|
608 | * wrapped version. Note: The function returned by `replacementFactory` needs to be a non-arrow function, in order to
|
609 | * preserve the correct value of `this`, and the original method must be called using `origMethod.call(this, <other
|
610 | * args>)` or `origMethod.apply(this, [<other args>])` (rather than being called directly), again to preserve `this`.
|
611 | * @returns void
|
612 | */
|
613 | function fill(source, name, replacementFactory) {
|
614 | if (!(name in source)) {
|
615 | return;
|
616 | }
|
617 | const original = source[name];
|
618 | const wrapped = replacementFactory(original);
|
619 | // Make sure it's a function first, as we need to attach an empty prototype for `defineProperties` to work
|
620 | // otherwise it'll throw "TypeError: Object.defineProperties called on non-object"
|
621 | if (typeof wrapped === 'function') {
|
622 | try {
|
623 | markFunctionWrapped(wrapped, original);
|
624 | }
|
625 | catch (_Oo) {
|
626 | // This can throw if multiple fill happens on a global object like XMLHttpRequest
|
627 | // Fixes https://github.com/getsentry/sentry-javascript/issues/2043
|
628 | }
|
629 | }
|
630 | source[name] = wrapped;
|
631 | }
|
632 | /**
|
633 | * Defines a non-enumerable property on the given object.
|
634 | *
|
635 | * @param obj The object on which to set the property
|
636 | * @param name The name of the property to be set
|
637 | * @param value The value to which to set the property
|
638 | */
|
639 | function addNonEnumerableProperty(obj, name, value) {
|
640 | Object.defineProperty(obj, name, {
|
641 | // enumerable: false, // the default, so we can save on bundle size by not explicitly setting it
|
642 | value: value,
|
643 | writable: true,
|
644 | configurable: true,
|
645 | });
|
646 | }
|
647 | /**
|
648 | * Remembers the original function on the wrapped function and
|
649 | * patches up the prototype.
|
650 | *
|
651 | * @param wrapped the wrapper function
|
652 | * @param original the original function that gets wrapped
|
653 | */
|
654 | function markFunctionWrapped(wrapped, original) {
|
655 | const proto = original.prototype || {};
|
656 | wrapped.prototype = original.prototype = proto;
|
657 | addNonEnumerableProperty(wrapped, '__sentry_original__', original);
|
658 | }
|
659 | /**
|
660 | * This extracts the original function if available. See
|
661 | * `markFunctionWrapped` for more information.
|
662 | *
|
663 | * @param func the function to unwrap
|
664 | * @returns the unwrapped version of the function if available.
|
665 | */
|
666 | function getOriginalFunction(func) {
|
667 | return func.__sentry_original__;
|
668 | }
|
669 | /**
|
670 | * Encodes given object into url-friendly format
|
671 | *
|
672 | * @param object An object that contains serializable values
|
673 | * @returns string Encoded
|
674 | */
|
675 | function urlEncode(object) {
|
676 | return Object.keys(object)
|
677 | .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(object[key])}`)
|
678 | .join('&');
|
679 | }
|
680 | /**
|
681 | * Transforms any object into an object literal with all its attributes
|
682 | * attached to it.
|
683 | *
|
684 | * @param value Initial source that we have to transform in order for it to be usable by the serializer
|
685 | */
|
686 | function convertToPlainObject(value) {
|
687 | let newObj = value;
|
688 | if (isError(value)) {
|
689 | newObj = Object.assign({ message: value.message, name: value.name, stack: value.stack }, getOwnProperties(value));
|
690 | }
|
691 | else if (isEvent(value)) {
|
692 | const event = value;
|
693 | newObj = Object.assign({ type: event.type, target: serializeEventTarget(event.target), currentTarget: serializeEventTarget(event.currentTarget) }, getOwnProperties(event));
|
694 | if (typeof CustomEvent !== 'undefined' && isInstanceOf(value, CustomEvent)) {
|
695 | newObj.detail = event.detail;
|
696 | }
|
697 | }
|
698 | return newObj;
|
699 | }
|
700 | /** Creates a string representation of the target of an `Event` object */
|
701 | function serializeEventTarget(target) {
|
702 | try {
|
703 | return isElement(target) ? htmlTreeAsString(target) : Object.prototype.toString.call(target);
|
704 | }
|
705 | catch (_oO) {
|
706 | return '<unknown>';
|
707 | }
|
708 | }
|
709 | /** Filters out all but an object's own properties */
|
710 | function getOwnProperties(obj) {
|
711 | const extractedProps = {};
|
712 | for (const property in obj) {
|
713 | if (Object.prototype.hasOwnProperty.call(obj, property)) {
|
714 | extractedProps[property] = obj[property];
|
715 | }
|
716 | }
|
717 | return extractedProps;
|
718 | }
|
719 | /**
|
720 | * Given any captured exception, extract its keys and create a sorted
|
721 | * and truncated list that will be used inside the event message.
|
722 | * eg. `Non-error exception captured with keys: foo, bar, baz`
|
723 | */
|
724 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
725 | function extractExceptionKeysForMessage(exception, maxLength = 40) {
|
726 | const keys = Object.keys(convertToPlainObject(exception));
|
727 | keys.sort();
|
728 | if (!keys.length) {
|
729 | return '[object has no keys]';
|
730 | }
|
731 | if (keys[0].length >= maxLength) {
|
732 | return truncate(keys[0], maxLength);
|
733 | }
|
734 | for (let includedKeys = keys.length; includedKeys > 0; includedKeys--) {
|
735 | const serialized = keys.slice(0, includedKeys).join(', ');
|
736 | if (serialized.length > maxLength) {
|
737 | continue;
|
738 | }
|
739 | if (includedKeys === keys.length) {
|
740 | return serialized;
|
741 | }
|
742 | return truncate(serialized, maxLength);
|
743 | }
|
744 | return '';
|
745 | }
|
746 | /**
|
747 | * Given any object, return the new object with removed keys that value was `undefined`.
|
748 | * Works recursively on objects and arrays.
|
749 | */
|
750 | function dropUndefinedKeys(val) {
|
751 | if (isPlainObject(val)) {
|
752 | const rv = {};
|
753 | for (const key of Object.keys(val)) {
|
754 | if (typeof val[key] !== 'undefined') {
|
755 | rv[key] = dropUndefinedKeys(val[key]);
|
756 | }
|
757 | }
|
758 | return rv;
|
759 | }
|
760 | if (Array.isArray(val)) {
|
761 | return val.map(dropUndefinedKeys);
|
762 | }
|
763 | return val;
|
764 | }
|
765 |
|
766 | const STACKTRACE_LIMIT = 50;
|
767 | /**
|
768 | * Creates a stack parser with the supplied line parsers
|
769 | *
|
770 | * StackFrames are returned in the correct order for Sentry Exception
|
771 | * frames and with Sentry SDK internal frames removed from the top and bottom
|
772 | *
|
773 | */
|
774 | function createStackParser(...parsers) {
|
775 | const sortedParsers = parsers.sort((a, b) => a[0] - b[0]).map(p => p[1]);
|
776 | return (stack, skipFirst = 0) => {
|
777 | const frames = [];
|
778 | for (const line of stack.split('\n').slice(skipFirst)) {
|
779 | for (const parser of sortedParsers) {
|
780 | const frame = parser(line);
|
781 | if (frame) {
|
782 | frames.push(frame);
|
783 | break;
|
784 | }
|
785 | }
|
786 | }
|
787 | return stripSentryFramesAndReverse(frames);
|
788 | };
|
789 | }
|
790 | /**
|
791 | * @hidden
|
792 | */
|
793 | function stripSentryFramesAndReverse(stack) {
|
794 | if (!stack.length) {
|
795 | return [];
|
796 | }
|
797 | let localStack = stack;
|
798 | const firstFrameFunction = localStack[0].function || '';
|
799 | const lastFrameFunction = localStack[localStack.length - 1].function || '';
|
800 | // If stack starts with one of our API calls, remove it (starts, meaning it's the top of the stack - aka last call)
|
801 | if (firstFrameFunction.indexOf('captureMessage') !== -1 || firstFrameFunction.indexOf('captureException') !== -1) {
|
802 | localStack = localStack.slice(1);
|
803 | }
|
804 | // If stack ends with one of our internal API calls, remove it (ends, meaning it's the bottom of the stack - aka top-most call)
|
805 | if (lastFrameFunction.indexOf('sentryWrapped') !== -1) {
|
806 | localStack = localStack.slice(0, -1);
|
807 | }
|
808 | // The frame where the crash happened, should be the last entry in the array
|
809 | return localStack
|
810 | .slice(0, STACKTRACE_LIMIT)
|
811 | .map(frame => (Object.assign(Object.assign({}, frame), { filename: frame.filename || localStack[0].filename, function: frame.function || '?' })))
|
812 | .reverse();
|
813 | }
|
814 | const defaultFunctionName = '<anonymous>';
|
815 | /**
|
816 | * Safely extract function name from itself
|
817 | */
|
818 | function getFunctionName(fn) {
|
819 | try {
|
820 | if (!fn || typeof fn !== 'function') {
|
821 | return defaultFunctionName;
|
822 | }
|
823 | return fn.name || defaultFunctionName;
|
824 | }
|
825 | catch (e) {
|
826 | // Just accessing custom props in some Selenium environments
|
827 | // can cause a "Permission denied" exception (see raven-js#495).
|
828 | return defaultFunctionName;
|
829 | }
|
830 | }
|
831 |
|
832 | /**
|
833 | * Tells whether current environment supports Fetch API
|
834 | * {@link supportsFetch}.
|
835 | *
|
836 | * @returns Answer to the given question.
|
837 | */
|
838 | function supportsFetch() {
|
839 | if (!('fetch' in getGlobalObject())) {
|
840 | return false;
|
841 | }
|
842 | try {
|
843 | new Headers();
|
844 | new Request('');
|
845 | new Response();
|
846 | return true;
|
847 | }
|
848 | catch (e) {
|
849 | return false;
|
850 | }
|
851 | }
|
852 | /**
|
853 | * isNativeFetch checks if the given function is a native implementation of fetch()
|
854 | */
|
855 | // eslint-disable-next-line @typescript-eslint/ban-types
|
856 | function isNativeFetch(func) {
|
857 | return func && /^function fetch\(\)\s+\{\s+\[native code\]\s+\}$/.test(func.toString());
|
858 | }
|
859 | /**
|
860 | * Tells whether current environment supports Fetch API natively
|
861 | * {@link supportsNativeFetch}.
|
862 | *
|
863 | * @returns true if `window.fetch` is natively implemented, false otherwise
|
864 | */
|
865 | function supportsNativeFetch() {
|
866 | if (!supportsFetch()) {
|
867 | return false;
|
868 | }
|
869 | const global = getGlobalObject();
|
870 | // Fast path to avoid DOM I/O
|
871 | // eslint-disable-next-line @typescript-eslint/unbound-method
|
872 | if (isNativeFetch(global.fetch)) {
|
873 | return true;
|
874 | }
|
875 | // window.fetch is implemented, but is polyfilled or already wrapped (e.g: by a chrome extension)
|
876 | // so create a "pure" iframe to see if that has native fetch
|
877 | let result = false;
|
878 | const doc = global.document;
|
879 | // eslint-disable-next-line deprecation/deprecation
|
880 | if (doc && typeof doc.createElement === 'function') {
|
881 | try {
|
882 | const sandbox = doc.createElement('iframe');
|
883 | sandbox.hidden = true;
|
884 | doc.head.appendChild(sandbox);
|
885 | if (sandbox.contentWindow && sandbox.contentWindow.fetch) {
|
886 | // eslint-disable-next-line @typescript-eslint/unbound-method
|
887 | result = isNativeFetch(sandbox.contentWindow.fetch);
|
888 | }
|
889 | doc.head.removeChild(sandbox);
|
890 | }
|
891 | catch (err) {
|
892 | logger.warn('Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ', err);
|
893 | }
|
894 | }
|
895 | return result;
|
896 | }
|
897 | /**
|
898 | * Tells whether current environment supports Referrer Policy API
|
899 | * {@link supportsReferrerPolicy}.
|
900 | *
|
901 | * @returns Answer to the given question.
|
902 | */
|
903 | function supportsReferrerPolicy() {
|
904 | // Despite all stars in the sky saying that Edge supports old draft syntax, aka 'never', 'always', 'origin' and 'default'
|
905 | // (see https://caniuse.com/#feat=referrer-policy),
|
906 | // it doesn't. And it throws an exception instead of ignoring this parameter...
|
907 | // REF: https://github.com/getsentry/raven-js/issues/1233
|
908 | if (!supportsFetch()) {
|
909 | return false;
|
910 | }
|
911 | try {
|
912 | new Request('_', {
|
913 | referrerPolicy: 'origin',
|
914 | });
|
915 | return true;
|
916 | }
|
917 | catch (e) {
|
918 | return false;
|
919 | }
|
920 | }
|
921 | /**
|
922 | * Tells whether current environment supports History API
|
923 | * {@link supportsHistory}.
|
924 | *
|
925 | * @returns Answer to the given question.
|
926 | */
|
927 | function supportsHistory() {
|
928 | // NOTE: in Chrome App environment, touching history.pushState, *even inside
|
929 | // a try/catch block*, will cause Chrome to output an error to console.error
|
930 | // borrowed from: https://github.com/angular/angular.js/pull/13945/files
|
931 | const global = getGlobalObject();
|
932 | /* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
933 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
934 | const chrome = global.chrome;
|
935 | const isChromePackagedApp = chrome && chrome.app && chrome.app.runtime;
|
936 | /* eslint-enable @typescript-eslint/no-unsafe-member-access */
|
937 | const hasHistoryApi = 'history' in global && !!global.history.pushState && !!global.history.replaceState;
|
938 | return !isChromePackagedApp && hasHistoryApi;
|
939 | }
|
940 |
|
941 | const global$5 = getGlobalObject();
|
942 | /**
|
943 | * Instrument native APIs to call handlers that can be used to create breadcrumbs, APM spans etc.
|
944 | * - Console API
|
945 | * - Fetch API
|
946 | * - XHR API
|
947 | * - History API
|
948 | * - DOM API (click/typing)
|
949 | * - Error API
|
950 | * - UnhandledRejection API
|
951 | */
|
952 | const handlers = {};
|
953 | const instrumented = {};
|
954 | /** Instruments given API */
|
955 | function instrument(type) {
|
956 | if (instrumented[type]) {
|
957 | return;
|
958 | }
|
959 | instrumented[type] = true;
|
960 | switch (type) {
|
961 | case 'console':
|
962 | instrumentConsole();
|
963 | break;
|
964 | case 'dom':
|
965 | instrumentDOM();
|
966 | break;
|
967 | case 'xhr':
|
968 | instrumentXHR();
|
969 | break;
|
970 | case 'fetch':
|
971 | instrumentFetch();
|
972 | break;
|
973 | case 'history':
|
974 | instrumentHistory();
|
975 | break;
|
976 | case 'error':
|
977 | instrumentError();
|
978 | break;
|
979 | case 'unhandledrejection':
|
980 | instrumentUnhandledRejection();
|
981 | break;
|
982 | default:
|
983 | logger.warn('unknown instrumentation type:', type);
|
984 | return;
|
985 | }
|
986 | }
|
987 | /**
|
988 | * Add handler that will be called when given type of instrumentation triggers.
|
989 | * Use at your own risk, this might break without changelog notice, only used internally.
|
990 | * @hidden
|
991 | */
|
992 | function addInstrumentationHandler(type, callback) {
|
993 | handlers[type] = handlers[type] || [];
|
994 | handlers[type].push(callback);
|
995 | instrument(type);
|
996 | }
|
997 | /** JSDoc */
|
998 | function triggerHandlers(type, data) {
|
999 | if (!type || !handlers[type]) {
|
1000 | return;
|
1001 | }
|
1002 | for (const handler of handlers[type] || []) {
|
1003 | try {
|
1004 | handler(data);
|
1005 | }
|
1006 | catch (e) {
|
1007 | logger.error(`Error while triggering instrumentation handler.\nType: ${type}\nName: ${getFunctionName(handler)}\nError:`, e);
|
1008 | }
|
1009 | }
|
1010 | }
|
1011 | /** JSDoc */
|
1012 | function instrumentConsole() {
|
1013 | if (!('console' in global$5)) {
|
1014 | return;
|
1015 | }
|
1016 | CONSOLE_LEVELS.forEach(function (level) {
|
1017 | if (!(level in global$5.console)) {
|
1018 | return;
|
1019 | }
|
1020 | fill(global$5.console, level, function (originalConsoleMethod) {
|
1021 | return function (...args) {
|
1022 | triggerHandlers('console', { args, level });
|
1023 | // this fails for some browsers. :(
|
1024 | if (originalConsoleMethod) {
|
1025 | originalConsoleMethod.apply(global$5.console, args);
|
1026 | }
|
1027 | };
|
1028 | });
|
1029 | });
|
1030 | }
|
1031 | /** JSDoc */
|
1032 | function instrumentFetch() {
|
1033 | if (!supportsNativeFetch()) {
|
1034 | return;
|
1035 | }
|
1036 | fill(global$5, 'fetch', function (originalFetch) {
|
1037 | return function (...args) {
|
1038 | const handlerData = {
|
1039 | args,
|
1040 | fetchData: {
|
1041 | method: getFetchMethod(args),
|
1042 | url: getFetchUrl(args),
|
1043 | },
|
1044 | startTimestamp: Date.now(),
|
1045 | };
|
1046 | triggerHandlers('fetch', Object.assign({}, handlerData));
|
1047 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
1048 | return originalFetch.apply(global$5, args).then((response) => {
|
1049 | triggerHandlers('fetch', Object.assign(Object.assign({}, handlerData), { endTimestamp: Date.now(), response }));
|
1050 | return response;
|
1051 | }, (error) => {
|
1052 | triggerHandlers('fetch', Object.assign(Object.assign({}, handlerData), { endTimestamp: Date.now(), error }));
|
1053 | // NOTE: If you are a Sentry user, and you are seeing this stack frame,
|
1054 | // it means the sentry.javascript SDK caught an error invoking your application code.
|
1055 | // This is expected behavior and NOT indicative of a bug with sentry.javascript.
|
1056 | throw error;
|
1057 | });
|
1058 | };
|
1059 | });
|
1060 | }
|
1061 | /* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
1062 | /** Extract `method` from fetch call arguments */
|
1063 | function getFetchMethod(fetchArgs = []) {
|
1064 | if ('Request' in global$5 && isInstanceOf(fetchArgs[0], Request) && fetchArgs[0].method) {
|
1065 | return String(fetchArgs[0].method).toUpperCase();
|
1066 | }
|
1067 | if (fetchArgs[1] && fetchArgs[1].method) {
|
1068 | return String(fetchArgs[1].method).toUpperCase();
|
1069 | }
|
1070 | return 'GET';
|
1071 | }
|
1072 | /** Extract `url` from fetch call arguments */
|
1073 | function getFetchUrl(fetchArgs = []) {
|
1074 | if (typeof fetchArgs[0] === 'string') {
|
1075 | return fetchArgs[0];
|
1076 | }
|
1077 | if ('Request' in global$5 && isInstanceOf(fetchArgs[0], Request)) {
|
1078 | return fetchArgs[0].url;
|
1079 | }
|
1080 | return String(fetchArgs[0]);
|
1081 | }
|
1082 | /* eslint-enable @typescript-eslint/no-unsafe-member-access */
|
1083 | /** JSDoc */
|
1084 | function instrumentXHR() {
|
1085 | if (!('XMLHttpRequest' in global$5)) {
|
1086 | return;
|
1087 | }
|
1088 | const xhrproto = XMLHttpRequest.prototype;
|
1089 | fill(xhrproto, 'open', function (originalOpen) {
|
1090 | return function (...args) {
|
1091 | // eslint-disable-next-line @typescript-eslint/no-this-alias
|
1092 | const xhr = this;
|
1093 | const url = args[1];
|
1094 | const xhrInfo = (xhr.__sentry_xhr__ = {
|
1095 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
1096 | method: isString(args[0]) ? args[0].toUpperCase() : args[0],
|
1097 | url: args[1],
|
1098 | });
|
1099 | // if Sentry key appears in URL, don't capture it as a request
|
1100 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
1101 | if (isString(url) && xhrInfo.method === 'POST' && url.match(/sentry_key/)) {
|
1102 | xhr.__sentry_own_request__ = true;
|
1103 | }
|
1104 | const onreadystatechangeHandler = function () {
|
1105 | if (xhr.readyState === 4) {
|
1106 | try {
|
1107 | // touching statusCode in some platforms throws
|
1108 | // an exception
|
1109 | xhrInfo.status_code = xhr.status;
|
1110 | }
|
1111 | catch (e) {
|
1112 | /* do nothing */
|
1113 | }
|
1114 | triggerHandlers('xhr', {
|
1115 | args,
|
1116 | endTimestamp: Date.now(),
|
1117 | startTimestamp: Date.now(),
|
1118 | xhr,
|
1119 | });
|
1120 | }
|
1121 | };
|
1122 | if ('onreadystatechange' in xhr && typeof xhr.onreadystatechange === 'function') {
|
1123 | fill(xhr, 'onreadystatechange', function (original) {
|
1124 | return function (...readyStateArgs) {
|
1125 | onreadystatechangeHandler();
|
1126 | return original.apply(xhr, readyStateArgs);
|
1127 | };
|
1128 | });
|
1129 | }
|
1130 | else {
|
1131 | xhr.addEventListener('readystatechange', onreadystatechangeHandler);
|
1132 | }
|
1133 | return originalOpen.apply(xhr, args);
|
1134 | };
|
1135 | });
|
1136 | fill(xhrproto, 'send', function (originalSend) {
|
1137 | return function (...args) {
|
1138 | if (this.__sentry_xhr__ && args[0] !== undefined) {
|
1139 | this.__sentry_xhr__.body = args[0];
|
1140 | }
|
1141 | triggerHandlers('xhr', {
|
1142 | args,
|
1143 | startTimestamp: Date.now(),
|
1144 | xhr: this,
|
1145 | });
|
1146 | return originalSend.apply(this, args);
|
1147 | };
|
1148 | });
|
1149 | }
|
1150 | let lastHref;
|
1151 | /** JSDoc */
|
1152 | function instrumentHistory() {
|
1153 | if (!supportsHistory()) {
|
1154 | return;
|
1155 | }
|
1156 | const oldOnPopState = global$5.onpopstate;
|
1157 | global$5.onpopstate = function (...args) {
|
1158 | const to = global$5.location.href;
|
1159 | // keep track of the current URL state, as we always receive only the updated state
|
1160 | const from = lastHref;
|
1161 | lastHref = to;
|
1162 | triggerHandlers('history', {
|
1163 | from,
|
1164 | to,
|
1165 | });
|
1166 | if (oldOnPopState) {
|
1167 | // Apparently this can throw in Firefox when incorrectly implemented plugin is installed.
|
1168 | // https://github.com/getsentry/sentry-javascript/issues/3344
|
1169 | // https://github.com/bugsnag/bugsnag-js/issues/469
|
1170 | try {
|
1171 | return oldOnPopState.apply(this, args);
|
1172 | }
|
1173 | catch (_oO) {
|
1174 | // no-empty
|
1175 | }
|
1176 | }
|
1177 | };
|
1178 | /** @hidden */
|
1179 | function historyReplacementFunction(originalHistoryFunction) {
|
1180 | return function (...args) {
|
1181 | const url = args.length > 2 ? args[2] : undefined;
|
1182 | if (url) {
|
1183 | // coerce to string (this is what pushState does)
|
1184 | const from = lastHref;
|
1185 | const to = String(url);
|
1186 | // keep track of the current URL state, as we always receive only the updated state
|
1187 | lastHref = to;
|
1188 | triggerHandlers('history', {
|
1189 | from,
|
1190 | to,
|
1191 | });
|
1192 | }
|
1193 | return originalHistoryFunction.apply(this, args);
|
1194 | };
|
1195 | }
|
1196 | fill(global$5.history, 'pushState', historyReplacementFunction);
|
1197 | fill(global$5.history, 'replaceState', historyReplacementFunction);
|
1198 | }
|
1199 | const debounceDuration = 1000;
|
1200 | let debounceTimerID;
|
1201 | let lastCapturedEvent;
|
1202 | /**
|
1203 | * Decide whether the current event should finish the debounce of previously captured one.
|
1204 | * @param previous previously captured event
|
1205 | * @param current event to be captured
|
1206 | */
|
1207 | function shouldShortcircuitPreviousDebounce(previous, current) {
|
1208 | // If there was no previous event, it should always be swapped for the new one.
|
1209 | if (!previous) {
|
1210 | return true;
|
1211 | }
|
1212 | // If both events have different type, then user definitely performed two separate actions. e.g. click + keypress.
|
1213 | if (previous.type !== current.type) {
|
1214 | return true;
|
1215 | }
|
1216 | try {
|
1217 | // If both events have the same type, it's still possible that actions were performed on different targets.
|
1218 | // e.g. 2 clicks on different buttons.
|
1219 | if (previous.target !== current.target) {
|
1220 | return true;
|
1221 | }
|
1222 | }
|
1223 | catch (e) {
|
1224 | // just accessing `target` property can throw an exception in some rare circumstances
|
1225 | // see: https://github.com/getsentry/sentry-javascript/issues/838
|
1226 | }
|
1227 | // If both events have the same type _and_ same `target` (an element which triggered an event, _not necessarily_
|
1228 | // to which an event listener was attached), we treat them as the same action, as we want to capture
|
1229 | // only one breadcrumb. e.g. multiple clicks on the same button, or typing inside a user input box.
|
1230 | return false;
|
1231 | }
|
1232 | /**
|
1233 | * Decide whether an event should be captured.
|
1234 | * @param event event to be captured
|
1235 | */
|
1236 | function shouldSkipDOMEvent(event) {
|
1237 | // We are only interested in filtering `keypress` events for now.
|
1238 | if (event.type !== 'keypress') {
|
1239 | return false;
|
1240 | }
|
1241 | try {
|
1242 | const target = event.target;
|
1243 | if (!target || !target.tagName) {
|
1244 | return true;
|
1245 | }
|
1246 | // Only consider keypress events on actual input elements. This will disregard keypresses targeting body
|
1247 | // e.g.tabbing through elements, hotkeys, etc.
|
1248 | if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
|
1249 | return false;
|
1250 | }
|
1251 | }
|
1252 | catch (e) {
|
1253 | // just accessing `target` property can throw an exception in some rare circumstances
|
1254 | // see: https://github.com/getsentry/sentry-javascript/issues/838
|
1255 | }
|
1256 | return true;
|
1257 | }
|
1258 | /**
|
1259 | * Wraps addEventListener to capture UI breadcrumbs
|
1260 | * @param handler function that will be triggered
|
1261 | * @param globalListener indicates whether event was captured by the global event listener
|
1262 | * @returns wrapped breadcrumb events handler
|
1263 | * @hidden
|
1264 | */
|
1265 | function makeDOMEventHandler(handler, globalListener = false) {
|
1266 | return (event) => {
|
1267 | // It's possible this handler might trigger multiple times for the same
|
1268 | // event (e.g. event propagation through node ancestors).
|
1269 | // Ignore if we've already captured that event.
|
1270 | if (!event || lastCapturedEvent === event) {
|
1271 | return;
|
1272 | }
|
1273 | // We always want to skip _some_ events.
|
1274 | if (shouldSkipDOMEvent(event)) {
|
1275 | return;
|
1276 | }
|
1277 | const name = event.type === 'keypress' ? 'input' : event.type;
|
1278 | // If there is no debounce timer, it means that we can safely capture the new event and store it for future comparisons.
|
1279 | if (debounceTimerID === undefined) {
|
1280 | handler({
|
1281 | event: event,
|
1282 | name,
|
1283 | global: globalListener,
|
1284 | });
|
1285 | lastCapturedEvent = event;
|
1286 | }
|
1287 | // If there is a debounce awaiting, see if the new event is different enough to treat it as a unique one.
|
1288 | // If that's the case, emit the previous event and store locally the newly-captured DOM event.
|
1289 | else if (shouldShortcircuitPreviousDebounce(lastCapturedEvent, event)) {
|
1290 | handler({
|
1291 | event: event,
|
1292 | name,
|
1293 | global: globalListener,
|
1294 | });
|
1295 | lastCapturedEvent = event;
|
1296 | }
|
1297 | // Start a new debounce timer that will prevent us from capturing multiple events that should be grouped together.
|
1298 | clearTimeout(debounceTimerID);
|
1299 | debounceTimerID = global$5.setTimeout(() => {
|
1300 | debounceTimerID = undefined;
|
1301 | }, debounceDuration);
|
1302 | };
|
1303 | }
|
1304 | /** JSDoc */
|
1305 | function instrumentDOM() {
|
1306 | if (!('document' in global$5)) {
|
1307 | return;
|
1308 | }
|
1309 | // Make it so that any click or keypress that is unhandled / bubbled up all the way to the document triggers our dom
|
1310 | // handlers. (Normally we have only one, which captures a breadcrumb for each click or keypress.) Do this before
|
1311 | // we instrument `addEventListener` so that we don't end up attaching this handler twice.
|
1312 | const triggerDOMHandler = triggerHandlers.bind(null, 'dom');
|
1313 | const globalDOMEventHandler = makeDOMEventHandler(triggerDOMHandler, true);
|
1314 | global$5.document.addEventListener('click', globalDOMEventHandler, false);
|
1315 | global$5.document.addEventListener('keypress', globalDOMEventHandler, false);
|
1316 | // After hooking into click and keypress events bubbled up to `document`, we also hook into user-handled
|
1317 | // clicks & keypresses, by adding an event listener of our own to any element to which they add a listener. That
|
1318 | // way, whenever one of their handlers is triggered, ours will be, too. (This is needed because their handler
|
1319 | // could potentially prevent the event from bubbling up to our global listeners. This way, our handler are still
|
1320 | // guaranteed to fire at least once.)
|
1321 | ['EventTarget', 'Node'].forEach((target) => {
|
1322 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
1323 | const proto = global$5[target] && global$5[target].prototype;
|
1324 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-prototype-builtins
|
1325 | if (!proto || !proto.hasOwnProperty || !proto.hasOwnProperty('addEventListener')) {
|
1326 | return;
|
1327 | }
|
1328 | fill(proto, 'addEventListener', function (originalAddEventListener) {
|
1329 | return function (type, listener, options) {
|
1330 | if (type === 'click' || type == 'keypress') {
|
1331 | try {
|
1332 | const el = this;
|
1333 | const handlers = (el.__sentry_instrumentation_handlers__ = el.__sentry_instrumentation_handlers__ || {});
|
1334 | const handlerForType = (handlers[type] = handlers[type] || { refCount: 0 });
|
1335 | if (!handlerForType.handler) {
|
1336 | const handler = makeDOMEventHandler(triggerDOMHandler);
|
1337 | handlerForType.handler = handler;
|
1338 | originalAddEventListener.call(this, type, handler, options);
|
1339 | }
|
1340 | handlerForType.refCount += 1;
|
1341 | }
|
1342 | catch (e) {
|
1343 | // Accessing dom properties is always fragile.
|
1344 | // Also allows us to skip `addEventListenrs` calls with no proper `this` context.
|
1345 | }
|
1346 | }
|
1347 | return originalAddEventListener.call(this, type, listener, options);
|
1348 | };
|
1349 | });
|
1350 | fill(proto, 'removeEventListener', function (originalRemoveEventListener) {
|
1351 | return function (type, listener, options) {
|
1352 | if (type === 'click' || type == 'keypress') {
|
1353 | try {
|
1354 | const el = this;
|
1355 | const handlers = el.__sentry_instrumentation_handlers__ || {};
|
1356 | const handlerForType = handlers[type];
|
1357 | if (handlerForType) {
|
1358 | handlerForType.refCount -= 1;
|
1359 | // If there are no longer any custom handlers of the current type on this element, we can remove ours, too.
|
1360 | if (handlerForType.refCount <= 0) {
|
1361 | originalRemoveEventListener.call(this, type, handlerForType.handler, options);
|
1362 | handlerForType.handler = undefined;
|
1363 | delete handlers[type]; // eslint-disable-line @typescript-eslint/no-dynamic-delete
|
1364 | }
|
1365 | // If there are no longer any custom handlers of any type on this element, cleanup everything.
|
1366 | if (Object.keys(handlers).length === 0) {
|
1367 | delete el.__sentry_instrumentation_handlers__;
|
1368 | }
|
1369 | }
|
1370 | }
|
1371 | catch (e) {
|
1372 | // Accessing dom properties is always fragile.
|
1373 | // Also allows us to skip `addEventListenrs` calls with no proper `this` context.
|
1374 | }
|
1375 | }
|
1376 | return originalRemoveEventListener.call(this, type, listener, options);
|
1377 | };
|
1378 | });
|
1379 | });
|
1380 | }
|
1381 | let _oldOnErrorHandler = null;
|
1382 | /** JSDoc */
|
1383 | function instrumentError() {
|
1384 | _oldOnErrorHandler = global$5.onerror;
|
1385 | global$5.onerror = function (msg, url, line, column, error) {
|
1386 | triggerHandlers('error', {
|
1387 | column,
|
1388 | error,
|
1389 | line,
|
1390 | msg,
|
1391 | url,
|
1392 | });
|
1393 | if (_oldOnErrorHandler) {
|
1394 | // eslint-disable-next-line prefer-rest-params
|
1395 | return _oldOnErrorHandler.apply(this, arguments);
|
1396 | }
|
1397 | return false;
|
1398 | };
|
1399 | }
|
1400 | let _oldOnUnhandledRejectionHandler = null;
|
1401 | /** JSDoc */
|
1402 | function instrumentUnhandledRejection() {
|
1403 | _oldOnUnhandledRejectionHandler = global$5.onunhandledrejection;
|
1404 | global$5.onunhandledrejection = function (e) {
|
1405 | triggerHandlers('unhandledrejection', e);
|
1406 | if (_oldOnUnhandledRejectionHandler) {
|
1407 | // eslint-disable-next-line prefer-rest-params
|
1408 | return _oldOnUnhandledRejectionHandler.apply(this, arguments);
|
1409 | }
|
1410 | return true;
|
1411 | };
|
1412 | }
|
1413 |
|
1414 | /* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
1415 | /* eslint-disable @typescript-eslint/no-explicit-any */
|
1416 | /**
|
1417 | * Helper to decycle json objects
|
1418 | */
|
1419 | function memoBuilder() {
|
1420 | const hasWeakSet = typeof WeakSet === 'function';
|
1421 | const inner = hasWeakSet ? new WeakSet() : [];
|
1422 | function memoize(obj) {
|
1423 | if (hasWeakSet) {
|
1424 | if (inner.has(obj)) {
|
1425 | return true;
|
1426 | }
|
1427 | inner.add(obj);
|
1428 | return false;
|
1429 | }
|
1430 | // eslint-disable-next-line @typescript-eslint/prefer-for-of
|
1431 | for (let i = 0; i < inner.length; i++) {
|
1432 | const value = inner[i];
|
1433 | if (value === obj) {
|
1434 | return true;
|
1435 | }
|
1436 | }
|
1437 | inner.push(obj);
|
1438 | return false;
|
1439 | }
|
1440 | function unmemoize(obj) {
|
1441 | if (hasWeakSet) {
|
1442 | inner.delete(obj);
|
1443 | }
|
1444 | else {
|
1445 | for (let i = 0; i < inner.length; i++) {
|
1446 | if (inner[i] === obj) {
|
1447 | inner.splice(i, 1);
|
1448 | break;
|
1449 | }
|
1450 | }
|
1451 | }
|
1452 | }
|
1453 | return [memoize, unmemoize];
|
1454 | }
|
1455 |
|
1456 | /**
|
1457 | * UUID4 generator
|
1458 | *
|
1459 | * @returns string Generated UUID4.
|
1460 | */
|
1461 | function uuid4() {
|
1462 | const global = getGlobalObject();
|
1463 | const crypto = global.crypto || global.msCrypto;
|
1464 | if (!(crypto === void 0) && crypto.getRandomValues) {
|
1465 | // Use window.crypto API if available
|
1466 | const arr = new Uint16Array(8);
|
1467 | crypto.getRandomValues(arr);
|
1468 | // set 4 in byte 7
|
1469 | // eslint-disable-next-line no-bitwise
|
1470 | arr[3] = (arr[3] & 0xfff) | 0x4000;
|
1471 | // set 2 most significant bits of byte 9 to '10'
|
1472 | // eslint-disable-next-line no-bitwise
|
1473 | arr[4] = (arr[4] & 0x3fff) | 0x8000;
|
1474 | const pad = (num) => {
|
1475 | let v = num.toString(16);
|
1476 | while (v.length < 4) {
|
1477 | v = `0${v}`;
|
1478 | }
|
1479 | return v;
|
1480 | };
|
1481 | return (pad(arr[0]) + pad(arr[1]) + pad(arr[2]) + pad(arr[3]) + pad(arr[4]) + pad(arr[5]) + pad(arr[6]) + pad(arr[7]));
|
1482 | }
|
1483 | // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
|
1484 | return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
1485 | // eslint-disable-next-line no-bitwise
|
1486 | const r = (Math.random() * 16) | 0;
|
1487 | // eslint-disable-next-line no-bitwise
|
1488 | const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
1489 | return v.toString(16);
|
1490 | });
|
1491 | }
|
1492 | /**
|
1493 | * Parses string form of URL into an object
|
1494 | * // borrowed from https://tools.ietf.org/html/rfc3986#appendix-B
|
1495 | * // intentionally using regex and not <a/> href parsing trick because React Native and other
|
1496 | * // environments where DOM might not be available
|
1497 | * @returns parsed URL object
|
1498 | */
|
1499 | function parseUrl(url) {
|
1500 | if (!url) {
|
1501 | return {};
|
1502 | }
|
1503 | const match = url.match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/);
|
1504 | if (!match) {
|
1505 | return {};
|
1506 | }
|
1507 | // coerce to undefined values to empty string so we don't get 'undefined'
|
1508 | const query = match[6] || '';
|
1509 | const fragment = match[8] || '';
|
1510 | return {
|
1511 | host: match[4],
|
1512 | path: match[5],
|
1513 | protocol: match[2],
|
1514 | relative: match[5] + query + fragment,
|
1515 | };
|
1516 | }
|
1517 | function getFirstException(event) {
|
1518 | return event.exception && event.exception.values ? event.exception.values[0] : undefined;
|
1519 | }
|
1520 | /**
|
1521 | * Extracts either message or type+value from an event that can be used for user-facing logs
|
1522 | * @returns event's description
|
1523 | */
|
1524 | function getEventDescription(event) {
|
1525 | const { message, event_id: eventId } = event;
|
1526 | if (message) {
|
1527 | return message;
|
1528 | }
|
1529 | const firstException = getFirstException(event);
|
1530 | if (firstException) {
|
1531 | if (firstException.type && firstException.value) {
|
1532 | return `${firstException.type}: ${firstException.value}`;
|
1533 | }
|
1534 | return firstException.type || firstException.value || eventId || '<unknown>';
|
1535 | }
|
1536 | return eventId || '<unknown>';
|
1537 | }
|
1538 | /**
|
1539 | * Adds exception values, type and value to an synthetic Exception.
|
1540 | * @param event The event to modify.
|
1541 | * @param value Value of the exception.
|
1542 | * @param type Type of the exception.
|
1543 | * @hidden
|
1544 | */
|
1545 | function addExceptionTypeValue(event, value, type) {
|
1546 | const exception = (event.exception = event.exception || {});
|
1547 | const values = (exception.values = exception.values || []);
|
1548 | const firstException = (values[0] = values[0] || {});
|
1549 | if (!firstException.value) {
|
1550 | firstException.value = value || '';
|
1551 | }
|
1552 | if (!firstException.type) {
|
1553 | firstException.type = type || 'Error';
|
1554 | }
|
1555 | }
|
1556 | /**
|
1557 | * Adds exception mechanism data to a given event. Uses defaults if the second parameter is not passed.
|
1558 | *
|
1559 | * @param event The event to modify.
|
1560 | * @param newMechanism Mechanism data to add to the event.
|
1561 | * @hidden
|
1562 | */
|
1563 | function addExceptionMechanism(event, newMechanism) {
|
1564 | const firstException = getFirstException(event);
|
1565 | if (!firstException) {
|
1566 | return;
|
1567 | }
|
1568 | const defaultMechanism = { type: 'generic', handled: true };
|
1569 | const currentMechanism = firstException.mechanism;
|
1570 | firstException.mechanism = Object.assign(Object.assign(Object.assign({}, defaultMechanism), currentMechanism), newMechanism);
|
1571 | if (newMechanism && 'data' in newMechanism) {
|
1572 | const mergedData = Object.assign(Object.assign({}, (currentMechanism && currentMechanism.data)), newMechanism.data);
|
1573 | firstException.mechanism.data = mergedData;
|
1574 | }
|
1575 | }
|
1576 | /**
|
1577 | * Checks whether or not we've already captured the given exception (note: not an identical exception - the very object
|
1578 | * in question), and marks it captured if not.
|
1579 | *
|
1580 | * This is useful because it's possible for an error to get captured by more than one mechanism. After we intercept and
|
1581 | * record an error, we rethrow it (assuming we've intercepted it before it's reached the top-level global handlers), so
|
1582 | * that we don't interfere with whatever effects the error might have had were the SDK not there. At that point, because
|
1583 | * the error has been rethrown, it's possible for it to bubble up to some other code we've instrumented. If it's not
|
1584 | * caught after that, it will bubble all the way up to the global handlers (which of course we also instrument). This
|
1585 | * function helps us ensure that even if we encounter the same error more than once, we only record it the first time we
|
1586 | * see it.
|
1587 | *
|
1588 | * Note: It will ignore primitives (always return `false` and not mark them as seen), as properties can't be set on
|
1589 | * them. {@link: Object.objectify} can be used on exceptions to convert any that are primitives into their equivalent
|
1590 | * object wrapper forms so that this check will always work. However, because we need to flag the exact object which
|
1591 | * will get rethrown, and because that rethrowing happens outside of the event processing pipeline, the objectification
|
1592 | * must be done before the exception captured.
|
1593 | *
|
1594 | * @param A thrown exception to check or flag as having been seen
|
1595 | * @returns `true` if the exception has already been captured, `false` if not (with the side effect of marking it seen)
|
1596 | */
|
1597 | function checkOrSetAlreadyCaught(exception) {
|
1598 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
1599 | if (exception && exception.__sentry_captured__) {
|
1600 | return true;
|
1601 | }
|
1602 | try {
|
1603 | // set it this way rather than by assignment so that it's not ennumerable and therefore isn't recorded by the
|
1604 | // `ExtraErrorData` integration
|
1605 | addNonEnumerableProperty(exception, '__sentry_captured__', true);
|
1606 | }
|
1607 | catch (err) {
|
1608 | // `exception` is a primitive, so we can't mark it seen
|
1609 | }
|
1610 | return false;
|
1611 | }
|
1612 |
|
1613 | /**
|
1614 | * Recursively normalizes the given object.
|
1615 | *
|
1616 | * - Creates a copy to prevent original input mutation
|
1617 | * - Skips non-enumerable properties
|
1618 | * - When stringifying, calls `toJSON` if implemented
|
1619 | * - Removes circular references
|
1620 | * - Translates non-serializable values (`undefined`/`NaN`/functions) to serializable format
|
1621 | * - Translates known global objects/classes to a string representations
|
1622 | * - Takes care of `Error` object serialization
|
1623 | * - Optionally limits depth of final output
|
1624 | * - Optionally limits number of properties/elements included in any single object/array
|
1625 | *
|
1626 | * @param input The object to be normalized.
|
1627 | * @param depth The max depth to which to normalize the object. (Anything deeper stringified whole.)
|
1628 | * @param maxProperties The max number of elements or properties to be included in any single array or
|
1629 | * object in the normallized output..
|
1630 | * @returns A normalized version of the object, or `"**non-serializable**"` if any errors are thrown during normalization.
|
1631 | */
|
1632 | function normalize(input, depth = +Infinity, maxProperties = +Infinity) {
|
1633 | try {
|
1634 | // since we're at the outermost level, there is no key
|
1635 | return visit('', input, depth, maxProperties);
|
1636 | }
|
1637 | catch (err) {
|
1638 | return { ERROR: `**non-serializable** (${err})` };
|
1639 | }
|
1640 | }
|
1641 | /** JSDoc */
|
1642 | function normalizeToSize(object,
|
1643 | // Default Node.js REPL depth
|
1644 | depth = 3,
|
1645 | // 100kB, as 200kB is max payload size, so half sounds reasonable
|
1646 | maxSize = 100 * 1024) {
|
1647 | const normalized = normalize(object, depth);
|
1648 | if (jsonSize(normalized) > maxSize) {
|
1649 | return normalizeToSize(object, depth - 1, maxSize);
|
1650 | }
|
1651 | return normalized;
|
1652 | }
|
1653 | /**
|
1654 | * Visits a node to perform normalization on it
|
1655 | *
|
1656 | * @param key The key corresponding to the given node
|
1657 | * @param value The node to be visited
|
1658 | * @param depth Optional number indicating the maximum recursion depth
|
1659 | * @param maxProperties Optional maximum number of properties/elements included in any single object/array
|
1660 | * @param memo Optional Memo class handling decycling
|
1661 | */
|
1662 | function visit(key, value, depth = +Infinity, maxProperties = +Infinity, memo = memoBuilder()) {
|
1663 | const [memoize, unmemoize] = memo;
|
1664 | // If the value has a `toJSON` method, see if we can bail and let it do the work
|
1665 | const valueWithToJSON = value;
|
1666 | if (valueWithToJSON && typeof valueWithToJSON.toJSON === 'function') {
|
1667 | try {
|
1668 | return valueWithToJSON.toJSON();
|
1669 | }
|
1670 | catch (err) {
|
1671 | // pass (The built-in `toJSON` failed, but we can still try to do it ourselves)
|
1672 | }
|
1673 | }
|
1674 | // Get the simple cases out of the way first
|
1675 | if (value === null || (['number', 'boolean', 'string'].includes(typeof value) && !isNaN$1(value))) {
|
1676 | return value;
|
1677 | }
|
1678 | const stringified = stringifyValue(key, value);
|
1679 | // Anything we could potentially dig into more (objects or arrays) will have come back as `"[object XXXX]"`.
|
1680 | // Everything else will have already been serialized, so if we don't see that pattern, we're done.
|
1681 | if (!stringified.startsWith('[object ')) {
|
1682 | return stringified;
|
1683 | }
|
1684 | // We're also done if we've reached the max depth
|
1685 | if (depth === 0) {
|
1686 | // At this point we know `serialized` is a string of the form `"[object XXXX]"`. Clean it up so it's just `"[XXXX]"`.
|
1687 | return stringified.replace('object ', '');
|
1688 | }
|
1689 | // If we've already visited this branch, bail out, as it's circular reference. If not, note that we're seeing it now.
|
1690 | if (memoize(value)) {
|
1691 | return '[Circular ~]';
|
1692 | }
|
1693 | // At this point we know we either have an object or an array, we haven't seen it before, and we're going to recurse
|
1694 | // because we haven't yet reached the max depth. Create an accumulator to hold the results of visiting each
|
1695 | // property/entry, and keep track of the number of items we add to it.
|
1696 | const normalized = (Array.isArray(value) ? [] : {});
|
1697 | let numAdded = 0;
|
1698 | // Before we begin, convert`Error` and`Event` instances into plain objects, since some of each of their relevant
|
1699 | // properties are non-enumerable and otherwise would get missed.
|
1700 | const visitable = (isError(value) || isEvent(value) ? convertToPlainObject(value) : value);
|
1701 | for (const visitKey in visitable) {
|
1702 | // Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration.
|
1703 | if (!Object.prototype.hasOwnProperty.call(visitable, visitKey)) {
|
1704 | continue;
|
1705 | }
|
1706 | if (numAdded >= maxProperties) {
|
1707 | normalized[visitKey] = '[MaxProperties ~]';
|
1708 | break;
|
1709 | }
|
1710 | // Recursively visit all the child nodes
|
1711 | const visitValue = visitable[visitKey];
|
1712 | normalized[visitKey] = visit(visitKey, visitValue, depth - 1, maxProperties, memo);
|
1713 | numAdded += 1;
|
1714 | }
|
1715 | // Once we've visited all the branches, remove the parent from memo storage
|
1716 | unmemoize(value);
|
1717 | // Return accumulated values
|
1718 | return normalized;
|
1719 | }
|
1720 | /**
|
1721 | * Stringify the given value. Handles various known special values and types.
|
1722 | *
|
1723 | * Not meant to be used on simple primitives which already have a string representation, as it will, for example, turn
|
1724 | * the number 1231 into "[Object Number]", nor on `null`, as it will throw.
|
1725 | *
|
1726 | * @param value The value to stringify
|
1727 | * @returns A stringified representation of the given value
|
1728 | */
|
1729 | function stringifyValue(key,
|
1730 | // this type is a tiny bit of a cheat, since this function does handle NaN (which is technically a number), but for
|
1731 | // our internal use, it'll do
|
1732 | value) {
|
1733 | try {
|
1734 | if (key === 'domain' && value && typeof value === 'object' && value._events) {
|
1735 | return '[Domain]';
|
1736 | }
|
1737 | if (key === 'domainEmitter') {
|
1738 | return '[DomainEmitter]';
|
1739 | }
|
1740 | // It's safe to use `global`, `window`, and `document` here in this manner, as we are asserting using `typeof` first
|
1741 | // which won't throw if they are not present.
|
1742 | if (typeof global !== 'undefined' && value === global) {
|
1743 | return '[Global]';
|
1744 | }
|
1745 | // eslint-disable-next-line no-restricted-globals
|
1746 | if (typeof window !== 'undefined' && value === window) {
|
1747 | return '[Window]';
|
1748 | }
|
1749 | // eslint-disable-next-line no-restricted-globals
|
1750 | if (typeof document !== 'undefined' && value === document) {
|
1751 | return '[Document]';
|
1752 | }
|
1753 | // React's SyntheticEvent thingy
|
1754 | if (isSyntheticEvent(value)) {
|
1755 | return '[SyntheticEvent]';
|
1756 | }
|
1757 | if (typeof value === 'number' && value !== value) {
|
1758 | return '[NaN]';
|
1759 | }
|
1760 | // this catches `undefined` (but not `null`, which is a primitive and can be serialized on its own)
|
1761 | if (value === void 0) {
|
1762 | return '[undefined]';
|
1763 | }
|
1764 | if (typeof value === 'function') {
|
1765 | return `[Function: ${getFunctionName(value)}]`;
|
1766 | }
|
1767 | if (typeof value === 'symbol') {
|
1768 | return `[${String(value)}]`;
|
1769 | }
|
1770 | // stringified BigInts are indistinguishable from regular numbers, so we need to label them to avoid confusion
|
1771 | if (typeof value === 'bigint') {
|
1772 | return `[BigInt: ${String(value)}]`;
|
1773 | }
|
1774 | // Now that we've knocked out all the special cases and the primitives, all we have left are objects. Simply casting
|
1775 | // them to strings means that instances of classes which haven't defined their `toStringTag` will just come out as
|
1776 | // `"[object Object]"`. If we instead look at the constructor's name (which is the same as the name of the class),
|
1777 | // we can make sure that only plain objects come out that way.
|
1778 | return `[object ${Object.getPrototypeOf(value).constructor.name}]`;
|
1779 | }
|
1780 | catch (err) {
|
1781 | return `**non-serializable** (${err})`;
|
1782 | }
|
1783 | }
|
1784 | /** Calculates bytes size of input string */
|
1785 | function utf8Length(value) {
|
1786 | // eslint-disable-next-line no-bitwise
|
1787 | return ~-encodeURI(value).split(/%..|./).length;
|
1788 | }
|
1789 | /** Calculates bytes size of input object */
|
1790 | function jsonSize(value) {
|
1791 | return utf8Length(JSON.stringify(value));
|
1792 | }
|
1793 |
|
1794 | /* eslint-disable @typescript-eslint/explicit-function-return-type */
|
1795 | /**
|
1796 | * Creates a resolved sync promise.
|
1797 | *
|
1798 | * @param value the value to resolve the promise with
|
1799 | * @returns the resolved sync promise
|
1800 | */
|
1801 | function resolvedSyncPromise(value) {
|
1802 | return new SyncPromise(resolve => {
|
1803 | resolve(value);
|
1804 | });
|
1805 | }
|
1806 | /**
|
1807 | * Creates a rejected sync promise.
|
1808 | *
|
1809 | * @param value the value to reject the promise with
|
1810 | * @returns the rejected sync promise
|
1811 | */
|
1812 | function rejectedSyncPromise(reason) {
|
1813 | return new SyncPromise((_, reject) => {
|
1814 | reject(reason);
|
1815 | });
|
1816 | }
|
1817 | /**
|
1818 | * Thenable class that behaves like a Promise and follows it's interface
|
1819 | * but is not async internally
|
1820 | */
|
1821 | class SyncPromise {
|
1822 | constructor(executor) {
|
1823 | this._state = 0 /* PENDING */;
|
1824 | this._handlers = [];
|
1825 | /** JSDoc */
|
1826 | this._resolve = (value) => {
|
1827 | this._setResult(1 /* RESOLVED */, value);
|
1828 | };
|
1829 | /** JSDoc */
|
1830 | this._reject = (reason) => {
|
1831 | this._setResult(2 /* REJECTED */, reason);
|
1832 | };
|
1833 | /** JSDoc */
|
1834 | this._setResult = (state, value) => {
|
1835 | if (this._state !== 0 /* PENDING */) {
|
1836 | return;
|
1837 | }
|
1838 | if (isThenable(value)) {
|
1839 | void value.then(this._resolve, this._reject);
|
1840 | return;
|
1841 | }
|
1842 | this._state = state;
|
1843 | this._value = value;
|
1844 | this._executeHandlers();
|
1845 | };
|
1846 | /** JSDoc */
|
1847 | this._executeHandlers = () => {
|
1848 | if (this._state === 0 /* PENDING */) {
|
1849 | return;
|
1850 | }
|
1851 | const cachedHandlers = this._handlers.slice();
|
1852 | this._handlers = [];
|
1853 | cachedHandlers.forEach(handler => {
|
1854 | if (handler[0]) {
|
1855 | return;
|
1856 | }
|
1857 | if (this._state === 1 /* RESOLVED */) {
|
1858 | // eslint-disable-next-line @typescript-eslint/no-floating-promises
|
1859 | handler[1](this._value);
|
1860 | }
|
1861 | if (this._state === 2 /* REJECTED */) {
|
1862 | handler[2](this._value);
|
1863 | }
|
1864 | handler[0] = true;
|
1865 | });
|
1866 | };
|
1867 | try {
|
1868 | executor(this._resolve, this._reject);
|
1869 | }
|
1870 | catch (e) {
|
1871 | this._reject(e);
|
1872 | }
|
1873 | }
|
1874 | /** JSDoc */
|
1875 | then(onfulfilled, onrejected) {
|
1876 | return new SyncPromise((resolve, reject) => {
|
1877 | this._handlers.push([
|
1878 | false,
|
1879 | result => {
|
1880 | if (!onfulfilled) {
|
1881 | // TODO: ¯\_(ツ)_/¯
|
1882 | // TODO: FIXME
|
1883 | resolve(result);
|
1884 | }
|
1885 | else {
|
1886 | try {
|
1887 | resolve(onfulfilled(result));
|
1888 | }
|
1889 | catch (e) {
|
1890 | reject(e);
|
1891 | }
|
1892 | }
|
1893 | },
|
1894 | reason => {
|
1895 | if (!onrejected) {
|
1896 | reject(reason);
|
1897 | }
|
1898 | else {
|
1899 | try {
|
1900 | resolve(onrejected(reason));
|
1901 | }
|
1902 | catch (e) {
|
1903 | reject(e);
|
1904 | }
|
1905 | }
|
1906 | },
|
1907 | ]);
|
1908 | this._executeHandlers();
|
1909 | });
|
1910 | }
|
1911 | /** JSDoc */
|
1912 | catch(onrejected) {
|
1913 | return this.then(val => val, onrejected);
|
1914 | }
|
1915 | /** JSDoc */
|
1916 | finally(onfinally) {
|
1917 | return new SyncPromise((resolve, reject) => {
|
1918 | let val;
|
1919 | let isRejected;
|
1920 | return this.then(value => {
|
1921 | isRejected = false;
|
1922 | val = value;
|
1923 | if (onfinally) {
|
1924 | onfinally();
|
1925 | }
|
1926 | }, reason => {
|
1927 | isRejected = true;
|
1928 | val = reason;
|
1929 | if (onfinally) {
|
1930 | onfinally();
|
1931 | }
|
1932 | }).then(() => {
|
1933 | if (isRejected) {
|
1934 | reject(val);
|
1935 | return;
|
1936 | }
|
1937 | resolve(val);
|
1938 | });
|
1939 | });
|
1940 | }
|
1941 | }
|
1942 |
|
1943 | /**
|
1944 | * Creates an new PromiseBuffer object with the specified limit
|
1945 | * @param limit max number of promises that can be stored in the buffer
|
1946 | */
|
1947 | function makePromiseBuffer(limit) {
|
1948 | const buffer = [];
|
1949 | function isReady() {
|
1950 | return limit === undefined || buffer.length < limit;
|
1951 | }
|
1952 | /**
|
1953 | * Remove a promise from the queue.
|
1954 | *
|
1955 | * @param task Can be any PromiseLike<T>
|
1956 | * @returns Removed promise.
|
1957 | */
|
1958 | function remove(task) {
|
1959 | return buffer.splice(buffer.indexOf(task), 1)[0];
|
1960 | }
|
1961 | /**
|
1962 | * Add a promise (representing an in-flight action) to the queue, and set it to remove itself on fulfillment.
|
1963 | *
|
1964 | * @param taskProducer A function producing any PromiseLike<T>; In previous versions this used to be `task:
|
1965 | * PromiseLike<T>`, but under that model, Promises were instantly created on the call-site and their executor
|
1966 | * functions therefore ran immediately. Thus, even if the buffer was full, the action still happened. By
|
1967 | * requiring the promise to be wrapped in a function, we can defer promise creation until after the buffer
|
1968 | * limit check.
|
1969 | * @returns The original promise.
|
1970 | */
|
1971 | function add(taskProducer) {
|
1972 | if (!isReady()) {
|
1973 | return rejectedSyncPromise(new SentryError('Not adding Promise due to buffer limit reached.'));
|
1974 | }
|
1975 | // start the task and add its promise to the queue
|
1976 | const task = taskProducer();
|
1977 | if (buffer.indexOf(task) === -1) {
|
1978 | buffer.push(task);
|
1979 | }
|
1980 | void task
|
1981 | .then(() => remove(task))
|
1982 | // Use `then(null, rejectionHandler)` rather than `catch(rejectionHandler)` so that we can use `PromiseLike`
|
1983 | // rather than `Promise`. `PromiseLike` doesn't have a `.catch` method, making its polyfill smaller. (ES5 didn't
|
1984 | // have promises, so TS has to polyfill when down-compiling.)
|
1985 | .then(null, () => remove(task).then(null, () => {
|
1986 | // We have to add another catch here because `remove()` starts a new promise chain.
|
1987 | }));
|
1988 | return task;
|
1989 | }
|
1990 | /**
|
1991 | * Wait for all promises in the queue to resolve or for timeout to expire, whichever comes first.
|
1992 | *
|
1993 | * @param timeout The time, in ms, after which to resolve to `false` if the queue is still non-empty. Passing `0` (or
|
1994 | * not passing anything) will make the promise wait as long as it takes for the queue to drain before resolving to
|
1995 | * `true`.
|
1996 | * @returns A promise which will resolve to `true` if the queue is already empty or drains before the timeout, and
|
1997 | * `false` otherwise
|
1998 | */
|
1999 | function drain(timeout) {
|
2000 | return new SyncPromise((resolve, reject) => {
|
2001 | let counter = buffer.length;
|
2002 | if (!counter) {
|
2003 | return resolve(true);
|
2004 | }
|
2005 | // wait for `timeout` ms and then resolve to `false` (if not cancelled first)
|
2006 | const capturedSetTimeout = setTimeout(() => {
|
2007 | if (timeout && timeout > 0) {
|
2008 | resolve(false);
|
2009 | }
|
2010 | }, timeout);
|
2011 | // if all promises resolve in time, cancel the timer and resolve to `true`
|
2012 | buffer.forEach(item => {
|
2013 | void resolvedSyncPromise(item).then(() => {
|
2014 | // eslint-disable-next-line no-plusplus
|
2015 | if (!--counter) {
|
2016 | clearTimeout(capturedSetTimeout);
|
2017 | resolve(true);
|
2018 | }
|
2019 | }, reject);
|
2020 | });
|
2021 | });
|
2022 | }
|
2023 | return {
|
2024 | $: buffer,
|
2025 | add,
|
2026 | drain,
|
2027 | };
|
2028 | }
|
2029 |
|
2030 | function isSupportedSeverity(level) {
|
2031 | return SeverityLevels.indexOf(level) !== -1;
|
2032 | }
|
2033 | /**
|
2034 | * Converts a string-based level into a {@link Severity}.
|
2035 | *
|
2036 | * @param level string representation of Severity
|
2037 | * @returns Severity
|
2038 | */
|
2039 | function severityFromString(level) {
|
2040 | if (level === 'warn')
|
2041 | return exports.Severity.Warning;
|
2042 | if (isSupportedSeverity(level)) {
|
2043 | return level;
|
2044 | }
|
2045 | return exports.Severity.Log;
|
2046 | }
|
2047 |
|
2048 | /**
|
2049 | * Converts an HTTP status code to sentry status {@link EventStatus}.
|
2050 | *
|
2051 | * @param code number HTTP status code
|
2052 | * @returns EventStatus
|
2053 | */
|
2054 | function eventStatusFromHttpCode(code) {
|
2055 | if (code >= 200 && code < 300) {
|
2056 | return 'success';
|
2057 | }
|
2058 | if (code === 429) {
|
2059 | return 'rate_limit';
|
2060 | }
|
2061 | if (code >= 400 && code < 500) {
|
2062 | return 'invalid';
|
2063 | }
|
2064 | if (code >= 500) {
|
2065 | return 'failed';
|
2066 | }
|
2067 | return 'unknown';
|
2068 | }
|
2069 |
|
2070 | /**
|
2071 | * A TimestampSource implementation for environments that do not support the Performance Web API natively.
|
2072 | *
|
2073 | * Note that this TimestampSource does not use a monotonic clock. A call to `nowSeconds` may return a timestamp earlier
|
2074 | * than a previously returned value. We do not try to emulate a monotonic behavior in order to facilitate debugging. It
|
2075 | * is more obvious to explain "why does my span have negative duration" than "why my spans have zero duration".
|
2076 | */
|
2077 | const dateTimestampSource = {
|
2078 | nowSeconds: () => Date.now() / 1000,
|
2079 | };
|
2080 | /**
|
2081 | * Returns a wrapper around the native Performance API browser implementation, or undefined for browsers that do not
|
2082 | * support the API.
|
2083 | *
|
2084 | * Wrapping the native API works around differences in behavior from different browsers.
|
2085 | */
|
2086 | function getBrowserPerformance() {
|
2087 | const { performance } = getGlobalObject();
|
2088 | if (!performance || !performance.now) {
|
2089 | return undefined;
|
2090 | }
|
2091 | // Replace performance.timeOrigin with our own timeOrigin based on Date.now().
|
2092 | //
|
2093 | // This is a partial workaround for browsers reporting performance.timeOrigin such that performance.timeOrigin +
|
2094 | // performance.now() gives a date arbitrarily in the past.
|
2095 | //
|
2096 | // Additionally, computing timeOrigin in this way fills the gap for browsers where performance.timeOrigin is
|
2097 | // undefined.
|
2098 | //
|
2099 | // The assumption that performance.timeOrigin + performance.now() ~= Date.now() is flawed, but we depend on it to
|
2100 | // interact with data coming out of performance entries.
|
2101 | //
|
2102 | // Note that despite recommendations against it in the spec, browsers implement the Performance API with a clock that
|
2103 | // might stop when the computer is asleep (and perhaps under other circumstances). Such behavior causes
|
2104 | // performance.timeOrigin + performance.now() to have an arbitrary skew over Date.now(). In laptop computers, we have
|
2105 | // observed skews that can be as long as days, weeks or months.
|
2106 | //
|
2107 | // See https://github.com/getsentry/sentry-javascript/issues/2590.
|
2108 | //
|
2109 | // BUG: despite our best intentions, this workaround has its limitations. It mostly addresses timings of pageload
|
2110 | // transactions, but ignores the skew built up over time that can aversely affect timestamps of navigation
|
2111 | // transactions of long-lived web pages.
|
2112 | const timeOrigin = Date.now() - performance.now();
|
2113 | return {
|
2114 | now: () => performance.now(),
|
2115 | timeOrigin,
|
2116 | };
|
2117 | }
|
2118 | /**
|
2119 | * The Performance API implementation for the current platform, if available.
|
2120 | */
|
2121 | const platformPerformance = getBrowserPerformance();
|
2122 | const timestampSource = platformPerformance === undefined
|
2123 | ? dateTimestampSource
|
2124 | : {
|
2125 | nowSeconds: () => (platformPerformance.timeOrigin + platformPerformance.now()) / 1000,
|
2126 | };
|
2127 | /**
|
2128 | * Returns a timestamp in seconds since the UNIX epoch using the Date API.
|
2129 | */
|
2130 | const dateTimestampInSeconds = dateTimestampSource.nowSeconds.bind(dateTimestampSource);
|
2131 | /**
|
2132 | * Returns a timestamp in seconds since the UNIX epoch using either the Performance or Date APIs, depending on the
|
2133 | * availability of the Performance API.
|
2134 | *
|
2135 | * See `usingPerformanceAPI` to test whether the Performance API is used.
|
2136 | *
|
2137 | * BUG: Note that because of how browsers implement the Performance API, the clock might stop when the computer is
|
2138 | * asleep. This creates a skew between `dateTimestampInSeconds` and `timestampInSeconds`. The
|
2139 | * skew can grow to arbitrary amounts like days, weeks or months.
|
2140 | * See https://github.com/getsentry/sentry-javascript/issues/2590.
|
2141 | */
|
2142 | const timestampInSeconds = timestampSource.nowSeconds.bind(timestampSource);
|
2143 | /**
|
2144 | * The number of milliseconds since the UNIX epoch. This value is only usable in a browser, and only when the
|
2145 | * performance API is available.
|
2146 | */
|
2147 | (() => {
|
2148 | // Unfortunately browsers may report an inaccurate time origin data, through either performance.timeOrigin or
|
2149 | // performance.timing.navigationStart, which results in poor results in performance data. We only treat time origin
|
2150 | // data as reliable if they are within a reasonable threshold of the current time.
|
2151 | const { performance } = getGlobalObject();
|
2152 | if (!performance || !performance.now) {
|
2153 | return undefined;
|
2154 | }
|
2155 | const threshold = 3600 * 1000;
|
2156 | const performanceNow = performance.now();
|
2157 | const dateNow = Date.now();
|
2158 | // if timeOrigin isn't available set delta to threshold so it isn't used
|
2159 | const timeOriginDelta = performance.timeOrigin
|
2160 | ? Math.abs(performance.timeOrigin + performanceNow - dateNow)
|
2161 | : threshold;
|
2162 | const timeOriginIsReliable = timeOriginDelta < threshold;
|
2163 | // While performance.timing.navigationStart is deprecated in favor of performance.timeOrigin, performance.timeOrigin
|
2164 | // is not as widely supported. Namely, performance.timeOrigin is undefined in Safari as of writing.
|
2165 | // Also as of writing, performance.timing is not available in Web Workers in mainstream browsers, so it is not always
|
2166 | // a valid fallback. In the absence of an initial time provided by the browser, fallback to the current time from the
|
2167 | // Date API.
|
2168 | // eslint-disable-next-line deprecation/deprecation
|
2169 | const navigationStart = performance.timing && performance.timing.navigationStart;
|
2170 | const hasNavigationStart = typeof navigationStart === 'number';
|
2171 | // if navigationStart isn't available set delta to threshold so it isn't used
|
2172 | const navigationStartDelta = hasNavigationStart ? Math.abs(navigationStart + performanceNow - dateNow) : threshold;
|
2173 | const navigationStartIsReliable = navigationStartDelta < threshold;
|
2174 | if (timeOriginIsReliable || navigationStartIsReliable) {
|
2175 | // Use the more reliable time origin
|
2176 | if (timeOriginDelta <= navigationStartDelta) {
|
2177 | return performance.timeOrigin;
|
2178 | }
|
2179 | else {
|
2180 | return navigationStart;
|
2181 | }
|
2182 | }
|
2183 | return dateNow;
|
2184 | })();
|
2185 |
|
2186 | /**
|
2187 | * Creates an envelope.
|
2188 | * Make sure to always explicitly provide the generic to this function
|
2189 | * so that the envelope types resolve correctly.
|
2190 | */
|
2191 | function createEnvelope(headers, items = []) {
|
2192 | return [headers, items];
|
2193 | }
|
2194 | /**
|
2195 | * Get the type of the envelope. Grabs the type from the first envelope item.
|
2196 | */
|
2197 | function getEnvelopeType(envelope) {
|
2198 | const [, [[firstItemHeader]]] = envelope;
|
2199 | return firstItemHeader.type;
|
2200 | }
|
2201 | /**
|
2202 | * Serializes an envelope into a string.
|
2203 | */
|
2204 | function serializeEnvelope(envelope) {
|
2205 | const [headers, items] = envelope;
|
2206 | const serializedHeaders = JSON.stringify(headers);
|
2207 | // Have to cast items to any here since Envelope is a union type
|
2208 | // Fixed in Typescript 4.2
|
2209 | // TODO: Remove any[] cast when we upgrade to TS 4.2
|
2210 | // https://github.com/microsoft/TypeScript/issues/36390
|
2211 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
2212 | return items.reduce((acc, item) => {
|
2213 | const [itemHeaders, payload] = item;
|
2214 | // We do not serialize payloads that are primitives
|
2215 | const serializedPayload = isPrimitive(payload) ? String(payload) : JSON.stringify(payload);
|
2216 | return `${acc}\n${JSON.stringify(itemHeaders)}\n${serializedPayload}`;
|
2217 | }, serializedHeaders);
|
2218 | }
|
2219 |
|
2220 | /**
|
2221 | * Creates client report envelope
|
2222 | * @param discarded_events An array of discard events
|
2223 | * @param dsn A DSN that can be set on the header. Optional.
|
2224 | */
|
2225 | function createClientReportEnvelope(discarded_events, dsn, timestamp) {
|
2226 | const clientReportItem = [
|
2227 | { type: 'client_report' },
|
2228 | {
|
2229 | timestamp: timestamp || dateTimestampInSeconds(),
|
2230 | discarded_events,
|
2231 | },
|
2232 | ];
|
2233 | return createEnvelope(dsn ? { dsn } : {}, [clientReportItem]);
|
2234 | }
|
2235 |
|
2236 | const DEFAULT_RETRY_AFTER = 60 * 1000; // 60 seconds
|
2237 | /**
|
2238 | * Extracts Retry-After value from the request header or returns default value
|
2239 | * @param header string representation of 'Retry-After' header
|
2240 | * @param now current unix timestamp
|
2241 | *
|
2242 | */
|
2243 | function parseRetryAfterHeader(header, now = Date.now()) {
|
2244 | const headerDelay = parseInt(`${header}`, 10);
|
2245 | if (!isNaN(headerDelay)) {
|
2246 | return headerDelay * 1000;
|
2247 | }
|
2248 | const headerDate = Date.parse(`${header}`);
|
2249 | if (!isNaN(headerDate)) {
|
2250 | return headerDate - now;
|
2251 | }
|
2252 | return DEFAULT_RETRY_AFTER;
|
2253 | }
|
2254 | /**
|
2255 | * Gets the time that given category is disabled until for rate limiting
|
2256 | */
|
2257 | function disabledUntil(limits, category) {
|
2258 | return limits[category] || limits.all || 0;
|
2259 | }
|
2260 | /**
|
2261 | * Checks if a category is rate limited
|
2262 | */
|
2263 | function isRateLimited(limits, category, now = Date.now()) {
|
2264 | return disabledUntil(limits, category) > now;
|
2265 | }
|
2266 | /**
|
2267 | * Update ratelimits from incoming headers.
|
2268 | * Returns true if headers contains a non-empty rate limiting header.
|
2269 | */
|
2270 | function updateRateLimits(limits, headers, now = Date.now()) {
|
2271 | const updatedRateLimits = Object.assign({}, limits);
|
2272 | // "The name is case-insensitive."
|
2273 | // https://developer.mozilla.org/en-US/docs/Web/API/Headers/get
|
2274 | const rateLimitHeader = headers['x-sentry-rate-limits'];
|
2275 | const retryAfterHeader = headers['retry-after'];
|
2276 | if (rateLimitHeader) {
|
2277 | /**
|
2278 | * rate limit headers are of the form
|
2279 | * <header>,<header>,..
|
2280 | * where each <header> is of the form
|
2281 | * <retry_after>: <categories>: <scope>: <reason_code>
|
2282 | * where
|
2283 | * <retry_after> is a delay in seconds
|
2284 | * <categories> is the event type(s) (error, transaction, etc) being rate limited and is of the form
|
2285 | * <category>;<category>;...
|
2286 | * <scope> is what's being limited (org, project, or key) - ignored by SDK
|
2287 | * <reason_code> is an arbitrary string like "org_quota" - ignored by SDK
|
2288 | */
|
2289 | for (const limit of rateLimitHeader.trim().split(',')) {
|
2290 | const parameters = limit.split(':', 2);
|
2291 | const headerDelay = parseInt(parameters[0], 10);
|
2292 | const delay = (!isNaN(headerDelay) ? headerDelay : 60) * 1000; // 60sec default
|
2293 | if (!parameters[1]) {
|
2294 | updatedRateLimits.all = now + delay;
|
2295 | }
|
2296 | else {
|
2297 | for (const category of parameters[1].split(';')) {
|
2298 | updatedRateLimits[category] = now + delay;
|
2299 | }
|
2300 | }
|
2301 | }
|
2302 | }
|
2303 | else if (retryAfterHeader) {
|
2304 | updatedRateLimits.all = now + parseRetryAfterHeader(retryAfterHeader, now);
|
2305 | }
|
2306 | return updatedRateLimits;
|
2307 | }
|
2308 |
|
2309 | /**
|
2310 | * Absolute maximum number of breadcrumbs added to an event.
|
2311 | * The `maxBreadcrumbs` option cannot be higher than this value.
|
2312 | */
|
2313 | const MAX_BREADCRUMBS = 100;
|
2314 | /**
|
2315 | * Holds additional event information. {@link Scope.applyToEvent} will be
|
2316 | * called by the client before an event will be sent.
|
2317 | */
|
2318 | class Scope {
|
2319 | constructor() {
|
2320 | /** Flag if notifying is happening. */
|
2321 | this._notifyingListeners = false;
|
2322 | /** Callback for client to receive scope changes. */
|
2323 | this._scopeListeners = [];
|
2324 | /** Callback list that will be called after {@link applyToEvent}. */
|
2325 | this._eventProcessors = [];
|
2326 | /** Array of breadcrumbs. */
|
2327 | this._breadcrumbs = [];
|
2328 | /** User */
|
2329 | this._user = {};
|
2330 | /** Tags */
|
2331 | this._tags = {};
|
2332 | /** Extra */
|
2333 | this._extra = {};
|
2334 | /** Contexts */
|
2335 | this._contexts = {};
|
2336 | /**
|
2337 | * A place to stash data which is needed at some point in the SDK's event processing pipeline but which shouldn't get
|
2338 | * sent to Sentry
|
2339 | */
|
2340 | this._sdkProcessingMetadata = {};
|
2341 | }
|
2342 | /**
|
2343 | * Inherit values from the parent scope.
|
2344 | * @param scope to clone.
|
2345 | */
|
2346 | static clone(scope) {
|
2347 | const newScope = new Scope();
|
2348 | if (scope) {
|
2349 | newScope._breadcrumbs = [...scope._breadcrumbs];
|
2350 | newScope._tags = Object.assign({}, scope._tags);
|
2351 | newScope._extra = Object.assign({}, scope._extra);
|
2352 | newScope._contexts = Object.assign({}, scope._contexts);
|
2353 | newScope._user = scope._user;
|
2354 | newScope._level = scope._level;
|
2355 | newScope._span = scope._span;
|
2356 | newScope._session = scope._session;
|
2357 | newScope._transactionName = scope._transactionName;
|
2358 | newScope._fingerprint = scope._fingerprint;
|
2359 | newScope._eventProcessors = [...scope._eventProcessors];
|
2360 | newScope._requestSession = scope._requestSession;
|
2361 | }
|
2362 | return newScope;
|
2363 | }
|
2364 | /**
|
2365 | * Add internal on change listener. Used for sub SDKs that need to store the scope.
|
2366 | * @hidden
|
2367 | */
|
2368 | addScopeListener(callback) {
|
2369 | this._scopeListeners.push(callback);
|
2370 | }
|
2371 | /**
|
2372 | * @inheritDoc
|
2373 | */
|
2374 | addEventProcessor(callback) {
|
2375 | this._eventProcessors.push(callback);
|
2376 | return this;
|
2377 | }
|
2378 | /**
|
2379 | * @inheritDoc
|
2380 | */
|
2381 | setUser(user) {
|
2382 | this._user = user || {};
|
2383 | if (this._session) {
|
2384 | this._session.update({ user });
|
2385 | }
|
2386 | this._notifyScopeListeners();
|
2387 | return this;
|
2388 | }
|
2389 | /**
|
2390 | * @inheritDoc
|
2391 | */
|
2392 | getUser() {
|
2393 | return this._user;
|
2394 | }
|
2395 | /**
|
2396 | * @inheritDoc
|
2397 | */
|
2398 | getRequestSession() {
|
2399 | return this._requestSession;
|
2400 | }
|
2401 | /**
|
2402 | * @inheritDoc
|
2403 | */
|
2404 | setRequestSession(requestSession) {
|
2405 | this._requestSession = requestSession;
|
2406 | return this;
|
2407 | }
|
2408 | /**
|
2409 | * @inheritDoc
|
2410 | */
|
2411 | setTags(tags) {
|
2412 | this._tags = Object.assign(Object.assign({}, this._tags), tags);
|
2413 | this._notifyScopeListeners();
|
2414 | return this;
|
2415 | }
|
2416 | /**
|
2417 | * @inheritDoc
|
2418 | */
|
2419 | setTag(key, value) {
|
2420 | this._tags = Object.assign(Object.assign({}, this._tags), { [key]: value });
|
2421 | this._notifyScopeListeners();
|
2422 | return this;
|
2423 | }
|
2424 | /**
|
2425 | * @inheritDoc
|
2426 | */
|
2427 | setExtras(extras) {
|
2428 | this._extra = Object.assign(Object.assign({}, this._extra), extras);
|
2429 | this._notifyScopeListeners();
|
2430 | return this;
|
2431 | }
|
2432 | /**
|
2433 | * @inheritDoc
|
2434 | */
|
2435 | setExtra(key, extra) {
|
2436 | this._extra = Object.assign(Object.assign({}, this._extra), { [key]: extra });
|
2437 | this._notifyScopeListeners();
|
2438 | return this;
|
2439 | }
|
2440 | /**
|
2441 | * @inheritDoc
|
2442 | */
|
2443 | setFingerprint(fingerprint) {
|
2444 | this._fingerprint = fingerprint;
|
2445 | this._notifyScopeListeners();
|
2446 | return this;
|
2447 | }
|
2448 | /**
|
2449 | * @inheritDoc
|
2450 | */
|
2451 | setLevel(level) {
|
2452 | this._level = level;
|
2453 | this._notifyScopeListeners();
|
2454 | return this;
|
2455 | }
|
2456 | /**
|
2457 | * @inheritDoc
|
2458 | */
|
2459 | setTransactionName(name) {
|
2460 | this._transactionName = name;
|
2461 | this._notifyScopeListeners();
|
2462 | return this;
|
2463 | }
|
2464 | /**
|
2465 | * Can be removed in major version.
|
2466 | * @deprecated in favor of {@link this.setTransactionName}
|
2467 | */
|
2468 | setTransaction(name) {
|
2469 | return this.setTransactionName(name);
|
2470 | }
|
2471 | /**
|
2472 | * @inheritDoc
|
2473 | */
|
2474 | setContext(key, context) {
|
2475 | if (context === null) {
|
2476 | // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
2477 | delete this._contexts[key];
|
2478 | }
|
2479 | else {
|
2480 | this._contexts = Object.assign(Object.assign({}, this._contexts), { [key]: context });
|
2481 | }
|
2482 | this._notifyScopeListeners();
|
2483 | return this;
|
2484 | }
|
2485 | /**
|
2486 | * @inheritDoc
|
2487 | */
|
2488 | setSpan(span) {
|
2489 | this._span = span;
|
2490 | this._notifyScopeListeners();
|
2491 | return this;
|
2492 | }
|
2493 | /**
|
2494 | * @inheritDoc
|
2495 | */
|
2496 | getSpan() {
|
2497 | return this._span;
|
2498 | }
|
2499 | /**
|
2500 | * @inheritDoc
|
2501 | */
|
2502 | getTransaction() {
|
2503 | // Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will
|
2504 | // have a pointer to the currently-active transaction.
|
2505 | const span = this.getSpan();
|
2506 | return span && span.transaction;
|
2507 | }
|
2508 | /**
|
2509 | * @inheritDoc
|
2510 | */
|
2511 | setSession(session) {
|
2512 | if (!session) {
|
2513 | delete this._session;
|
2514 | }
|
2515 | else {
|
2516 | this._session = session;
|
2517 | }
|
2518 | this._notifyScopeListeners();
|
2519 | return this;
|
2520 | }
|
2521 | /**
|
2522 | * @inheritDoc
|
2523 | */
|
2524 | getSession() {
|
2525 | return this._session;
|
2526 | }
|
2527 | /**
|
2528 | * @inheritDoc
|
2529 | */
|
2530 | update(captureContext) {
|
2531 | if (!captureContext) {
|
2532 | return this;
|
2533 | }
|
2534 | if (typeof captureContext === 'function') {
|
2535 | const updatedScope = captureContext(this);
|
2536 | return updatedScope instanceof Scope ? updatedScope : this;
|
2537 | }
|
2538 | if (captureContext instanceof Scope) {
|
2539 | this._tags = Object.assign(Object.assign({}, this._tags), captureContext._tags);
|
2540 | this._extra = Object.assign(Object.assign({}, this._extra), captureContext._extra);
|
2541 | this._contexts = Object.assign(Object.assign({}, this._contexts), captureContext._contexts);
|
2542 | if (captureContext._user && Object.keys(captureContext._user).length) {
|
2543 | this._user = captureContext._user;
|
2544 | }
|
2545 | if (captureContext._level) {
|
2546 | this._level = captureContext._level;
|
2547 | }
|
2548 | if (captureContext._fingerprint) {
|
2549 | this._fingerprint = captureContext._fingerprint;
|
2550 | }
|
2551 | if (captureContext._requestSession) {
|
2552 | this._requestSession = captureContext._requestSession;
|
2553 | }
|
2554 | }
|
2555 | else if (isPlainObject(captureContext)) {
|
2556 | // eslint-disable-next-line no-param-reassign
|
2557 | captureContext = captureContext;
|
2558 | this._tags = Object.assign(Object.assign({}, this._tags), captureContext.tags);
|
2559 | this._extra = Object.assign(Object.assign({}, this._extra), captureContext.extra);
|
2560 | this._contexts = Object.assign(Object.assign({}, this._contexts), captureContext.contexts);
|
2561 | if (captureContext.user) {
|
2562 | this._user = captureContext.user;
|
2563 | }
|
2564 | if (captureContext.level) {
|
2565 | this._level = captureContext.level;
|
2566 | }
|
2567 | if (captureContext.fingerprint) {
|
2568 | this._fingerprint = captureContext.fingerprint;
|
2569 | }
|
2570 | if (captureContext.requestSession) {
|
2571 | this._requestSession = captureContext.requestSession;
|
2572 | }
|
2573 | }
|
2574 | return this;
|
2575 | }
|
2576 | /**
|
2577 | * @inheritDoc
|
2578 | */
|
2579 | clear() {
|
2580 | this._breadcrumbs = [];
|
2581 | this._tags = {};
|
2582 | this._extra = {};
|
2583 | this._user = {};
|
2584 | this._contexts = {};
|
2585 | this._level = undefined;
|
2586 | this._transactionName = undefined;
|
2587 | this._fingerprint = undefined;
|
2588 | this._requestSession = undefined;
|
2589 | this._span = undefined;
|
2590 | this._session = undefined;
|
2591 | this._notifyScopeListeners();
|
2592 | return this;
|
2593 | }
|
2594 | /**
|
2595 | * @inheritDoc
|
2596 | */
|
2597 | addBreadcrumb(breadcrumb, maxBreadcrumbs) {
|
2598 | const maxCrumbs = typeof maxBreadcrumbs === 'number' ? Math.min(maxBreadcrumbs, MAX_BREADCRUMBS) : MAX_BREADCRUMBS;
|
2599 | // No data has been changed, so don't notify scope listeners
|
2600 | if (maxCrumbs <= 0) {
|
2601 | return this;
|
2602 | }
|
2603 | const mergedBreadcrumb = Object.assign({ timestamp: dateTimestampInSeconds() }, breadcrumb);
|
2604 | this._breadcrumbs = [...this._breadcrumbs, mergedBreadcrumb].slice(-maxCrumbs);
|
2605 | this._notifyScopeListeners();
|
2606 | return this;
|
2607 | }
|
2608 | /**
|
2609 | * @inheritDoc
|
2610 | */
|
2611 | clearBreadcrumbs() {
|
2612 | this._breadcrumbs = [];
|
2613 | this._notifyScopeListeners();
|
2614 | return this;
|
2615 | }
|
2616 | /**
|
2617 | * Applies the current context and fingerprint to the event.
|
2618 | * Note that breadcrumbs will be added by the client.
|
2619 | * Also if the event has already breadcrumbs on it, we do not merge them.
|
2620 | * @param event Event
|
2621 | * @param hint May contain additional information about the original exception.
|
2622 | * @hidden
|
2623 | */
|
2624 | applyToEvent(event, hint) {
|
2625 | if (this._extra && Object.keys(this._extra).length) {
|
2626 | event.extra = Object.assign(Object.assign({}, this._extra), event.extra);
|
2627 | }
|
2628 | if (this._tags && Object.keys(this._tags).length) {
|
2629 | event.tags = Object.assign(Object.assign({}, this._tags), event.tags);
|
2630 | }
|
2631 | if (this._user && Object.keys(this._user).length) {
|
2632 | event.user = Object.assign(Object.assign({}, this._user), event.user);
|
2633 | }
|
2634 | if (this._contexts && Object.keys(this._contexts).length) {
|
2635 | event.contexts = Object.assign(Object.assign({}, this._contexts), event.contexts);
|
2636 | }
|
2637 | if (this._level) {
|
2638 | event.level = this._level;
|
2639 | }
|
2640 | if (this._transactionName) {
|
2641 | event.transaction = this._transactionName;
|
2642 | }
|
2643 | // We want to set the trace context for normal events only if there isn't already
|
2644 | // a trace context on the event. There is a product feature in place where we link
|
2645 | // errors with transaction and it relies on that.
|
2646 | if (this._span) {
|
2647 | event.contexts = Object.assign({ trace: this._span.getTraceContext() }, event.contexts);
|
2648 | const transactionName = this._span.transaction && this._span.transaction.name;
|
2649 | if (transactionName) {
|
2650 | event.tags = Object.assign({ transaction: transactionName }, event.tags);
|
2651 | }
|
2652 | }
|
2653 | this._applyFingerprint(event);
|
2654 | event.breadcrumbs = [...(event.breadcrumbs || []), ...this._breadcrumbs];
|
2655 | event.breadcrumbs = event.breadcrumbs.length > 0 ? event.breadcrumbs : undefined;
|
2656 | event.sdkProcessingMetadata = this._sdkProcessingMetadata;
|
2657 | return this._notifyEventProcessors([...getGlobalEventProcessors(), ...this._eventProcessors], event, hint);
|
2658 | }
|
2659 | /**
|
2660 | * Add data which will be accessible during event processing but won't get sent to Sentry
|
2661 | */
|
2662 | setSDKProcessingMetadata(newData) {
|
2663 | this._sdkProcessingMetadata = Object.assign(Object.assign({}, this._sdkProcessingMetadata), newData);
|
2664 | return this;
|
2665 | }
|
2666 | /**
|
2667 | * This will be called after {@link applyToEvent} is finished.
|
2668 | */
|
2669 | _notifyEventProcessors(processors, event, hint, index = 0) {
|
2670 | return new SyncPromise((resolve, reject) => {
|
2671 | const processor = processors[index];
|
2672 | if (event === null || typeof processor !== 'function') {
|
2673 | resolve(event);
|
2674 | }
|
2675 | else {
|
2676 | const result = processor(Object.assign({}, event), hint);
|
2677 | if (isThenable(result)) {
|
2678 | void result
|
2679 | .then(final => this._notifyEventProcessors(processors, final, hint, index + 1).then(resolve))
|
2680 | .then(null, reject);
|
2681 | }
|
2682 | else {
|
2683 | void this._notifyEventProcessors(processors, result, hint, index + 1)
|
2684 | .then(resolve)
|
2685 | .then(null, reject);
|
2686 | }
|
2687 | }
|
2688 | });
|
2689 | }
|
2690 | /**
|
2691 | * This will be called on every set call.
|
2692 | */
|
2693 | _notifyScopeListeners() {
|
2694 | // We need this check for this._notifyingListeners to be able to work on scope during updates
|
2695 | // If this check is not here we'll produce endless recursion when something is done with the scope
|
2696 | // during the callback.
|
2697 | if (!this._notifyingListeners) {
|
2698 | this._notifyingListeners = true;
|
2699 | this._scopeListeners.forEach(callback => {
|
2700 | callback(this);
|
2701 | });
|
2702 | this._notifyingListeners = false;
|
2703 | }
|
2704 | }
|
2705 | /**
|
2706 | * Applies fingerprint from the scope to the event if there's one,
|
2707 | * uses message if there's one instead or get rid of empty fingerprint
|
2708 | */
|
2709 | _applyFingerprint(event) {
|
2710 | // Make sure it's an array first and we actually have something in place
|
2711 | event.fingerprint = event.fingerprint
|
2712 | ? Array.isArray(event.fingerprint)
|
2713 | ? event.fingerprint
|
2714 | : [event.fingerprint]
|
2715 | : [];
|
2716 | // If we have something on the scope, then merge it with event
|
2717 | if (this._fingerprint) {
|
2718 | event.fingerprint = event.fingerprint.concat(this._fingerprint);
|
2719 | }
|
2720 | // If we have no data at all, remove empty array default
|
2721 | if (event.fingerprint && !event.fingerprint.length) {
|
2722 | delete event.fingerprint;
|
2723 | }
|
2724 | }
|
2725 | }
|
2726 | /**
|
2727 | * Returns the global event processors.
|
2728 | */
|
2729 | function getGlobalEventProcessors() {
|
2730 | return getGlobalSingleton('globalEventProcessors', () => []);
|
2731 | }
|
2732 | /**
|
2733 | * Add a EventProcessor to be kept globally.
|
2734 | * @param callback EventProcessor to add
|
2735 | */
|
2736 | function addGlobalEventProcessor(callback) {
|
2737 | getGlobalEventProcessors().push(callback);
|
2738 | }
|
2739 |
|
2740 | /**
|
2741 | * @inheritdoc
|
2742 | */
|
2743 | class Session {
|
2744 | constructor(context) {
|
2745 | this.errors = 0;
|
2746 | this.sid = uuid4();
|
2747 | this.duration = 0;
|
2748 | this.status = 'ok';
|
2749 | this.init = true;
|
2750 | this.ignoreDuration = false;
|
2751 | // Both timestamp and started are in seconds since the UNIX epoch.
|
2752 | const startingTime = timestampInSeconds();
|
2753 | this.timestamp = startingTime;
|
2754 | this.started = startingTime;
|
2755 | if (context) {
|
2756 | this.update(context);
|
2757 | }
|
2758 | }
|
2759 | /** JSDoc */
|
2760 | // eslint-disable-next-line complexity
|
2761 | update(context = {}) {
|
2762 | if (context.user) {
|
2763 | if (!this.ipAddress && context.user.ip_address) {
|
2764 | this.ipAddress = context.user.ip_address;
|
2765 | }
|
2766 | if (!this.did && !context.did) {
|
2767 | this.did = context.user.id || context.user.email || context.user.username;
|
2768 | }
|
2769 | }
|
2770 | this.timestamp = context.timestamp || timestampInSeconds();
|
2771 | if (context.ignoreDuration) {
|
2772 | this.ignoreDuration = context.ignoreDuration;
|
2773 | }
|
2774 | if (context.sid) {
|
2775 | // Good enough uuid validation. — Kamil
|
2776 | this.sid = context.sid.length === 32 ? context.sid : uuid4();
|
2777 | }
|
2778 | if (context.init !== undefined) {
|
2779 | this.init = context.init;
|
2780 | }
|
2781 | if (!this.did && context.did) {
|
2782 | this.did = `${context.did}`;
|
2783 | }
|
2784 | if (typeof context.started === 'number') {
|
2785 | this.started = context.started;
|
2786 | }
|
2787 | if (this.ignoreDuration) {
|
2788 | this.duration = undefined;
|
2789 | }
|
2790 | else if (typeof context.duration === 'number') {
|
2791 | this.duration = context.duration;
|
2792 | }
|
2793 | else {
|
2794 | const duration = this.timestamp - this.started;
|
2795 | this.duration = duration >= 0 ? duration : 0;
|
2796 | }
|
2797 | if (context.release) {
|
2798 | this.release = context.release;
|
2799 | }
|
2800 | if (context.environment) {
|
2801 | this.environment = context.environment;
|
2802 | }
|
2803 | if (!this.ipAddress && context.ipAddress) {
|
2804 | this.ipAddress = context.ipAddress;
|
2805 | }
|
2806 | if (!this.userAgent && context.userAgent) {
|
2807 | this.userAgent = context.userAgent;
|
2808 | }
|
2809 | if (typeof context.errors === 'number') {
|
2810 | this.errors = context.errors;
|
2811 | }
|
2812 | if (context.status) {
|
2813 | this.status = context.status;
|
2814 | }
|
2815 | }
|
2816 | /** JSDoc */
|
2817 | close(status) {
|
2818 | if (status) {
|
2819 | this.update({ status });
|
2820 | }
|
2821 | else if (this.status === 'ok') {
|
2822 | this.update({ status: 'exited' });
|
2823 | }
|
2824 | else {
|
2825 | this.update();
|
2826 | }
|
2827 | }
|
2828 | /** JSDoc */
|
2829 | toJSON() {
|
2830 | return dropUndefinedKeys({
|
2831 | sid: `${this.sid}`,
|
2832 | init: this.init,
|
2833 | // Make sure that sec is converted to ms for date constructor
|
2834 | started: new Date(this.started * 1000).toISOString(),
|
2835 | timestamp: new Date(this.timestamp * 1000).toISOString(),
|
2836 | status: this.status,
|
2837 | errors: this.errors,
|
2838 | did: typeof this.did === 'number' || typeof this.did === 'string' ? `${this.did}` : undefined,
|
2839 | duration: this.duration,
|
2840 | attrs: {
|
2841 | release: this.release,
|
2842 | environment: this.environment,
|
2843 | ip_address: this.ipAddress,
|
2844 | user_agent: this.userAgent,
|
2845 | },
|
2846 | });
|
2847 | }
|
2848 | }
|
2849 |
|
2850 | /*
|
2851 | * This file defines flags and constants that can be modified during compile time in order to facilitate tree shaking
|
2852 | * for users.
|
2853 | *
|
2854 | * Debug flags need to be declared in each package individually and must not be imported across package boundaries,
|
2855 | * because some build tools have trouble tree-shaking imported guards.
|
2856 | *
|
2857 | * As a convention, we define debug flags in a `flags.ts` file in the root of a package's `src` folder.
|
2858 | *
|
2859 | * Debug flag files will contain "magic strings" like `true` that may get replaced with actual values during
|
2860 | * our, or the user's build process. Take care when introducing new flags - they must not throw if they are not
|
2861 | * replaced.
|
2862 | */
|
2863 | /** Flag that is true for debug builds, false otherwise. */
|
2864 | const IS_DEBUG_BUILD$2 = true;
|
2865 |
|
2866 | /**
|
2867 | * API compatibility version of this hub.
|
2868 | *
|
2869 | * WARNING: This number should only be increased when the global interface
|
2870 | * changes and new methods are introduced.
|
2871 | *
|
2872 | * @hidden
|
2873 | */
|
2874 | const API_VERSION = 4;
|
2875 | /**
|
2876 | * Default maximum number of breadcrumbs added to an event. Can be overwritten
|
2877 | * with {@link Options.maxBreadcrumbs}.
|
2878 | */
|
2879 | const DEFAULT_BREADCRUMBS = 100;
|
2880 | /**
|
2881 | * @inheritDoc
|
2882 | */
|
2883 | class Hub {
|
2884 | /**
|
2885 | * Creates a new instance of the hub, will push one {@link Layer} into the
|
2886 | * internal stack on creation.
|
2887 | *
|
2888 | * @param client bound to the hub.
|
2889 | * @param scope bound to the hub.
|
2890 | * @param version number, higher number means higher priority.
|
2891 | */
|
2892 | constructor(client, scope = new Scope(), _version = API_VERSION) {
|
2893 | this._version = _version;
|
2894 | /** Is a {@link Layer}[] containing the client and scope */
|
2895 | this._stack = [{}];
|
2896 | this.getStackTop().scope = scope;
|
2897 | if (client) {
|
2898 | this.bindClient(client);
|
2899 | }
|
2900 | }
|
2901 | /**
|
2902 | * @inheritDoc
|
2903 | */
|
2904 | isOlderThan(version) {
|
2905 | return this._version < version;
|
2906 | }
|
2907 | /**
|
2908 | * @inheritDoc
|
2909 | */
|
2910 | bindClient(client) {
|
2911 | const top = this.getStackTop();
|
2912 | top.client = client;
|
2913 | if (client && client.setupIntegrations) {
|
2914 | client.setupIntegrations();
|
2915 | }
|
2916 | }
|
2917 | /**
|
2918 | * @inheritDoc
|
2919 | */
|
2920 | pushScope() {
|
2921 | // We want to clone the content of prev scope
|
2922 | const scope = Scope.clone(this.getScope());
|
2923 | this.getStack().push({
|
2924 | client: this.getClient(),
|
2925 | scope,
|
2926 | });
|
2927 | return scope;
|
2928 | }
|
2929 | /**
|
2930 | * @inheritDoc
|
2931 | */
|
2932 | popScope() {
|
2933 | if (this.getStack().length <= 1)
|
2934 | return false;
|
2935 | return !!this.getStack().pop();
|
2936 | }
|
2937 | /**
|
2938 | * @inheritDoc
|
2939 | */
|
2940 | withScope(callback) {
|
2941 | const scope = this.pushScope();
|
2942 | try {
|
2943 | callback(scope);
|
2944 | }
|
2945 | finally {
|
2946 | this.popScope();
|
2947 | }
|
2948 | }
|
2949 | /**
|
2950 | * @inheritDoc
|
2951 | */
|
2952 | getClient() {
|
2953 | return this.getStackTop().client;
|
2954 | }
|
2955 | /** Returns the scope of the top stack. */
|
2956 | getScope() {
|
2957 | return this.getStackTop().scope;
|
2958 | }
|
2959 | /** Returns the scope stack for domains or the process. */
|
2960 | getStack() {
|
2961 | return this._stack;
|
2962 | }
|
2963 | /** Returns the topmost scope layer in the order domain > local > process. */
|
2964 | getStackTop() {
|
2965 | return this._stack[this._stack.length - 1];
|
2966 | }
|
2967 | /**
|
2968 | * @inheritDoc
|
2969 | */
|
2970 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
2971 | captureException(exception, hint) {
|
2972 | const eventId = (this._lastEventId = hint && hint.event_id ? hint.event_id : uuid4());
|
2973 | let finalHint = hint;
|
2974 | // If there's no explicit hint provided, mimic the same thing that would happen
|
2975 | // in the minimal itself to create a consistent behavior.
|
2976 | // We don't do this in the client, as it's the lowest level API, and doing this,
|
2977 | // would prevent user from having full control over direct calls.
|
2978 | if (!hint) {
|
2979 | let syntheticException;
|
2980 | try {
|
2981 | throw new Error('Sentry syntheticException');
|
2982 | }
|
2983 | catch (exception) {
|
2984 | syntheticException = exception;
|
2985 | }
|
2986 | finalHint = {
|
2987 | originalException: exception,
|
2988 | syntheticException,
|
2989 | };
|
2990 | }
|
2991 | this._invokeClient('captureException', exception, Object.assign(Object.assign({}, finalHint), { event_id: eventId }));
|
2992 | return eventId;
|
2993 | }
|
2994 | /**
|
2995 | * @inheritDoc
|
2996 | */
|
2997 | captureMessage(message, level, hint) {
|
2998 | const eventId = (this._lastEventId = hint && hint.event_id ? hint.event_id : uuid4());
|
2999 | let finalHint = hint;
|
3000 | // If there's no explicit hint provided, mimic the same thing that would happen
|
3001 | // in the minimal itself to create a consistent behavior.
|
3002 | // We don't do this in the client, as it's the lowest level API, and doing this,
|
3003 | // would prevent user from having full control over direct calls.
|
3004 | if (!hint) {
|
3005 | let syntheticException;
|
3006 | try {
|
3007 | throw new Error(message);
|
3008 | }
|
3009 | catch (exception) {
|
3010 | syntheticException = exception;
|
3011 | }
|
3012 | finalHint = {
|
3013 | originalException: message,
|
3014 | syntheticException,
|
3015 | };
|
3016 | }
|
3017 | this._invokeClient('captureMessage', message, level, Object.assign(Object.assign({}, finalHint), { event_id: eventId }));
|
3018 | return eventId;
|
3019 | }
|
3020 | /**
|
3021 | * @inheritDoc
|
3022 | */
|
3023 | captureEvent(event, hint) {
|
3024 | const eventId = hint && hint.event_id ? hint.event_id : uuid4();
|
3025 | if (event.type !== 'transaction') {
|
3026 | this._lastEventId = eventId;
|
3027 | }
|
3028 | this._invokeClient('captureEvent', event, Object.assign(Object.assign({}, hint), { event_id: eventId }));
|
3029 | return eventId;
|
3030 | }
|
3031 | /**
|
3032 | * @inheritDoc
|
3033 | */
|
3034 | lastEventId() {
|
3035 | return this._lastEventId;
|
3036 | }
|
3037 | /**
|
3038 | * @inheritDoc
|
3039 | */
|
3040 | addBreadcrumb(breadcrumb, hint) {
|
3041 | const { scope, client } = this.getStackTop();
|
3042 | if (!scope || !client)
|
3043 | return;
|
3044 | // eslint-disable-next-line @typescript-eslint/unbound-method
|
3045 | const { beforeBreadcrumb = null, maxBreadcrumbs = DEFAULT_BREADCRUMBS } = (client.getOptions && client.getOptions()) || {};
|
3046 | if (maxBreadcrumbs <= 0)
|
3047 | return;
|
3048 | const timestamp = dateTimestampInSeconds();
|
3049 | const mergedBreadcrumb = Object.assign({ timestamp }, breadcrumb);
|
3050 | const finalBreadcrumb = beforeBreadcrumb
|
3051 | ? consoleSandbox(() => beforeBreadcrumb(mergedBreadcrumb, hint))
|
3052 | : mergedBreadcrumb;
|
3053 | if (finalBreadcrumb === null)
|
3054 | return;
|
3055 | scope.addBreadcrumb(finalBreadcrumb, maxBreadcrumbs);
|
3056 | }
|
3057 | /**
|
3058 | * @inheritDoc
|
3059 | */
|
3060 | setUser(user) {
|
3061 | const scope = this.getScope();
|
3062 | if (scope)
|
3063 | scope.setUser(user);
|
3064 | }
|
3065 | /**
|
3066 | * @inheritDoc
|
3067 | */
|
3068 | setTags(tags) {
|
3069 | const scope = this.getScope();
|
3070 | if (scope)
|
3071 | scope.setTags(tags);
|
3072 | }
|
3073 | /**
|
3074 | * @inheritDoc
|
3075 | */
|
3076 | setExtras(extras) {
|
3077 | const scope = this.getScope();
|
3078 | if (scope)
|
3079 | scope.setExtras(extras);
|
3080 | }
|
3081 | /**
|
3082 | * @inheritDoc
|
3083 | */
|
3084 | setTag(key, value) {
|
3085 | const scope = this.getScope();
|
3086 | if (scope)
|
3087 | scope.setTag(key, value);
|
3088 | }
|
3089 | /**
|
3090 | * @inheritDoc
|
3091 | */
|
3092 | setExtra(key, extra) {
|
3093 | const scope = this.getScope();
|
3094 | if (scope)
|
3095 | scope.setExtra(key, extra);
|
3096 | }
|
3097 | /**
|
3098 | * @inheritDoc
|
3099 | */
|
3100 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
3101 | setContext(name, context) {
|
3102 | const scope = this.getScope();
|
3103 | if (scope)
|
3104 | scope.setContext(name, context);
|
3105 | }
|
3106 | /**
|
3107 | * @inheritDoc
|
3108 | */
|
3109 | configureScope(callback) {
|
3110 | const { scope, client } = this.getStackTop();
|
3111 | if (scope && client) {
|
3112 | callback(scope);
|
3113 | }
|
3114 | }
|
3115 | /**
|
3116 | * @inheritDoc
|
3117 | */
|
3118 | run(callback) {
|
3119 | const oldHub = makeMain(this);
|
3120 | try {
|
3121 | callback(this);
|
3122 | }
|
3123 | finally {
|
3124 | makeMain(oldHub);
|
3125 | }
|
3126 | }
|
3127 | /**
|
3128 | * @inheritDoc
|
3129 | */
|
3130 | getIntegration(integration) {
|
3131 | const client = this.getClient();
|
3132 | if (!client)
|
3133 | return null;
|
3134 | try {
|
3135 | return client.getIntegration(integration);
|
3136 | }
|
3137 | catch (_oO) {
|
3138 | IS_DEBUG_BUILD$2 && logger.warn(`Cannot retrieve integration ${integration.id} from the current Hub`);
|
3139 | return null;
|
3140 | }
|
3141 | }
|
3142 | /**
|
3143 | * @inheritDoc
|
3144 | */
|
3145 | startSpan(context) {
|
3146 | return this._callExtensionMethod('startSpan', context);
|
3147 | }
|
3148 | /**
|
3149 | * @inheritDoc
|
3150 | */
|
3151 | startTransaction(context, customSamplingContext) {
|
3152 | return this._callExtensionMethod('startTransaction', context, customSamplingContext);
|
3153 | }
|
3154 | /**
|
3155 | * @inheritDoc
|
3156 | */
|
3157 | traceHeaders() {
|
3158 | return this._callExtensionMethod('traceHeaders');
|
3159 | }
|
3160 | /**
|
3161 | * @inheritDoc
|
3162 | */
|
3163 | captureSession(endSession = false) {
|
3164 | // both send the update and pull the session from the scope
|
3165 | if (endSession) {
|
3166 | return this.endSession();
|
3167 | }
|
3168 | // only send the update
|
3169 | this._sendSessionUpdate();
|
3170 | }
|
3171 | /**
|
3172 | * @inheritDoc
|
3173 | */
|
3174 | endSession() {
|
3175 | const layer = this.getStackTop();
|
3176 | const scope = layer && layer.scope;
|
3177 | const session = scope && scope.getSession();
|
3178 | if (session) {
|
3179 | session.close();
|
3180 | }
|
3181 | this._sendSessionUpdate();
|
3182 | // the session is over; take it off of the scope
|
3183 | if (scope) {
|
3184 | scope.setSession();
|
3185 | }
|
3186 | }
|
3187 | /**
|
3188 | * @inheritDoc
|
3189 | */
|
3190 | startSession(context) {
|
3191 | const { scope, client } = this.getStackTop();
|
3192 | const { release, environment } = (client && client.getOptions()) || {};
|
3193 | // Will fetch userAgent if called from browser sdk
|
3194 | const global = getGlobalObject();
|
3195 | const { userAgent } = global.navigator || {};
|
3196 | const session = new Session(Object.assign(Object.assign(Object.assign({ release,
|
3197 | environment }, (scope && { user: scope.getUser() })), (userAgent && { userAgent })), context));
|
3198 | if (scope) {
|
3199 | // End existing session if there's one
|
3200 | const currentSession = scope.getSession && scope.getSession();
|
3201 | if (currentSession && currentSession.status === 'ok') {
|
3202 | currentSession.update({ status: 'exited' });
|
3203 | }
|
3204 | this.endSession();
|
3205 | // Afterwards we set the new session on the scope
|
3206 | scope.setSession(session);
|
3207 | }
|
3208 | return session;
|
3209 | }
|
3210 | /**
|
3211 | * Sends the current Session on the scope
|
3212 | */
|
3213 | _sendSessionUpdate() {
|
3214 | const { scope, client } = this.getStackTop();
|
3215 | if (!scope)
|
3216 | return;
|
3217 | const session = scope.getSession && scope.getSession();
|
3218 | if (session) {
|
3219 | if (client && client.captureSession) {
|
3220 | client.captureSession(session);
|
3221 | }
|
3222 | }
|
3223 | }
|
3224 | /**
|
3225 | * Internal helper function to call a method on the top client if it exists.
|
3226 | *
|
3227 | * @param method The method to call on the client.
|
3228 | * @param args Arguments to pass to the client function.
|
3229 | */
|
3230 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
3231 | _invokeClient(method, ...args) {
|
3232 | const { scope, client } = this.getStackTop();
|
3233 | if (client && client[method]) {
|
3234 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
|
3235 | client[method](...args, scope);
|
3236 | }
|
3237 | }
|
3238 | /**
|
3239 | * Calls global extension method and binding current instance to the function call
|
3240 | */
|
3241 | // @ts-ignore Function lacks ending return statement and return type does not include 'undefined'. ts(2366)
|
3242 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
3243 | _callExtensionMethod(method, ...args) {
|
3244 | const carrier = getMainCarrier();
|
3245 | const sentry = carrier.__SENTRY__;
|
3246 | if (sentry && sentry.extensions && typeof sentry.extensions[method] === 'function') {
|
3247 | return sentry.extensions[method].apply(this, args);
|
3248 | }
|
3249 | IS_DEBUG_BUILD$2 && logger.warn(`Extension method ${method} couldn't be found, doing nothing.`);
|
3250 | }
|
3251 | }
|
3252 | /**
|
3253 | * Returns the global shim registry.
|
3254 | *
|
3255 | * FIXME: This function is problematic, because despite always returning a valid Carrier,
|
3256 | * it has an optional `__SENTRY__` property, which then in turn requires us to always perform an unnecessary check
|
3257 | * at the call-site. We always access the carrier through this function, so we can guarantee that `__SENTRY__` is there.
|
3258 | **/
|
3259 | function getMainCarrier() {
|
3260 | const carrier = getGlobalObject();
|
3261 | carrier.__SENTRY__ = carrier.__SENTRY__ || {
|
3262 | extensions: {},
|
3263 | hub: undefined,
|
3264 | };
|
3265 | return carrier;
|
3266 | }
|
3267 | /**
|
3268 | * Replaces the current main hub with the passed one on the global object
|
3269 | *
|
3270 | * @returns The old replaced hub
|
3271 | */
|
3272 | function makeMain(hub) {
|
3273 | const registry = getMainCarrier();
|
3274 | const oldHub = getHubFromCarrier(registry);
|
3275 | setHubOnCarrier(registry, hub);
|
3276 | return oldHub;
|
3277 | }
|
3278 | /**
|
3279 | * Returns the default hub instance.
|
3280 | *
|
3281 | * If a hub is already registered in the global carrier but this module
|
3282 | * contains a more recent version, it replaces the registered version.
|
3283 | * Otherwise, the currently registered hub will be returned.
|
3284 | */
|
3285 | function getCurrentHub() {
|
3286 | // Get main carrier (global for every environment)
|
3287 | const registry = getMainCarrier();
|
3288 | // If there's no hub, or its an old API, assign a new one
|
3289 | if (!hasHubOnCarrier(registry) || getHubFromCarrier(registry).isOlderThan(API_VERSION)) {
|
3290 | setHubOnCarrier(registry, new Hub());
|
3291 | }
|
3292 | // Return hub that lives on a global object
|
3293 | return getHubFromCarrier(registry);
|
3294 | }
|
3295 | /**
|
3296 | * This will tell whether a carrier has a hub on it or not
|
3297 | * @param carrier object
|
3298 | */
|
3299 | function hasHubOnCarrier(carrier) {
|
3300 | return !!(carrier && carrier.__SENTRY__ && carrier.__SENTRY__.hub);
|
3301 | }
|
3302 | /**
|
3303 | * This will create a new {@link Hub} and add to the passed object on
|
3304 | * __SENTRY__.hub.
|
3305 | * @param carrier object
|
3306 | * @hidden
|
3307 | */
|
3308 | function getHubFromCarrier(carrier) {
|
3309 | return getGlobalSingleton('hub', () => new Hub(), carrier);
|
3310 | }
|
3311 | /**
|
3312 | * This will set passed {@link Hub} on the passed object's __SENTRY__.hub attribute
|
3313 | * @param carrier object
|
3314 | * @param hub Hub
|
3315 | * @returns A boolean indicating success or failure
|
3316 | */
|
3317 | function setHubOnCarrier(carrier, hub) {
|
3318 | if (!carrier)
|
3319 | return false;
|
3320 | const __SENTRY__ = (carrier.__SENTRY__ = carrier.__SENTRY__ || {});
|
3321 | __SENTRY__.hub = hub;
|
3322 | return true;
|
3323 | }
|
3324 |
|
3325 | /**
|
3326 | * This calls a function on the current hub.
|
3327 | * @param method function to call on hub.
|
3328 | * @param args to pass to function.
|
3329 | */
|
3330 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
3331 | function callOnHub(method, ...args) {
|
3332 | const hub = getCurrentHub();
|
3333 | if (hub && hub[method]) {
|
3334 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
3335 | return hub[method](...args);
|
3336 | }
|
3337 | throw new Error(`No hub defined or ${method} was not found on the hub, please open a bug report.`);
|
3338 | }
|
3339 | /**
|
3340 | * Captures an exception event and sends it to Sentry.
|
3341 | *
|
3342 | * @param exception An exception-like object.
|
3343 | * @returns The generated eventId.
|
3344 | */
|
3345 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
3346 | function captureException(exception, captureContext) {
|
3347 | const syntheticException = new Error('Sentry syntheticException');
|
3348 | return callOnHub('captureException', exception, {
|
3349 | captureContext,
|
3350 | originalException: exception,
|
3351 | syntheticException,
|
3352 | });
|
3353 | }
|
3354 | /**
|
3355 | * Captures a message event and sends it to Sentry.
|
3356 | *
|
3357 | * @param message The message to send to Sentry.
|
3358 | * @param Severity Define the level of the message.
|
3359 | * @returns The generated eventId.
|
3360 | */
|
3361 | function captureMessage(message, captureContext) {
|
3362 | const syntheticException = new Error(message);
|
3363 | // This is necessary to provide explicit scopes upgrade, without changing the original
|
3364 | // arity of the `captureMessage(message, level)` method.
|
3365 | const level = typeof captureContext === 'string' ? captureContext : undefined;
|
3366 | const context = typeof captureContext !== 'string' ? { captureContext } : undefined;
|
3367 | return callOnHub('captureMessage', message, level, Object.assign({ originalException: message, syntheticException }, context));
|
3368 | }
|
3369 | /**
|
3370 | * Captures a manually created event and sends it to Sentry.
|
3371 | *
|
3372 | * @param event The event to send to Sentry.
|
3373 | * @returns The generated eventId.
|
3374 | */
|
3375 | function captureEvent(event) {
|
3376 | return callOnHub('captureEvent', event);
|
3377 | }
|
3378 | /**
|
3379 | * Callback to set context information onto the scope.
|
3380 | * @param callback Callback function that receives Scope.
|
3381 | */
|
3382 | function configureScope(callback) {
|
3383 | callOnHub('configureScope', callback);
|
3384 | }
|
3385 | /**
|
3386 | * Records a new breadcrumb which will be attached to future events.
|
3387 | *
|
3388 | * Breadcrumbs will be added to subsequent events to provide more context on
|
3389 | * user's actions prior to an error or crash.
|
3390 | *
|
3391 | * @param breadcrumb The breadcrumb to record.
|
3392 | */
|
3393 | function addBreadcrumb(breadcrumb) {
|
3394 | callOnHub('addBreadcrumb', breadcrumb);
|
3395 | }
|
3396 | /**
|
3397 | * Sets context data with the given name.
|
3398 | * @param name of the context
|
3399 | * @param context Any kind of data. This data will be normalized.
|
3400 | */
|
3401 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
3402 | function setContext(name, context) {
|
3403 | callOnHub('setContext', name, context);
|
3404 | }
|
3405 | /**
|
3406 | * Set an object that will be merged sent as extra data with the event.
|
3407 | * @param extras Extras object to merge into current context.
|
3408 | */
|
3409 | function setExtras(extras) {
|
3410 | callOnHub('setExtras', extras);
|
3411 | }
|
3412 | /**
|
3413 | * Set an object that will be merged sent as tags data with the event.
|
3414 | * @param tags Tags context object to merge into current context.
|
3415 | */
|
3416 | function setTags(tags) {
|
3417 | callOnHub('setTags', tags);
|
3418 | }
|
3419 | /**
|
3420 | * Set key:value that will be sent as extra data with the event.
|
3421 | * @param key String of extra
|
3422 | * @param extra Any kind of data. This data will be normalized.
|
3423 | */
|
3424 | function setExtra(key, extra) {
|
3425 | callOnHub('setExtra', key, extra);
|
3426 | }
|
3427 | /**
|
3428 | * Set key:value that will be sent as tags data with the event.
|
3429 | *
|
3430 | * Can also be used to unset a tag, by passing `undefined`.
|
3431 | *
|
3432 | * @param key String key of tag
|
3433 | * @param value Value of tag
|
3434 | */
|
3435 | function setTag(key, value) {
|
3436 | callOnHub('setTag', key, value);
|
3437 | }
|
3438 | /**
|
3439 | * Updates user context information for future events.
|
3440 | *
|
3441 | * @param user User context object to be set in the current context. Pass `null` to unset the user.
|
3442 | */
|
3443 | function setUser(user) {
|
3444 | callOnHub('setUser', user);
|
3445 | }
|
3446 | /**
|
3447 | * Creates a new scope with and executes the given operation within.
|
3448 | * The scope is automatically removed once the operation
|
3449 | * finishes or throws.
|
3450 | *
|
3451 | * This is essentially a convenience function for:
|
3452 | *
|
3453 | * pushScope();
|
3454 | * callback();
|
3455 | * popScope();
|
3456 | *
|
3457 | * @param callback that will be enclosed into push/popScope.
|
3458 | */
|
3459 | function withScope(callback) {
|
3460 | callOnHub('withScope', callback);
|
3461 | }
|
3462 | /**
|
3463 | * Starts a new `Transaction` and returns it. This is the entry point to manual tracing instrumentation.
|
3464 | *
|
3465 | * A tree structure can be built by adding child spans to the transaction, and child spans to other spans. To start a
|
3466 | * new child span within the transaction or any span, call the respective `.startChild()` method.
|
3467 | *
|
3468 | * Every child span must be finished before the transaction is finished, otherwise the unfinished spans are discarded.
|
3469 | *
|
3470 | * The transaction must be finished with a call to its `.finish()` method, at which point the transaction with all its
|
3471 | * finished child spans will be sent to Sentry.
|
3472 | *
|
3473 | * @param context Properties of the new `Transaction`.
|
3474 | * @param customSamplingContext Information given to the transaction sampling function (along with context-dependent
|
3475 | * default values). See {@link Options.tracesSampler}.
|
3476 | *
|
3477 | * @returns The transaction which was just started
|
3478 | */
|
3479 | function startTransaction(context, customSamplingContext) {
|
3480 | return callOnHub('startTransaction', Object.assign({}, context), customSamplingContext);
|
3481 | }
|
3482 |
|
3483 | const SENTRY_API_VERSION = '7';
|
3484 | /** Initializes API Details */
|
3485 | function initAPIDetails(dsn, metadata, tunnel) {
|
3486 | return {
|
3487 | initDsn: dsn,
|
3488 | metadata: metadata || {},
|
3489 | dsn: makeDsn(dsn),
|
3490 | tunnel,
|
3491 | };
|
3492 | }
|
3493 | /** Returns the prefix to construct Sentry ingestion API endpoints. */
|
3494 | function getBaseApiEndpoint(dsn) {
|
3495 | const protocol = dsn.protocol ? `${dsn.protocol}:` : '';
|
3496 | const port = dsn.port ? `:${dsn.port}` : '';
|
3497 | return `${protocol}//${dsn.host}${port}${dsn.path ? `/${dsn.path}` : ''}/api/`;
|
3498 | }
|
3499 | /** Returns the ingest API endpoint for target. */
|
3500 | function _getIngestEndpoint(dsn, target) {
|
3501 | return `${getBaseApiEndpoint(dsn)}${dsn.projectId}/${target}/`;
|
3502 | }
|
3503 | /** Returns a URL-encoded string with auth config suitable for a query string. */
|
3504 | function _encodedAuth(dsn) {
|
3505 | return urlEncode({
|
3506 | // We send only the minimum set of required information. See
|
3507 | // https://github.com/getsentry/sentry-javascript/issues/2572.
|
3508 | sentry_key: dsn.publicKey,
|
3509 | sentry_version: SENTRY_API_VERSION,
|
3510 | });
|
3511 | }
|
3512 | /** Returns the store endpoint URL. */
|
3513 | function getStoreEndpoint(dsn) {
|
3514 | return _getIngestEndpoint(dsn, 'store');
|
3515 | }
|
3516 | /**
|
3517 | * Returns the store endpoint URL with auth in the query string.
|
3518 | *
|
3519 | * Sending auth as part of the query string and not as custom HTTP headers avoids CORS preflight requests.
|
3520 | */
|
3521 | function getStoreEndpointWithUrlEncodedAuth(dsn) {
|
3522 | return `${getStoreEndpoint(dsn)}?${_encodedAuth(dsn)}`;
|
3523 | }
|
3524 | /** Returns the envelope endpoint URL. */
|
3525 | function _getEnvelopeEndpoint(dsn) {
|
3526 | return _getIngestEndpoint(dsn, 'envelope');
|
3527 | }
|
3528 | /**
|
3529 | * Returns the envelope endpoint URL with auth in the query string.
|
3530 | *
|
3531 | * Sending auth as part of the query string and not as custom HTTP headers avoids CORS preflight requests.
|
3532 | */
|
3533 | function getEnvelopeEndpointWithUrlEncodedAuth(dsn, tunnel) {
|
3534 | return tunnel ? tunnel : `${_getEnvelopeEndpoint(dsn)}?${_encodedAuth(dsn)}`;
|
3535 | }
|
3536 | /** Returns the url to the report dialog endpoint. */
|
3537 | function getReportDialogEndpoint(dsnLike, dialogOptions) {
|
3538 | const dsn = makeDsn(dsnLike);
|
3539 | const endpoint = `${getBaseApiEndpoint(dsn)}embed/error-page/`;
|
3540 | let encodedOptions = `dsn=${dsnToString(dsn)}`;
|
3541 | for (const key in dialogOptions) {
|
3542 | if (key === 'dsn') {
|
3543 | continue;
|
3544 | }
|
3545 | if (key === 'user') {
|
3546 | if (!dialogOptions.user) {
|
3547 | continue;
|
3548 | }
|
3549 | if (dialogOptions.user.name) {
|
3550 | encodedOptions += `&name=${encodeURIComponent(dialogOptions.user.name)}`;
|
3551 | }
|
3552 | if (dialogOptions.user.email) {
|
3553 | encodedOptions += `&email=${encodeURIComponent(dialogOptions.user.email)}`;
|
3554 | }
|
3555 | }
|
3556 | else {
|
3557 | encodedOptions += `&${encodeURIComponent(key)}=${encodeURIComponent(dialogOptions[key])}`;
|
3558 | }
|
3559 | }
|
3560 | return `${endpoint}?${encodedOptions}`;
|
3561 | }
|
3562 |
|
3563 | /*
|
3564 | * This file defines flags and constants that can be modified during compile time in order to facilitate tree shaking
|
3565 | * for users.
|
3566 | *
|
3567 | * Debug flags need to be declared in each package individually and must not be imported across package boundaries,
|
3568 | * because some build tools have trouble tree-shaking imported guards.
|
3569 | *
|
3570 | * As a convention, we define debug flags in a `flags.ts` file in the root of a package's `src` folder.
|
3571 | *
|
3572 | * Debug flag files will contain "magic strings" like `true` that may get replaced with actual values during
|
3573 | * our, or the user's build process. Take care when introducing new flags - they must not throw if they are not
|
3574 | * replaced.
|
3575 | */
|
3576 | /** Flag that is true for debug builds, false otherwise. */
|
3577 | const IS_DEBUG_BUILD$1 = true;
|
3578 |
|
3579 | const installedIntegrations = [];
|
3580 | /**
|
3581 | * @private
|
3582 | */
|
3583 | function filterDuplicates(integrations) {
|
3584 | return integrations.reduce((acc, integrations) => {
|
3585 | if (acc.every(accIntegration => integrations.name !== accIntegration.name)) {
|
3586 | acc.push(integrations);
|
3587 | }
|
3588 | return acc;
|
3589 | }, []);
|
3590 | }
|
3591 | /** Gets integration to install */
|
3592 | function getIntegrationsToSetup(options) {
|
3593 | const defaultIntegrations = (options.defaultIntegrations && [...options.defaultIntegrations]) || [];
|
3594 | const userIntegrations = options.integrations;
|
3595 | let integrations = [...filterDuplicates(defaultIntegrations)];
|
3596 | if (Array.isArray(userIntegrations)) {
|
3597 | // Filter out integrations that are also included in user options
|
3598 | integrations = [
|
3599 | ...integrations.filter(integrations => userIntegrations.every(userIntegration => userIntegration.name !== integrations.name)),
|
3600 | // And filter out duplicated user options integrations
|
3601 | ...filterDuplicates(userIntegrations),
|
3602 | ];
|
3603 | }
|
3604 | else if (typeof userIntegrations === 'function') {
|
3605 | integrations = userIntegrations(integrations);
|
3606 | integrations = Array.isArray(integrations) ? integrations : [integrations];
|
3607 | }
|
3608 | // Make sure that if present, `Debug` integration will always run last
|
3609 | const integrationsNames = integrations.map(i => i.name);
|
3610 | const alwaysLastToRun = 'Debug';
|
3611 | if (integrationsNames.indexOf(alwaysLastToRun) !== -1) {
|
3612 | integrations.push(...integrations.splice(integrationsNames.indexOf(alwaysLastToRun), 1));
|
3613 | }
|
3614 | return integrations;
|
3615 | }
|
3616 | /** Setup given integration */
|
3617 | function setupIntegration(integration) {
|
3618 | if (installedIntegrations.indexOf(integration.name) !== -1) {
|
3619 | return;
|
3620 | }
|
3621 | integration.setupOnce(addGlobalEventProcessor, getCurrentHub);
|
3622 | installedIntegrations.push(integration.name);
|
3623 | logger.log(`Integration installed: ${integration.name}`);
|
3624 | }
|
3625 | /**
|
3626 | * Given a list of integration instances this installs them all. When `withDefaults` is set to `true` then all default
|
3627 | * integrations are added unless they were already provided before.
|
3628 | * @param integrations array of integration instances
|
3629 | * @param withDefault should enable default integrations
|
3630 | */
|
3631 | function setupIntegrations(options) {
|
3632 | const integrations = {};
|
3633 | getIntegrationsToSetup(options).forEach(integration => {
|
3634 | integrations[integration.name] = integration;
|
3635 | setupIntegration(integration);
|
3636 | });
|
3637 | // set the `initialized` flag so we don't run through the process again unecessarily; use `Object.defineProperty`
|
3638 | // because by default it creates a property which is nonenumerable, which we want since `initialized` shouldn't be
|
3639 | // considered a member of the index the way the actual integrations are
|
3640 | addNonEnumerableProperty(integrations, 'initialized', true);
|
3641 | return integrations;
|
3642 | }
|
3643 |
|
3644 | /* eslint-disable max-lines */
|
3645 | const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been captured.";
|
3646 | /**
|
3647 | * Base implementation for all JavaScript SDK clients.
|
3648 | *
|
3649 | * Call the constructor with the corresponding backend constructor and options
|
3650 | * specific to the client subclass. To access these options later, use
|
3651 | * {@link Client.getOptions}. Also, the Backend instance is available via
|
3652 | * {@link Client.getBackend}.
|
3653 | *
|
3654 | * If a Dsn is specified in the options, it will be parsed and stored. Use
|
3655 | * {@link Client.getDsn} to retrieve the Dsn at any moment. In case the Dsn is
|
3656 | * invalid, the constructor will throw a {@link SentryException}. Note that
|
3657 | * without a valid Dsn, the SDK will not send any events to Sentry.
|
3658 | *
|
3659 | * Before sending an event via the backend, it is passed through
|
3660 | * {@link BaseClient._prepareEvent} to add SDK information and scope data
|
3661 | * (breadcrumbs and context). To add more custom information, override this
|
3662 | * method and extend the resulting prepared event.
|
3663 | *
|
3664 | * To issue automatically created events (e.g. via instrumentation), use
|
3665 | * {@link Client.captureEvent}. It will prepare the event and pass it through
|
3666 | * the callback lifecycle. To issue auto-breadcrumbs, use
|
3667 | * {@link Client.addBreadcrumb}.
|
3668 | *
|
3669 | * @example
|
3670 | * class NodeClient extends BaseClient<NodeBackend, NodeOptions> {
|
3671 | * public constructor(options: NodeOptions) {
|
3672 | * super(NodeBackend, options);
|
3673 | * }
|
3674 | *
|
3675 | * // ...
|
3676 | * }
|
3677 | */
|
3678 | class BaseClient {
|
3679 | /**
|
3680 | * Initializes this client instance.
|
3681 | *
|
3682 | * @param backendClass A constructor function to create the backend.
|
3683 | * @param options Options for the client.
|
3684 | */
|
3685 | constructor(backendClass, options) {
|
3686 | /** Array of used integrations. */
|
3687 | this._integrations = {};
|
3688 | /** Number of calls being processed */
|
3689 | this._numProcessing = 0;
|
3690 | this._backend = new backendClass(options);
|
3691 | this._options = options;
|
3692 | if (options.dsn) {
|
3693 | this._dsn = makeDsn(options.dsn);
|
3694 | }
|
3695 | }
|
3696 | /**
|
3697 | * @inheritDoc
|
3698 | */
|
3699 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
3700 | captureException(exception, hint, scope) {
|
3701 | // ensure we haven't captured this very object before
|
3702 | if (checkOrSetAlreadyCaught(exception)) {
|
3703 | logger.log(ALREADY_SEEN_ERROR);
|
3704 | return;
|
3705 | }
|
3706 | let eventId = hint && hint.event_id;
|
3707 | this._process(this._getBackend()
|
3708 | .eventFromException(exception, hint)
|
3709 | .then(event => this._captureEvent(event, hint, scope))
|
3710 | .then(result => {
|
3711 | eventId = result;
|
3712 | }));
|
3713 | return eventId;
|
3714 | }
|
3715 | /**
|
3716 | * @inheritDoc
|
3717 | */
|
3718 | captureMessage(message, level, hint, scope) {
|
3719 | let eventId = hint && hint.event_id;
|
3720 | const promisedEvent = isPrimitive(message)
|
3721 | ? this._getBackend().eventFromMessage(String(message), level, hint)
|
3722 | : this._getBackend().eventFromException(message, hint);
|
3723 | this._process(promisedEvent
|
3724 | .then(event => this._captureEvent(event, hint, scope))
|
3725 | .then(result => {
|
3726 | eventId = result;
|
3727 | }));
|
3728 | return eventId;
|
3729 | }
|
3730 | /**
|
3731 | * @inheritDoc
|
3732 | */
|
3733 | captureEvent(event, hint, scope) {
|
3734 | // ensure we haven't captured this very object before
|
3735 | if (hint && hint.originalException && checkOrSetAlreadyCaught(hint.originalException)) {
|
3736 | logger.log(ALREADY_SEEN_ERROR);
|
3737 | return;
|
3738 | }
|
3739 | let eventId = hint && hint.event_id;
|
3740 | this._process(this._captureEvent(event, hint, scope).then(result => {
|
3741 | eventId = result;
|
3742 | }));
|
3743 | return eventId;
|
3744 | }
|
3745 | /**
|
3746 | * @inheritDoc
|
3747 | */
|
3748 | captureSession(session) {
|
3749 | if (!this._isEnabled()) {
|
3750 | logger.warn('SDK not enabled, will not capture session.');
|
3751 | return;
|
3752 | }
|
3753 | if (!(typeof session.release === 'string')) {
|
3754 | logger.warn('Discarded session because of missing or non-string release');
|
3755 | }
|
3756 | else {
|
3757 | this._sendSession(session);
|
3758 | // After sending, we set init false to indicate it's not the first occurrence
|
3759 | session.update({ init: false });
|
3760 | }
|
3761 | }
|
3762 | /**
|
3763 | * @inheritDoc
|
3764 | */
|
3765 | getDsn() {
|
3766 | return this._dsn;
|
3767 | }
|
3768 | /**
|
3769 | * @inheritDoc
|
3770 | */
|
3771 | getOptions() {
|
3772 | return this._options;
|
3773 | }
|
3774 | /**
|
3775 | * @inheritDoc
|
3776 | */
|
3777 | getTransport() {
|
3778 | return this._getBackend().getTransport();
|
3779 | }
|
3780 | /**
|
3781 | * @inheritDoc
|
3782 | */
|
3783 | flush(timeout) {
|
3784 | return this._isClientDoneProcessing(timeout).then(clientFinished => {
|
3785 | return this.getTransport()
|
3786 | .close(timeout)
|
3787 | .then(transportFlushed => clientFinished && transportFlushed);
|
3788 | });
|
3789 | }
|
3790 | /**
|
3791 | * @inheritDoc
|
3792 | */
|
3793 | close(timeout) {
|
3794 | return this.flush(timeout).then(result => {
|
3795 | this.getOptions().enabled = false;
|
3796 | return result;
|
3797 | });
|
3798 | }
|
3799 | /**
|
3800 | * Sets up the integrations
|
3801 | */
|
3802 | setupIntegrations() {
|
3803 | if (this._isEnabled() && !this._integrations.initialized) {
|
3804 | this._integrations = setupIntegrations(this._options);
|
3805 | }
|
3806 | }
|
3807 | /**
|
3808 | * @inheritDoc
|
3809 | */
|
3810 | getIntegration(integration) {
|
3811 | try {
|
3812 | return this._integrations[integration.id] || null;
|
3813 | }
|
3814 | catch (_oO) {
|
3815 | logger.warn(`Cannot retrieve integration ${integration.id} from the current Client`);
|
3816 | return null;
|
3817 | }
|
3818 | }
|
3819 | /** Updates existing session based on the provided event */
|
3820 | _updateSessionFromEvent(session, event) {
|
3821 | let crashed = false;
|
3822 | let errored = false;
|
3823 | const exceptions = event.exception && event.exception.values;
|
3824 | if (exceptions) {
|
3825 | errored = true;
|
3826 | for (const ex of exceptions) {
|
3827 | const mechanism = ex.mechanism;
|
3828 | if (mechanism && mechanism.handled === false) {
|
3829 | crashed = true;
|
3830 | break;
|
3831 | }
|
3832 | }
|
3833 | }
|
3834 | // A session is updated and that session update is sent in only one of the two following scenarios:
|
3835 | // 1. Session with non terminal status and 0 errors + an error occurred -> Will set error count to 1 and send update
|
3836 | // 2. Session with non terminal status and 1 error + a crash occurred -> Will set status crashed and send update
|
3837 | const sessionNonTerminal = session.status === 'ok';
|
3838 | const shouldUpdateAndSend = (sessionNonTerminal && session.errors === 0) || (sessionNonTerminal && crashed);
|
3839 | if (shouldUpdateAndSend) {
|
3840 | session.update(Object.assign(Object.assign({}, (crashed && { status: 'crashed' })), { errors: session.errors || Number(errored || crashed) }));
|
3841 | this.captureSession(session);
|
3842 | }
|
3843 | }
|
3844 | /** Deliver captured session to Sentry */
|
3845 | _sendSession(session) {
|
3846 | this._getBackend().sendSession(session);
|
3847 | }
|
3848 | /**
|
3849 | * Determine if the client is finished processing. Returns a promise because it will wait `timeout` ms before saying
|
3850 | * "no" (resolving to `false`) in order to give the client a chance to potentially finish first.
|
3851 | *
|
3852 | * @param timeout The time, in ms, after which to resolve to `false` if the client is still busy. Passing `0` (or not
|
3853 | * passing anything) will make the promise wait as long as it takes for processing to finish before resolving to
|
3854 | * `true`.
|
3855 | * @returns A promise which will resolve to `true` if processing is already done or finishes before the timeout, and
|
3856 | * `false` otherwise
|
3857 | */
|
3858 | _isClientDoneProcessing(timeout) {
|
3859 | return new SyncPromise(resolve => {
|
3860 | let ticked = 0;
|
3861 | const tick = 1;
|
3862 | const interval = setInterval(() => {
|
3863 | if (this._numProcessing == 0) {
|
3864 | clearInterval(interval);
|
3865 | resolve(true);
|
3866 | }
|
3867 | else {
|
3868 | ticked += tick;
|
3869 | if (timeout && ticked >= timeout) {
|
3870 | clearInterval(interval);
|
3871 | resolve(false);
|
3872 | }
|
3873 | }
|
3874 | }, tick);
|
3875 | });
|
3876 | }
|
3877 | /** Returns the current backend. */
|
3878 | _getBackend() {
|
3879 | return this._backend;
|
3880 | }
|
3881 | /** Determines whether this SDK is enabled and a valid Dsn is present. */
|
3882 | _isEnabled() {
|
3883 | return this.getOptions().enabled !== false && this._dsn !== undefined;
|
3884 | }
|
3885 | /**
|
3886 | * Adds common information to events.
|
3887 | *
|
3888 | * The information includes release and environment from `options`,
|
3889 | * breadcrumbs and context (extra, tags and user) from the scope.
|
3890 | *
|
3891 | * Information that is already present in the event is never overwritten. For
|
3892 | * nested objects, such as the context, keys are merged.
|
3893 | *
|
3894 | * @param event The original event.
|
3895 | * @param hint May contain additional information about the original exception.
|
3896 | * @param scope A scope containing event metadata.
|
3897 | * @returns A new event with more information.
|
3898 | */
|
3899 | _prepareEvent(event, scope, hint) {
|
3900 | const { normalizeDepth = 3, normalizeMaxBreadth = 1000 } = this.getOptions();
|
3901 | const prepared = Object.assign(Object.assign({}, event), { event_id: event.event_id || (hint && hint.event_id ? hint.event_id : uuid4()), timestamp: event.timestamp || dateTimestampInSeconds() });
|
3902 | this._applyClientOptions(prepared);
|
3903 | this._applyIntegrationsMetadata(prepared);
|
3904 | // If we have scope given to us, use it as the base for further modifications.
|
3905 | // This allows us to prevent unnecessary copying of data if `captureContext` is not provided.
|
3906 | let finalScope = scope;
|
3907 | if (hint && hint.captureContext) {
|
3908 | finalScope = Scope.clone(finalScope).update(hint.captureContext);
|
3909 | }
|
3910 | // We prepare the result here with a resolved Event.
|
3911 | let result = resolvedSyncPromise(prepared);
|
3912 | // This should be the last thing called, since we want that
|
3913 | // {@link Hub.addEventProcessor} gets the finished prepared event.
|
3914 | if (finalScope) {
|
3915 | // In case we have a hub we reassign it.
|
3916 | result = finalScope.applyToEvent(prepared, hint);
|
3917 | }
|
3918 | return result.then(evt => {
|
3919 | if (evt) {
|
3920 | // TODO this is more of the hack trying to solve https://github.com/getsentry/sentry-javascript/issues/2809
|
3921 | // it is only attached as extra data to the event if the event somehow skips being normalized
|
3922 | evt.sdkProcessingMetadata = Object.assign(Object.assign({}, evt.sdkProcessingMetadata), { normalizeDepth: `${normalize(normalizeDepth)} (${typeof normalizeDepth})` });
|
3923 | }
|
3924 | if (typeof normalizeDepth === 'number' && normalizeDepth > 0) {
|
3925 | return this._normalizeEvent(evt, normalizeDepth, normalizeMaxBreadth);
|
3926 | }
|
3927 | return evt;
|
3928 | });
|
3929 | }
|
3930 | /**
|
3931 | * Applies `normalize` function on necessary `Event` attributes to make them safe for serialization.
|
3932 | * Normalized keys:
|
3933 | * - `breadcrumbs.data`
|
3934 | * - `user`
|
3935 | * - `contexts`
|
3936 | * - `extra`
|
3937 | * @param event Event
|
3938 | * @returns Normalized event
|
3939 | */
|
3940 | _normalizeEvent(event, depth, maxBreadth) {
|
3941 | if (!event) {
|
3942 | return null;
|
3943 | }
|
3944 | const normalized = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, event), (event.breadcrumbs && {
|
3945 | breadcrumbs: event.breadcrumbs.map(b => (Object.assign(Object.assign({}, b), (b.data && {
|
3946 | data: normalize(b.data, depth, maxBreadth),
|
3947 | })))),
|
3948 | })), (event.user && {
|
3949 | user: normalize(event.user, depth, maxBreadth),
|
3950 | })), (event.contexts && {
|
3951 | contexts: normalize(event.contexts, depth, maxBreadth),
|
3952 | })), (event.extra && {
|
3953 | extra: normalize(event.extra, depth, maxBreadth),
|
3954 | }));
|
3955 | // event.contexts.trace stores information about a Transaction. Similarly,
|
3956 | // event.spans[] stores information about child Spans. Given that a
|
3957 | // Transaction is conceptually a Span, normalization should apply to both
|
3958 | // Transactions and Spans consistently.
|
3959 | // For now the decision is to skip normalization of Transactions and Spans,
|
3960 | // so this block overwrites the normalized event to add back the original
|
3961 | // Transaction information prior to normalization.
|
3962 | if (event.contexts && event.contexts.trace) {
|
3963 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
3964 | normalized.contexts.trace = event.contexts.trace;
|
3965 | }
|
3966 | normalized.sdkProcessingMetadata = Object.assign(Object.assign({}, normalized.sdkProcessingMetadata), { baseClientNormalized: true });
|
3967 | return normalized;
|
3968 | }
|
3969 | /**
|
3970 | * Enhances event using the client configuration.
|
3971 | * It takes care of all "static" values like environment, release and `dist`,
|
3972 | * as well as truncating overly long values.
|
3973 | * @param event event instance to be enhanced
|
3974 | */
|
3975 | _applyClientOptions(event) {
|
3976 | const options = this.getOptions();
|
3977 | const { environment, release, dist, maxValueLength = 250 } = options;
|
3978 | if (!('environment' in event)) {
|
3979 | event.environment = 'environment' in options ? environment : 'production';
|
3980 | }
|
3981 | if (event.release === undefined && release !== undefined) {
|
3982 | event.release = release;
|
3983 | }
|
3984 | if (event.dist === undefined && dist !== undefined) {
|
3985 | event.dist = dist;
|
3986 | }
|
3987 | if (event.message) {
|
3988 | event.message = truncate(event.message, maxValueLength);
|
3989 | }
|
3990 | const exception = event.exception && event.exception.values && event.exception.values[0];
|
3991 | if (exception && exception.value) {
|
3992 | exception.value = truncate(exception.value, maxValueLength);
|
3993 | }
|
3994 | const request = event.request;
|
3995 | if (request && request.url) {
|
3996 | request.url = truncate(request.url, maxValueLength);
|
3997 | }
|
3998 | }
|
3999 | /**
|
4000 | * This function adds all used integrations to the SDK info in the event.
|
4001 | * @param event The event that will be filled with all integrations.
|
4002 | */
|
4003 | _applyIntegrationsMetadata(event) {
|
4004 | const integrationsArray = Object.keys(this._integrations);
|
4005 | if (integrationsArray.length > 0) {
|
4006 | event.sdk = event.sdk || {};
|
4007 | event.sdk.integrations = [...(event.sdk.integrations || []), ...integrationsArray];
|
4008 | }
|
4009 | }
|
4010 | /**
|
4011 | * Tells the backend to send this event
|
4012 | * @param event The Sentry event to send
|
4013 | */
|
4014 | _sendEvent(event) {
|
4015 | this._getBackend().sendEvent(event);
|
4016 | }
|
4017 | /**
|
4018 | * Processes the event and logs an error in case of rejection
|
4019 | * @param event
|
4020 | * @param hint
|
4021 | * @param scope
|
4022 | */
|
4023 | _captureEvent(event, hint, scope) {
|
4024 | return this._processEvent(event, hint, scope).then(finalEvent => {
|
4025 | return finalEvent.event_id;
|
4026 | }, reason => {
|
4027 | logger.error(reason);
|
4028 | return undefined;
|
4029 | });
|
4030 | }
|
4031 | /**
|
4032 | * Processes an event (either error or message) and sends it to Sentry.
|
4033 | *
|
4034 | * This also adds breadcrumbs and context information to the event. However,
|
4035 | * platform specific meta data (such as the User's IP address) must be added
|
4036 | * by the SDK implementor.
|
4037 | *
|
4038 | *
|
4039 | * @param event The event to send to Sentry.
|
4040 | * @param hint May contain additional information about the original exception.
|
4041 | * @param scope A scope containing event metadata.
|
4042 | * @returns A SyncPromise that resolves with the event or rejects in case event was/will not be send.
|
4043 | */
|
4044 | _processEvent(event, hint, scope) {
|
4045 | // eslint-disable-next-line @typescript-eslint/unbound-method
|
4046 | const { beforeSend, sampleRate } = this.getOptions();
|
4047 | const transport = this.getTransport();
|
4048 | function recordLostEvent(outcome, category) {
|
4049 | if (transport.recordLostEvent) {
|
4050 | transport.recordLostEvent(outcome, category);
|
4051 | }
|
4052 | }
|
4053 | if (!this._isEnabled()) {
|
4054 | return rejectedSyncPromise(new SentryError('SDK not enabled, will not capture event.'));
|
4055 | }
|
4056 | const isTransaction = event.type === 'transaction';
|
4057 | // 1.0 === 100% events are sent
|
4058 | // 0.0 === 0% events are sent
|
4059 | // Sampling for transaction happens somewhere else
|
4060 | if (!isTransaction && typeof sampleRate === 'number' && Math.random() > sampleRate) {
|
4061 | recordLostEvent('sample_rate', 'event');
|
4062 | return rejectedSyncPromise(new SentryError(`Discarding event because it's not included in the random sample (sampling rate = ${sampleRate})`));
|
4063 | }
|
4064 | return this._prepareEvent(event, scope, hint)
|
4065 | .then(prepared => {
|
4066 | if (prepared === null) {
|
4067 | recordLostEvent('event_processor', event.type || 'event');
|
4068 | throw new SentryError('An event processor returned null, will not send event.');
|
4069 | }
|
4070 | const isInternalException = hint && hint.data && hint.data.__sentry__ === true;
|
4071 | if (isInternalException || isTransaction || !beforeSend) {
|
4072 | return prepared;
|
4073 | }
|
4074 | const beforeSendResult = beforeSend(prepared, hint);
|
4075 | return _ensureBeforeSendRv(beforeSendResult);
|
4076 | })
|
4077 | .then(processedEvent => {
|
4078 | if (processedEvent === null) {
|
4079 | recordLostEvent('before_send', event.type || 'event');
|
4080 | throw new SentryError('`beforeSend` returned `null`, will not send event.');
|
4081 | }
|
4082 | const session = scope && scope.getSession && scope.getSession();
|
4083 | if (!isTransaction && session) {
|
4084 | this._updateSessionFromEvent(session, processedEvent);
|
4085 | }
|
4086 | this._sendEvent(processedEvent);
|
4087 | return processedEvent;
|
4088 | })
|
4089 | .then(null, reason => {
|
4090 | if (reason instanceof SentryError) {
|
4091 | throw reason;
|
4092 | }
|
4093 | this.captureException(reason, {
|
4094 | data: {
|
4095 | __sentry__: true,
|
4096 | },
|
4097 | originalException: reason,
|
4098 | });
|
4099 | throw new SentryError(`Event processing pipeline threw an error, original event will not be sent. Details have been sent as a new event.\nReason: ${reason}`);
|
4100 | });
|
4101 | }
|
4102 | /**
|
4103 | * Occupies the client with processing and event
|
4104 | */
|
4105 | _process(promise) {
|
4106 | this._numProcessing += 1;
|
4107 | void promise.then(value => {
|
4108 | this._numProcessing -= 1;
|
4109 | return value;
|
4110 | }, reason => {
|
4111 | this._numProcessing -= 1;
|
4112 | return reason;
|
4113 | });
|
4114 | }
|
4115 | }
|
4116 | /**
|
4117 | * Verifies that return value of configured `beforeSend` is of expected type.
|
4118 | */
|
4119 | function _ensureBeforeSendRv(rv) {
|
4120 | const nullErr = '`beforeSend` method has to return `null` or a valid event.';
|
4121 | if (isThenable(rv)) {
|
4122 | return rv.then(event => {
|
4123 | if (!(isPlainObject(event) || event === null)) {
|
4124 | throw new SentryError(nullErr);
|
4125 | }
|
4126 | return event;
|
4127 | }, e => {
|
4128 | throw new SentryError(`beforeSend rejected with ${e}`);
|
4129 | });
|
4130 | }
|
4131 | else if (!(isPlainObject(rv) || rv === null)) {
|
4132 | throw new SentryError(nullErr);
|
4133 | }
|
4134 | return rv;
|
4135 | }
|
4136 |
|
4137 | /** Extract sdk info from from the API metadata */
|
4138 | function getSdkMetadataForEnvelopeHeader(api) {
|
4139 | if (!api.metadata || !api.metadata.sdk) {
|
4140 | return;
|
4141 | }
|
4142 | const { name, version } = api.metadata.sdk;
|
4143 | return { name, version };
|
4144 | }
|
4145 | /**
|
4146 | * Apply SdkInfo (name, version, packages, integrations) to the corresponding event key.
|
4147 | * Merge with existing data if any.
|
4148 | **/
|
4149 | function enhanceEventWithSdkInfo(event, sdkInfo) {
|
4150 | if (!sdkInfo) {
|
4151 | return event;
|
4152 | }
|
4153 | event.sdk = event.sdk || {};
|
4154 | event.sdk.name = event.sdk.name || sdkInfo.name;
|
4155 | event.sdk.version = event.sdk.version || sdkInfo.version;
|
4156 | event.sdk.integrations = [...(event.sdk.integrations || []), ...(sdkInfo.integrations || [])];
|
4157 | event.sdk.packages = [...(event.sdk.packages || []), ...(sdkInfo.packages || [])];
|
4158 | return event;
|
4159 | }
|
4160 | /** Creates an envelope from a Session */
|
4161 | function createSessionEnvelope(session, api) {
|
4162 | const sdkInfo = getSdkMetadataForEnvelopeHeader(api);
|
4163 | const envelopeHeaders = Object.assign(Object.assign({ sent_at: new Date().toISOString() }, (sdkInfo && { sdk: sdkInfo })), (!!api.tunnel && { dsn: dsnToString(api.dsn) }));
|
4164 | // I know this is hacky but we don't want to add `sessions` to request type since it's never rate limited
|
4165 | const type = 'aggregates' in session ? 'sessions' : 'session';
|
4166 | // TODO (v7) Have to cast type because envelope items do not accept a `SentryRequestType`
|
4167 | const envelopeItem = [{ type }, session];
|
4168 | const envelope = createEnvelope(envelopeHeaders, [envelopeItem]);
|
4169 | return [envelope, type];
|
4170 | }
|
4171 | /** Creates a SentryRequest from a Session. */
|
4172 | function sessionToSentryRequest(session, api) {
|
4173 | const [envelope, type] = createSessionEnvelope(session, api);
|
4174 | return {
|
4175 | body: serializeEnvelope(envelope),
|
4176 | type,
|
4177 | url: getEnvelopeEndpointWithUrlEncodedAuth(api.dsn, api.tunnel),
|
4178 | };
|
4179 | }
|
4180 | /**
|
4181 | * Create an Envelope from an event. Note that this is duplicated from below,
|
4182 | * but on purpose as this will be refactored in v7.
|
4183 | */
|
4184 | function createEventEnvelope(event, api) {
|
4185 | const sdkInfo = getSdkMetadataForEnvelopeHeader(api);
|
4186 | const eventType = event.type || 'event';
|
4187 | const { transactionSampling } = event.sdkProcessingMetadata || {};
|
4188 | const { method: samplingMethod, rate: sampleRate } = transactionSampling || {};
|
4189 | // TODO: Below is a temporary hack in order to debug a serialization error - see
|
4190 | // https://github.com/getsentry/sentry-javascript/issues/2809,
|
4191 | // https://github.com/getsentry/sentry-javascript/pull/4425, and
|
4192 | // https://github.com/getsentry/sentry-javascript/pull/4574.
|
4193 | //
|
4194 | // TL; DR: even though we normalize all events (which should prevent this), something is causing `JSON.stringify` to
|
4195 | // throw a circular reference error.
|
4196 | //
|
4197 | // When it's time to remove it:
|
4198 | // 1. Delete everything between here and where the request object `req` is created, EXCEPT the line deleting
|
4199 | // `sdkProcessingMetadata`
|
4200 | // 2. Restore the original version of the request body, which is commented out
|
4201 | // 3. Search for either of the PR URLs above and pull out the companion hacks in the browser playwright tests and the
|
4202 | // baseClient tests in this package
|
4203 | enhanceEventWithSdkInfo(event, api.metadata.sdk);
|
4204 | event.tags = event.tags || {};
|
4205 | event.extra = event.extra || {};
|
4206 | // In theory, all events should be marked as having gone through normalization and so
|
4207 | // we should never set this tag/extra data
|
4208 | if (!(event.sdkProcessingMetadata && event.sdkProcessingMetadata.baseClientNormalized)) {
|
4209 | event.tags.skippedNormalization = true;
|
4210 | event.extra.normalizeDepth = event.sdkProcessingMetadata ? event.sdkProcessingMetadata.normalizeDepth : 'unset';
|
4211 | }
|
4212 | // prevent this data from being sent to sentry
|
4213 | // TODO: This is NOT part of the hack - DO NOT DELETE
|
4214 | delete event.sdkProcessingMetadata;
|
4215 | const envelopeHeaders = Object.assign(Object.assign({ event_id: event.event_id, sent_at: new Date().toISOString() }, (sdkInfo && { sdk: sdkInfo })), (!!api.tunnel && { dsn: dsnToString(api.dsn) }));
|
4216 | const eventItem = [
|
4217 | {
|
4218 | type: eventType,
|
4219 | sample_rates: [{ id: samplingMethod, rate: sampleRate }],
|
4220 | },
|
4221 | event,
|
4222 | ];
|
4223 | return createEnvelope(envelopeHeaders, [eventItem]);
|
4224 | }
|
4225 | /** Creates a SentryRequest from an event. */
|
4226 | function eventToSentryRequest(event, api) {
|
4227 | const sdkInfo = getSdkMetadataForEnvelopeHeader(api);
|
4228 | const eventType = event.type || 'event';
|
4229 | const useEnvelope = eventType === 'transaction' || !!api.tunnel;
|
4230 | const { transactionSampling } = event.sdkProcessingMetadata || {};
|
4231 | const { method: samplingMethod, rate: sampleRate } = transactionSampling || {};
|
4232 | // TODO: Below is a temporary hack in order to debug a serialization error - see
|
4233 | // https://github.com/getsentry/sentry-javascript/issues/2809,
|
4234 | // https://github.com/getsentry/sentry-javascript/pull/4425, and
|
4235 | // https://github.com/getsentry/sentry-javascript/pull/4574.
|
4236 | //
|
4237 | // TL; DR: even though we normalize all events (which should prevent this), something is causing `JSON.stringify` to
|
4238 | // throw a circular reference error.
|
4239 | //
|
4240 | // When it's time to remove it:
|
4241 | // 1. Delete everything between here and where the request object `req` is created, EXCEPT the line deleting
|
4242 | // `sdkProcessingMetadata`
|
4243 | // 2. Restore the original version of the request body, which is commented out
|
4244 | // 3. Search for either of the PR URLs above and pull out the companion hacks in the browser playwright tests and the
|
4245 | // baseClient tests in this package
|
4246 | enhanceEventWithSdkInfo(event, api.metadata.sdk);
|
4247 | event.tags = event.tags || {};
|
4248 | event.extra = event.extra || {};
|
4249 | // In theory, all events should be marked as having gone through normalization and so
|
4250 | // we should never set this tag/extra data
|
4251 | if (!(event.sdkProcessingMetadata && event.sdkProcessingMetadata.baseClientNormalized)) {
|
4252 | event.tags.skippedNormalization = true;
|
4253 | event.extra.normalizeDepth = event.sdkProcessingMetadata ? event.sdkProcessingMetadata.normalizeDepth : 'unset';
|
4254 | }
|
4255 | // prevent this data from being sent to sentry
|
4256 | // TODO: This is NOT part of the hack - DO NOT DELETE
|
4257 | delete event.sdkProcessingMetadata;
|
4258 | let body;
|
4259 | try {
|
4260 | // 99.9% of events should get through just fine - no change in behavior for them
|
4261 | body = JSON.stringify(event);
|
4262 | }
|
4263 | catch (err) {
|
4264 | // Record data about the error without replacing original event data, then force renormalization
|
4265 | event.tags.JSONStringifyError = true;
|
4266 | event.extra.JSONStringifyError = err;
|
4267 | try {
|
4268 | body = JSON.stringify(normalize(event));
|
4269 | }
|
4270 | catch (newErr) {
|
4271 | // At this point even renormalization hasn't worked, meaning something about the event data has gone very wrong.
|
4272 | // Time to cut our losses and record only the new error. With luck, even in the problematic cases we're trying to
|
4273 | // debug with this hack, we won't ever land here.
|
4274 | const innerErr = newErr;
|
4275 | body = JSON.stringify({
|
4276 | message: 'JSON.stringify error after renormalization',
|
4277 | // setting `extra: { innerErr }` here for some reason results in an empty object, so unpack manually
|
4278 | extra: { message: innerErr.message, stack: innerErr.stack },
|
4279 | });
|
4280 | }
|
4281 | }
|
4282 | const req = {
|
4283 | // this is the relevant line of code before the hack was added, to make it easy to undo said hack once we've solved
|
4284 | // the mystery
|
4285 | // body: JSON.stringify(sdkInfo ? enhanceEventWithSdkInfo(event, api.metadata.sdk) : event),
|
4286 | body,
|
4287 | type: eventType,
|
4288 | url: useEnvelope
|
4289 | ? getEnvelopeEndpointWithUrlEncodedAuth(api.dsn, api.tunnel)
|
4290 | : getStoreEndpointWithUrlEncodedAuth(api.dsn),
|
4291 | };
|
4292 | // https://develop.sentry.dev/sdk/envelopes/
|
4293 | // Since we don't need to manipulate envelopes nor store them, there is no
|
4294 | // exported concept of an Envelope with operations including serialization and
|
4295 | // deserialization. Instead, we only implement a minimal subset of the spec to
|
4296 | // serialize events inline here.
|
4297 | if (useEnvelope) {
|
4298 | const envelopeHeaders = Object.assign(Object.assign({ event_id: event.event_id, sent_at: new Date().toISOString() }, (sdkInfo && { sdk: sdkInfo })), (!!api.tunnel && { dsn: dsnToString(api.dsn) }));
|
4299 | const eventItem = [
|
4300 | {
|
4301 | type: eventType,
|
4302 | sample_rates: [{ id: samplingMethod, rate: sampleRate }],
|
4303 | },
|
4304 | req.body,
|
4305 | ];
|
4306 | const envelope = createEnvelope(envelopeHeaders, [eventItem]);
|
4307 | req.body = serializeEnvelope(envelope);
|
4308 | }
|
4309 | return req;
|
4310 | }
|
4311 |
|
4312 | /** Noop transport */
|
4313 | class NoopTransport {
|
4314 | /**
|
4315 | * @inheritDoc
|
4316 | */
|
4317 | sendEvent(_) {
|
4318 | return resolvedSyncPromise({
|
4319 | reason: 'NoopTransport: Event has been skipped because no Dsn is configured.',
|
4320 | status: 'skipped',
|
4321 | });
|
4322 | }
|
4323 | /**
|
4324 | * @inheritDoc
|
4325 | */
|
4326 | close(_) {
|
4327 | return resolvedSyncPromise(true);
|
4328 | }
|
4329 | }
|
4330 |
|
4331 | /**
|
4332 | * This is the base implemention of a Backend.
|
4333 | * @hidden
|
4334 | */
|
4335 | class BaseBackend {
|
4336 | /** Creates a new backend instance. */
|
4337 | constructor(options) {
|
4338 | this._options = options;
|
4339 | if (!this._options.dsn) {
|
4340 | logger.warn('No DSN provided, backend will not do anything.');
|
4341 | }
|
4342 | this._transport = this._setupTransport();
|
4343 | }
|
4344 | /**
|
4345 | * @inheritDoc
|
4346 | */
|
4347 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
4348 | eventFromException(_exception, _hint) {
|
4349 | throw new SentryError('Backend has to implement `eventFromException` method');
|
4350 | }
|
4351 | /**
|
4352 | * @inheritDoc
|
4353 | */
|
4354 | eventFromMessage(_message, _level, _hint) {
|
4355 | throw new SentryError('Backend has to implement `eventFromMessage` method');
|
4356 | }
|
4357 | /**
|
4358 | * @inheritDoc
|
4359 | */
|
4360 | sendEvent(event) {
|
4361 | // TODO(v7): Remove the if-else
|
4362 | if (this._newTransport &&
|
4363 | this._options.dsn &&
|
4364 | this._options._experiments &&
|
4365 | this._options._experiments.newTransport) {
|
4366 | const api = initAPIDetails(this._options.dsn, this._options._metadata, this._options.tunnel);
|
4367 | const env = createEventEnvelope(event, api);
|
4368 | void this._newTransport.send(env).then(null, reason => {
|
4369 | logger.error('Error while sending event:', reason);
|
4370 | });
|
4371 | }
|
4372 | else {
|
4373 | void this._transport.sendEvent(event).then(null, reason => {
|
4374 | logger.error('Error while sending event:', reason);
|
4375 | });
|
4376 | }
|
4377 | }
|
4378 | /**
|
4379 | * @inheritDoc
|
4380 | */
|
4381 | sendSession(session) {
|
4382 | if (!this._transport.sendSession) {
|
4383 | logger.warn("Dropping session because custom transport doesn't implement sendSession");
|
4384 | return;
|
4385 | }
|
4386 | // TODO(v7): Remove the if-else
|
4387 | if (this._newTransport &&
|
4388 | this._options.dsn &&
|
4389 | this._options._experiments &&
|
4390 | this._options._experiments.newTransport) {
|
4391 | const api = initAPIDetails(this._options.dsn, this._options._metadata, this._options.tunnel);
|
4392 | const [env] = createSessionEnvelope(session, api);
|
4393 | void this._newTransport.send(env).then(null, reason => {
|
4394 | logger.error('Error while sending session:', reason);
|
4395 | });
|
4396 | }
|
4397 | else {
|
4398 | void this._transport.sendSession(session).then(null, reason => {
|
4399 | logger.error('Error while sending session:', reason);
|
4400 | });
|
4401 | }
|
4402 | }
|
4403 | /**
|
4404 | * @inheritDoc
|
4405 | */
|
4406 | getTransport() {
|
4407 | return this._transport;
|
4408 | }
|
4409 | /**
|
4410 | * Sets up the transport so it can be used later to send requests.
|
4411 | */
|
4412 | _setupTransport() {
|
4413 | return new NoopTransport();
|
4414 | }
|
4415 | }
|
4416 |
|
4417 | /**
|
4418 | * Internal function to create a new SDK client instance. The client is
|
4419 | * installed and then bound to the current scope.
|
4420 | *
|
4421 | * @param clientClass The client class to instantiate.
|
4422 | * @param options Options to pass to the client.
|
4423 | */
|
4424 | function initAndBind(clientClass, options) {
|
4425 | if (options.debug === true) {
|
4426 | if (IS_DEBUG_BUILD$1) {
|
4427 | logger.enable();
|
4428 | }
|
4429 | else {
|
4430 | // use `console.warn` rather than `logger.warn` since by non-debug bundles have all `logger.x` statements stripped
|
4431 | // eslint-disable-next-line no-console
|
4432 | console.warn('[Sentry] Cannot initialize SDK with `debug` option using a non-debug bundle.');
|
4433 | }
|
4434 | }
|
4435 | const hub = getCurrentHub();
|
4436 | const scope = hub.getScope();
|
4437 | if (scope) {
|
4438 | scope.update(options.initialScope);
|
4439 | }
|
4440 | const client = new clientClass(options);
|
4441 | hub.bindClient(client);
|
4442 | }
|
4443 |
|
4444 | const DEFAULT_TRANSPORT_BUFFER_SIZE = 30;
|
4445 | /**
|
4446 | * Creates a `NewTransport`
|
4447 | *
|
4448 | * @param options
|
4449 | * @param makeRequest
|
4450 | */
|
4451 | function createTransport(options, makeRequest, buffer = makePromiseBuffer(options.bufferSize || DEFAULT_TRANSPORT_BUFFER_SIZE)) {
|
4452 | let rateLimits = {};
|
4453 | const flush = (timeout) => buffer.drain(timeout);
|
4454 | function send(envelope) {
|
4455 | const envCategory = getEnvelopeType(envelope);
|
4456 | const category = envCategory === 'event' ? 'error' : envCategory;
|
4457 | const request = {
|
4458 | category,
|
4459 | body: serializeEnvelope(envelope),
|
4460 | };
|
4461 | // Don't add to buffer if transport is already rate-limited
|
4462 | if (isRateLimited(rateLimits, category)) {
|
4463 | return rejectedSyncPromise({
|
4464 | status: 'rate_limit',
|
4465 | reason: getRateLimitReason(rateLimits, category),
|
4466 | });
|
4467 | }
|
4468 | const requestTask = () => makeRequest(request).then(({ body, headers, reason, statusCode }) => {
|
4469 | const status = eventStatusFromHttpCode(statusCode);
|
4470 | if (headers) {
|
4471 | rateLimits = updateRateLimits(rateLimits, headers);
|
4472 | }
|
4473 | if (status === 'success') {
|
4474 | return resolvedSyncPromise({ status, reason });
|
4475 | }
|
4476 | return rejectedSyncPromise({
|
4477 | status,
|
4478 | reason: reason ||
|
4479 | body ||
|
4480 | (status === 'rate_limit' ? getRateLimitReason(rateLimits, category) : 'Unknown transport error'),
|
4481 | });
|
4482 | });
|
4483 | return buffer.add(requestTask);
|
4484 | }
|
4485 | return {
|
4486 | send,
|
4487 | flush,
|
4488 | };
|
4489 | }
|
4490 | function getRateLimitReason(rateLimits, category) {
|
4491 | return `Too many ${category} requests, backing off until: ${new Date(disabledUntil(rateLimits, category)).toISOString()}`;
|
4492 | }
|
4493 |
|
4494 | const SDK_VERSION = '6.19.7';
|
4495 |
|
4496 | let originalFunctionToString;
|
4497 | /** Patch toString calls to return proper name for wrapped functions */
|
4498 | class FunctionToString {
|
4499 | constructor() {
|
4500 | /**
|
4501 | * @inheritDoc
|
4502 | */
|
4503 | this.name = FunctionToString.id;
|
4504 | }
|
4505 | /**
|
4506 | * @inheritDoc
|
4507 | */
|
4508 | setupOnce() {
|
4509 | // eslint-disable-next-line @typescript-eslint/unbound-method
|
4510 | originalFunctionToString = Function.prototype.toString;
|
4511 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
4512 | Function.prototype.toString = function (...args) {
|
4513 | const context = getOriginalFunction(this) || this;
|
4514 | return originalFunctionToString.apply(context, args);
|
4515 | };
|
4516 | }
|
4517 | }
|
4518 | /**
|
4519 | * @inheritDoc
|
4520 | */
|
4521 | FunctionToString.id = 'FunctionToString';
|
4522 |
|
4523 | // "Script error." is hard coded into browsers for errors that it can't read.
|
4524 | // this is the result of a script being pulled in from an external domain and CORS.
|
4525 | const DEFAULT_IGNORE_ERRORS = [/^Script error\.?$/, /^Javascript error: Script error\.? on line 0$/];
|
4526 | /** Inbound filters configurable by the user */
|
4527 | class InboundFilters {
|
4528 | constructor(_options = {}) {
|
4529 | this._options = _options;
|
4530 | /**
|
4531 | * @inheritDoc
|
4532 | */
|
4533 | this.name = InboundFilters.id;
|
4534 | }
|
4535 | /**
|
4536 | * @inheritDoc
|
4537 | */
|
4538 | setupOnce(addGlobalEventProcessor, getCurrentHub) {
|
4539 | addGlobalEventProcessor((event) => {
|
4540 | const hub = getCurrentHub();
|
4541 | if (hub) {
|
4542 | const self = hub.getIntegration(InboundFilters);
|
4543 | if (self) {
|
4544 | const client = hub.getClient();
|
4545 | const clientOptions = client ? client.getOptions() : {};
|
4546 | const options = _mergeOptions(self._options, clientOptions);
|
4547 | return _shouldDropEvent$1(event, options) ? null : event;
|
4548 | }
|
4549 | }
|
4550 | return event;
|
4551 | });
|
4552 | }
|
4553 | }
|
4554 | /**
|
4555 | * @inheritDoc
|
4556 | */
|
4557 | InboundFilters.id = 'InboundFilters';
|
4558 | /** JSDoc */
|
4559 | function _mergeOptions(internalOptions = {}, clientOptions = {}) {
|
4560 | return {
|
4561 | allowUrls: [
|
4562 | // eslint-disable-next-line deprecation/deprecation
|
4563 | ...(internalOptions.whitelistUrls || []),
|
4564 | ...(internalOptions.allowUrls || []),
|
4565 | // eslint-disable-next-line deprecation/deprecation
|
4566 | ...(clientOptions.whitelistUrls || []),
|
4567 | ...(clientOptions.allowUrls || []),
|
4568 | ],
|
4569 | denyUrls: [
|
4570 | // eslint-disable-next-line deprecation/deprecation
|
4571 | ...(internalOptions.blacklistUrls || []),
|
4572 | ...(internalOptions.denyUrls || []),
|
4573 | // eslint-disable-next-line deprecation/deprecation
|
4574 | ...(clientOptions.blacklistUrls || []),
|
4575 | ...(clientOptions.denyUrls || []),
|
4576 | ],
|
4577 | ignoreErrors: [
|
4578 | ...(internalOptions.ignoreErrors || []),
|
4579 | ...(clientOptions.ignoreErrors || []),
|
4580 | ...DEFAULT_IGNORE_ERRORS,
|
4581 | ],
|
4582 | ignoreInternal: internalOptions.ignoreInternal !== undefined ? internalOptions.ignoreInternal : true,
|
4583 | };
|
4584 | }
|
4585 | /** JSDoc */
|
4586 | function _shouldDropEvent$1(event, options) {
|
4587 | if (options.ignoreInternal && _isSentryError(event)) {
|
4588 | IS_DEBUG_BUILD$1 &&
|
4589 | logger.warn(`Event dropped due to being internal Sentry Error.\nEvent: ${getEventDescription(event)}`);
|
4590 | return true;
|
4591 | }
|
4592 | if (_isIgnoredError(event, options.ignoreErrors)) {
|
4593 | IS_DEBUG_BUILD$1 &&
|
4594 | logger.warn(`Event dropped due to being matched by \`ignoreErrors\` option.\nEvent: ${getEventDescription(event)}`);
|
4595 | return true;
|
4596 | }
|
4597 | if (_isDeniedUrl(event, options.denyUrls)) {
|
4598 | IS_DEBUG_BUILD$1 &&
|
4599 | logger.warn(`Event dropped due to being matched by \`denyUrls\` option.\nEvent: ${getEventDescription(event)}.\nUrl: ${_getEventFilterUrl(event)}`);
|
4600 | return true;
|
4601 | }
|
4602 | if (!_isAllowedUrl(event, options.allowUrls)) {
|
4603 | IS_DEBUG_BUILD$1 &&
|
4604 | logger.warn(`Event dropped due to not being matched by \`allowUrls\` option.\nEvent: ${getEventDescription(event)}.\nUrl: ${_getEventFilterUrl(event)}`);
|
4605 | return true;
|
4606 | }
|
4607 | return false;
|
4608 | }
|
4609 | function _isIgnoredError(event, ignoreErrors) {
|
4610 | if (!ignoreErrors || !ignoreErrors.length) {
|
4611 | return false;
|
4612 | }
|
4613 | return _getPossibleEventMessages(event).some(message => ignoreErrors.some(pattern => isMatchingPattern(message, pattern)));
|
4614 | }
|
4615 | function _isDeniedUrl(event, denyUrls) {
|
4616 | // TODO: Use Glob instead?
|
4617 | if (!denyUrls || !denyUrls.length) {
|
4618 | return false;
|
4619 | }
|
4620 | const url = _getEventFilterUrl(event);
|
4621 | return !url ? false : denyUrls.some(pattern => isMatchingPattern(url, pattern));
|
4622 | }
|
4623 | function _isAllowedUrl(event, allowUrls) {
|
4624 | // TODO: Use Glob instead?
|
4625 | if (!allowUrls || !allowUrls.length) {
|
4626 | return true;
|
4627 | }
|
4628 | const url = _getEventFilterUrl(event);
|
4629 | return !url ? true : allowUrls.some(pattern => isMatchingPattern(url, pattern));
|
4630 | }
|
4631 | function _getPossibleEventMessages(event) {
|
4632 | if (event.message) {
|
4633 | return [event.message];
|
4634 | }
|
4635 | if (event.exception) {
|
4636 | try {
|
4637 | const { type = '', value = '' } = (event.exception.values && event.exception.values[0]) || {};
|
4638 | return [`${value}`, `${type}: ${value}`];
|
4639 | }
|
4640 | catch (oO) {
|
4641 | IS_DEBUG_BUILD$1 && logger.error(`Cannot extract message for event ${getEventDescription(event)}`);
|
4642 | return [];
|
4643 | }
|
4644 | }
|
4645 | return [];
|
4646 | }
|
4647 | function _isSentryError(event) {
|
4648 | try {
|
4649 | // @ts-ignore can't be a sentry error if undefined
|
4650 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
4651 | return event.exception.values[0].type === 'SentryError';
|
4652 | }
|
4653 | catch (e) {
|
4654 | // ignore
|
4655 | }
|
4656 | return false;
|
4657 | }
|
4658 | function _getLastValidUrl(frames = []) {
|
4659 | for (let i = frames.length - 1; i >= 0; i--) {
|
4660 | const frame = frames[i];
|
4661 | if (frame && frame.filename !== '<anonymous>' && frame.filename !== '[native code]') {
|
4662 | return frame.filename || null;
|
4663 | }
|
4664 | }
|
4665 | return null;
|
4666 | }
|
4667 | function _getEventFilterUrl(event) {
|
4668 | try {
|
4669 | if (event.stacktrace) {
|
4670 | return _getLastValidUrl(event.stacktrace.frames);
|
4671 | }
|
4672 | let frames;
|
4673 | try {
|
4674 | // @ts-ignore we only care about frames if the whole thing here is defined
|
4675 | frames = event.exception.values[0].stacktrace.frames;
|
4676 | }
|
4677 | catch (e) {
|
4678 | // ignore
|
4679 | }
|
4680 | return frames ? _getLastValidUrl(frames) : null;
|
4681 | }
|
4682 | catch (oO) {
|
4683 | IS_DEBUG_BUILD$1 && logger.error(`Cannot extract url for event ${getEventDescription(event)}`);
|
4684 | return null;
|
4685 | }
|
4686 | }
|
4687 |
|
4688 | var CoreIntegrations = /*#__PURE__*/Object.freeze({
|
4689 | __proto__: null,
|
4690 | FunctionToString: FunctionToString,
|
4691 | InboundFilters: InboundFilters
|
4692 | });
|
4693 |
|
4694 | // global reference to slice
|
4695 | const UNKNOWN_FUNCTION = '?';
|
4696 | const OPERA10_PRIORITY = 10;
|
4697 | const OPERA11_PRIORITY = 20;
|
4698 | const CHROME_PRIORITY = 30;
|
4699 | const WINJS_PRIORITY = 40;
|
4700 | const GECKO_PRIORITY = 50;
|
4701 | function createFrame(filename, func, lineno, colno) {
|
4702 | const frame = {
|
4703 | filename,
|
4704 | function: func,
|
4705 | // All browser frames are considered in_app
|
4706 | in_app: true,
|
4707 | };
|
4708 | if (lineno !== undefined) {
|
4709 | frame.lineno = lineno;
|
4710 | }
|
4711 | if (colno !== undefined) {
|
4712 | frame.colno = colno;
|
4713 | }
|
4714 | return frame;
|
4715 | }
|
4716 | // Chromium based browsers: Chrome, Brave, new Opera, new Edge
|
4717 | const chromeRegex = /^\s*at (?:(.*?) ?\((?:address at )?)?((?:file|https?|blob|chrome-extension|address|native|eval|webpack|<anonymous>|[-a-z]+:|.*bundle|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i;
|
4718 | const chromeEvalRegex = /\((\S*)(?::(\d+))(?::(\d+))\)/;
|
4719 | const chrome = line => {
|
4720 | const parts = chromeRegex.exec(line);
|
4721 | if (parts) {
|
4722 | const isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line
|
4723 | if (isEval) {
|
4724 | const subMatch = chromeEvalRegex.exec(parts[2]);
|
4725 | if (subMatch) {
|
4726 | // throw out eval line/column and use top-most line/column number
|
4727 | parts[2] = subMatch[1]; // url
|
4728 | parts[3] = subMatch[2]; // line
|
4729 | parts[4] = subMatch[3]; // column
|
4730 | }
|
4731 | }
|
4732 | // Kamil: One more hack won't hurt us right? Understanding and adding more rules on top of these regexps right now
|
4733 | // would be way too time consuming. (TODO: Rewrite whole RegExp to be more readable)
|
4734 | const [func, filename] = extractSafariExtensionDetails(parts[1] || UNKNOWN_FUNCTION, parts[2]);
|
4735 | return createFrame(filename, func, parts[3] ? +parts[3] : undefined, parts[4] ? +parts[4] : undefined);
|
4736 | }
|
4737 | return;
|
4738 | };
|
4739 | const chromeStackParser = [CHROME_PRIORITY, chrome];
|
4740 | // gecko regex: `(?:bundle|\d+\.js)`: `bundle` is for react native, `\d+\.js` also but specifically for ram bundles because it
|
4741 | // generates filenames without a prefix like `file://` the filenames in the stacktrace are just 42.js
|
4742 | // We need this specific case for now because we want no other regex to match.
|
4743 | const geckoREgex = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)?((?:file|https?|blob|chrome|webpack|resource|moz-extension|capacitor).*?:\/.*?|\[native code\]|[^@]*(?:bundle|\d+\.js)|\/[\w\-. /=]+)(?::(\d+))?(?::(\d+))?\s*$/i;
|
4744 | const geckoEvalRegex = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i;
|
4745 | const gecko = line => {
|
4746 | const parts = geckoREgex.exec(line);
|
4747 | if (parts) {
|
4748 | const isEval = parts[3] && parts[3].indexOf(' > eval') > -1;
|
4749 | if (isEval) {
|
4750 | const subMatch = geckoEvalRegex.exec(parts[3]);
|
4751 | if (subMatch) {
|
4752 | // throw out eval line/column and use top-most line number
|
4753 | parts[1] = parts[1] || 'eval';
|
4754 | parts[3] = subMatch[1];
|
4755 | parts[4] = subMatch[2];
|
4756 | parts[5] = ''; // no column when eval
|
4757 | }
|
4758 | }
|
4759 | let filename = parts[3];
|
4760 | let func = parts[1] || UNKNOWN_FUNCTION;
|
4761 | [func, filename] = extractSafariExtensionDetails(func, filename);
|
4762 | return createFrame(filename, func, parts[4] ? +parts[4] : undefined, parts[5] ? +parts[5] : undefined);
|
4763 | }
|
4764 | return;
|
4765 | };
|
4766 | const geckoStackParser = [GECKO_PRIORITY, gecko];
|
4767 | const winjsRegex = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i;
|
4768 | const winjs = line => {
|
4769 | const parts = winjsRegex.exec(line);
|
4770 | return parts
|
4771 | ? createFrame(parts[2], parts[1] || UNKNOWN_FUNCTION, +parts[3], parts[4] ? +parts[4] : undefined)
|
4772 | : undefined;
|
4773 | };
|
4774 | const winjsStackParser = [WINJS_PRIORITY, winjs];
|
4775 | const opera10Regex = / line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i;
|
4776 | const opera10 = line => {
|
4777 | const parts = opera10Regex.exec(line);
|
4778 | return parts ? createFrame(parts[2], parts[3] || UNKNOWN_FUNCTION, +parts[1]) : undefined;
|
4779 | };
|
4780 | const opera10StackParser = [OPERA10_PRIORITY, opera10];
|
4781 | const opera11Regex = / line (\d+), column (\d+)\s*(?:in (?:<anonymous function: ([^>]+)>|([^)]+))\(.*\))? in (.*):\s*$/i;
|
4782 | const opera11 = line => {
|
4783 | const parts = opera11Regex.exec(line);
|
4784 | return parts ? createFrame(parts[5], parts[3] || parts[4] || UNKNOWN_FUNCTION, +parts[1], +parts[2]) : undefined;
|
4785 | };
|
4786 | const opera11StackParser = [OPERA11_PRIORITY, opera11];
|
4787 | /**
|
4788 | * Safari web extensions, starting version unknown, can produce "frames-only" stacktraces.
|
4789 | * What it means, is that instead of format like:
|
4790 | *
|
4791 | * Error: wat
|
4792 | * at function@url:row:col
|
4793 | * at function@url:row:col
|
4794 | * at function@url:row:col
|
4795 | *
|
4796 | * it produces something like:
|
4797 | *
|
4798 | * function@url:row:col
|
4799 | * function@url:row:col
|
4800 | * function@url:row:col
|
4801 | *
|
4802 | * Because of that, it won't be captured by `chrome` RegExp and will fall into `Gecko` branch.
|
4803 | * This function is extracted so that we can use it in both places without duplicating the logic.
|
4804 | * Unfortunately "just" changing RegExp is too complicated now and making it pass all tests
|
4805 | * and fix this case seems like an impossible, or at least way too time-consuming task.
|
4806 | */
|
4807 | const extractSafariExtensionDetails = (func, filename) => {
|
4808 | const isSafariExtension = func.indexOf('safari-extension') !== -1;
|
4809 | const isSafariWebExtension = func.indexOf('safari-web-extension') !== -1;
|
4810 | return isSafariExtension || isSafariWebExtension
|
4811 | ? [
|
4812 | func.indexOf('@') !== -1 ? func.split('@')[0] : UNKNOWN_FUNCTION,
|
4813 | isSafariExtension ? `safari-extension:${filename}` : `safari-web-extension:${filename}`,
|
4814 | ]
|
4815 | : [func, filename];
|
4816 | };
|
4817 |
|
4818 | /**
|
4819 | * This function creates an exception from an TraceKitStackTrace
|
4820 | * @param stacktrace TraceKitStackTrace that will be converted to an exception
|
4821 | * @hidden
|
4822 | */
|
4823 | function exceptionFromError(ex) {
|
4824 | // Get the frames first since Opera can lose the stack if we touch anything else first
|
4825 | const frames = parseStackFrames(ex);
|
4826 | const exception = {
|
4827 | type: ex && ex.name,
|
4828 | value: extractMessage(ex),
|
4829 | };
|
4830 | if (frames.length) {
|
4831 | exception.stacktrace = { frames };
|
4832 | }
|
4833 | if (exception.type === undefined && exception.value === '') {
|
4834 | exception.value = 'Unrecoverable error caught';
|
4835 | }
|
4836 | return exception;
|
4837 | }
|
4838 | /**
|
4839 | * @hidden
|
4840 | */
|
4841 | function eventFromPlainObject(exception, syntheticException, isUnhandledRejection) {
|
4842 | const event = {
|
4843 | exception: {
|
4844 | values: [
|
4845 | {
|
4846 | type: isEvent(exception) ? exception.constructor.name : isUnhandledRejection ? 'UnhandledRejection' : 'Error',
|
4847 | value: `Non-Error ${isUnhandledRejection ? 'promise rejection' : 'exception'} captured with keys: ${extractExceptionKeysForMessage(exception)}`,
|
4848 | },
|
4849 | ],
|
4850 | },
|
4851 | extra: {
|
4852 | __serialized__: normalizeToSize(exception),
|
4853 | },
|
4854 | };
|
4855 | if (syntheticException) {
|
4856 | const frames = parseStackFrames(syntheticException);
|
4857 | if (frames.length) {
|
4858 | event.stacktrace = { frames };
|
4859 | }
|
4860 | }
|
4861 | return event;
|
4862 | }
|
4863 | /**
|
4864 | * @hidden
|
4865 | */
|
4866 | function eventFromError(ex) {
|
4867 | return {
|
4868 | exception: {
|
4869 | values: [exceptionFromError(ex)],
|
4870 | },
|
4871 | };
|
4872 | }
|
4873 | /** Parses stack frames from an error */
|
4874 | function parseStackFrames(ex) {
|
4875 | // Access and store the stacktrace property before doing ANYTHING
|
4876 | // else to it because Opera is not very good at providing it
|
4877 | // reliably in other circumstances.
|
4878 | const stacktrace = ex.stacktrace || ex.stack || '';
|
4879 | const popSize = getPopSize(ex);
|
4880 | try {
|
4881 | return createStackParser(opera10StackParser, opera11StackParser, chromeStackParser, winjsStackParser, geckoStackParser)(stacktrace, popSize);
|
4882 | }
|
4883 | catch (e) {
|
4884 | // no-empty
|
4885 | }
|
4886 | return [];
|
4887 | }
|
4888 | // Based on our own mapping pattern - https://github.com/getsentry/sentry/blob/9f08305e09866c8bd6d0c24f5b0aabdd7dd6c59c/src/sentry/lang/javascript/errormapping.py#L83-L108
|
4889 | const reactMinifiedRegexp = /Minified React error #\d+;/i;
|
4890 | function getPopSize(ex) {
|
4891 | if (ex) {
|
4892 | if (typeof ex.framesToPop === 'number') {
|
4893 | return ex.framesToPop;
|
4894 | }
|
4895 | if (reactMinifiedRegexp.test(ex.message)) {
|
4896 | return 1;
|
4897 | }
|
4898 | }
|
4899 | return 0;
|
4900 | }
|
4901 | /**
|
4902 | * There are cases where stacktrace.message is an Event object
|
4903 | * https://github.com/getsentry/sentry-javascript/issues/1949
|
4904 | * In this specific case we try to extract stacktrace.message.error.message
|
4905 | */
|
4906 | function extractMessage(ex) {
|
4907 | const message = ex && ex.message;
|
4908 | if (!message) {
|
4909 | return 'No error message';
|
4910 | }
|
4911 | if (message.error && typeof message.error.message === 'string') {
|
4912 | return message.error.message;
|
4913 | }
|
4914 | return message;
|
4915 | }
|
4916 | /**
|
4917 | * Creates an {@link Event} from all inputs to `captureException` and non-primitive inputs to `captureMessage`.
|
4918 | * @hidden
|
4919 | */
|
4920 | function eventFromException(exception, hint, attachStacktrace) {
|
4921 | const syntheticException = (hint && hint.syntheticException) || undefined;
|
4922 | const event = eventFromUnknownInput(exception, syntheticException, attachStacktrace);
|
4923 | addExceptionMechanism(event); // defaults to { type: 'generic', handled: true }
|
4924 | event.level = exports.Severity.Error;
|
4925 | if (hint && hint.event_id) {
|
4926 | event.event_id = hint.event_id;
|
4927 | }
|
4928 | return resolvedSyncPromise(event);
|
4929 | }
|
4930 | /**
|
4931 | * Builds and Event from a Message
|
4932 | * @hidden
|
4933 | */
|
4934 | function eventFromMessage(message, level = exports.Severity.Info, hint, attachStacktrace) {
|
4935 | const syntheticException = (hint && hint.syntheticException) || undefined;
|
4936 | const event = eventFromString(message, syntheticException, attachStacktrace);
|
4937 | event.level = level;
|
4938 | if (hint && hint.event_id) {
|
4939 | event.event_id = hint.event_id;
|
4940 | }
|
4941 | return resolvedSyncPromise(event);
|
4942 | }
|
4943 | /**
|
4944 | * @hidden
|
4945 | */
|
4946 | function eventFromUnknownInput(exception, syntheticException, attachStacktrace, isUnhandledRejection) {
|
4947 | let event;
|
4948 | if (isErrorEvent(exception) && exception.error) {
|
4949 | // If it is an ErrorEvent with `error` property, extract it to get actual Error
|
4950 | const errorEvent = exception;
|
4951 | return eventFromError(errorEvent.error);
|
4952 | }
|
4953 | // If it is a `DOMError` (which is a legacy API, but still supported in some browsers) then we just extract the name
|
4954 | // and message, as it doesn't provide anything else. According to the spec, all `DOMExceptions` should also be
|
4955 | // `Error`s, but that's not the case in IE11, so in that case we treat it the same as we do a `DOMError`.
|
4956 | //
|
4957 | // https://developer.mozilla.org/en-US/docs/Web/API/DOMError
|
4958 | // https://developer.mozilla.org/en-US/docs/Web/API/DOMException
|
4959 | // https://webidl.spec.whatwg.org/#es-DOMException-specialness
|
4960 | if (isDOMError(exception) || isDOMException(exception)) {
|
4961 | const domException = exception;
|
4962 | if ('stack' in exception) {
|
4963 | event = eventFromError(exception);
|
4964 | }
|
4965 | else {
|
4966 | const name = domException.name || (isDOMError(domException) ? 'DOMError' : 'DOMException');
|
4967 | const message = domException.message ? `${name}: ${domException.message}` : name;
|
4968 | event = eventFromString(message, syntheticException, attachStacktrace);
|
4969 | addExceptionTypeValue(event, message);
|
4970 | }
|
4971 | if ('code' in domException) {
|
4972 | event.tags = Object.assign(Object.assign({}, event.tags), { 'DOMException.code': `${domException.code}` });
|
4973 | }
|
4974 | return event;
|
4975 | }
|
4976 | if (isError(exception)) {
|
4977 | // we have a real Error object, do nothing
|
4978 | return eventFromError(exception);
|
4979 | }
|
4980 | if (isPlainObject(exception) || isEvent(exception)) {
|
4981 | // If it's a plain object or an instance of `Event` (the built-in JS kind, not this SDK's `Event` type), serialize
|
4982 | // it manually. This will allow us to group events based on top-level keys which is much better than creating a new
|
4983 | // group on any key/value change.
|
4984 | const objectException = exception;
|
4985 | event = eventFromPlainObject(objectException, syntheticException, isUnhandledRejection);
|
4986 | addExceptionMechanism(event, {
|
4987 | synthetic: true,
|
4988 | });
|
4989 | return event;
|
4990 | }
|
4991 | // If none of previous checks were valid, then it means that it's not:
|
4992 | // - an instance of DOMError
|
4993 | // - an instance of DOMException
|
4994 | // - an instance of Event
|
4995 | // - an instance of Error
|
4996 | // - a valid ErrorEvent (one with an error property)
|
4997 | // - a plain Object
|
4998 | //
|
4999 | // So bail out and capture it as a simple message:
|
5000 | event = eventFromString(exception, syntheticException, attachStacktrace);
|
5001 | addExceptionTypeValue(event, `${exception}`, undefined);
|
5002 | addExceptionMechanism(event, {
|
5003 | synthetic: true,
|
5004 | });
|
5005 | return event;
|
5006 | }
|
5007 | /**
|
5008 | * @hidden
|
5009 | */
|
5010 | function eventFromString(input, syntheticException, attachStacktrace) {
|
5011 | const event = {
|
5012 | message: input,
|
5013 | };
|
5014 | if (attachStacktrace && syntheticException) {
|
5015 | const frames = parseStackFrames(syntheticException);
|
5016 | if (frames.length) {
|
5017 | event.stacktrace = { frames };
|
5018 | }
|
5019 | }
|
5020 | return event;
|
5021 | }
|
5022 |
|
5023 | /*
|
5024 | * This file defines flags and constants that can be modified during compile time in order to facilitate tree shaking
|
5025 | * for users.
|
5026 | *
|
5027 | * Debug flags need to be declared in each package individually and must not be imported across package boundaries,
|
5028 | * because some build tools have trouble tree-shaking imported guards.
|
5029 | *
|
5030 | * As a convention, we define debug flags in a `flags.ts` file in the root of a package's `src` folder.
|
5031 | *
|
5032 | * Debug flag files will contain "magic strings" like `true` that may get replaced with actual values during
|
5033 | * our, or the user's build process. Take care when introducing new flags - they must not throw if they are not
|
5034 | * replaced.
|
5035 | */
|
5036 | /** Flag that is true for debug builds, false otherwise. */
|
5037 | const IS_DEBUG_BUILD = true;
|
5038 |
|
5039 | const global$4 = getGlobalObject();
|
5040 | let cachedFetchImpl;
|
5041 | /**
|
5042 | * A special usecase for incorrectly wrapped Fetch APIs in conjunction with ad-blockers.
|
5043 | * Whenever someone wraps the Fetch API and returns the wrong promise chain,
|
5044 | * this chain becomes orphaned and there is no possible way to capture it's rejections
|
5045 | * other than allowing it bubble up to this very handler. eg.
|
5046 | *
|
5047 | * const f = window.fetch;
|
5048 | * window.fetch = function () {
|
5049 | * const p = f.apply(this, arguments);
|
5050 | *
|
5051 | * p.then(function() {
|
5052 | * console.log('hi.');
|
5053 | * });
|
5054 | *
|
5055 | * return p;
|
5056 | * }
|
5057 | *
|
5058 | * `p.then(function () { ... })` is producing a completely separate promise chain,
|
5059 | * however, what's returned is `p` - the result of original `fetch` call.
|
5060 | *
|
5061 | * This mean, that whenever we use the Fetch API to send our own requests, _and_
|
5062 | * some ad-blocker blocks it, this orphaned chain will _always_ reject,
|
5063 | * effectively causing another event to be captured.
|
5064 | * This makes a whole process become an infinite loop, which we need to somehow
|
5065 | * deal with, and break it in one way or another.
|
5066 | *
|
5067 | * To deal with this issue, we are making sure that we _always_ use the real
|
5068 | * browser Fetch API, instead of relying on what `window.fetch` exposes.
|
5069 | * The only downside to this would be missing our own requests as breadcrumbs,
|
5070 | * but because we are already not doing this, it should be just fine.
|
5071 | *
|
5072 | * Possible failed fetch error messages per-browser:
|
5073 | *
|
5074 | * Chrome: Failed to fetch
|
5075 | * Edge: Failed to Fetch
|
5076 | * Firefox: NetworkError when attempting to fetch resource
|
5077 | * Safari: resource blocked by content blocker
|
5078 | */
|
5079 | function getNativeFetchImplementation() {
|
5080 | if (cachedFetchImpl) {
|
5081 | return cachedFetchImpl;
|
5082 | }
|
5083 | /* eslint-disable @typescript-eslint/unbound-method */
|
5084 | // Fast path to avoid DOM I/O
|
5085 | if (isNativeFetch(global$4.fetch)) {
|
5086 | return (cachedFetchImpl = global$4.fetch.bind(global$4));
|
5087 | }
|
5088 | const document = global$4.document;
|
5089 | let fetchImpl = global$4.fetch;
|
5090 | // eslint-disable-next-line deprecation/deprecation
|
5091 | if (document && typeof document.createElement === 'function') {
|
5092 | try {
|
5093 | const sandbox = document.createElement('iframe');
|
5094 | sandbox.hidden = true;
|
5095 | document.head.appendChild(sandbox);
|
5096 | const contentWindow = sandbox.contentWindow;
|
5097 | if (contentWindow && contentWindow.fetch) {
|
5098 | fetchImpl = contentWindow.fetch;
|
5099 | }
|
5100 | document.head.removeChild(sandbox);
|
5101 | }
|
5102 | catch (e) {
|
5103 | logger.warn('Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ', e);
|
5104 | }
|
5105 | }
|
5106 | return (cachedFetchImpl = fetchImpl.bind(global$4));
|
5107 | /* eslint-enable @typescript-eslint/unbound-method */
|
5108 | }
|
5109 | /**
|
5110 | * Sends sdk client report using sendBeacon or fetch as a fallback if available
|
5111 | *
|
5112 | * @param url report endpoint
|
5113 | * @param body report payload
|
5114 | */
|
5115 | function sendReport(url, body) {
|
5116 | const isRealNavigator = Object.prototype.toString.call(global$4 && global$4.navigator) === '[object Navigator]';
|
5117 | const hasSendBeacon = isRealNavigator && typeof global$4.navigator.sendBeacon === 'function';
|
5118 | if (hasSendBeacon) {
|
5119 | // Prevent illegal invocations - https://xgwang.me/posts/you-may-not-know-beacon/#it-may-throw-error%2C-be-sure-to-catch
|
5120 | const sendBeacon = global$4.navigator.sendBeacon.bind(global$4.navigator);
|
5121 | return sendBeacon(url, body);
|
5122 | }
|
5123 | if (supportsFetch()) {
|
5124 | const fetch = getNativeFetchImplementation();
|
5125 | return forget(fetch(url, {
|
5126 | body,
|
5127 | method: 'POST',
|
5128 | credentials: 'omit',
|
5129 | keepalive: true,
|
5130 | }));
|
5131 | }
|
5132 | }
|
5133 |
|
5134 | function requestTypeToCategory(ty) {
|
5135 | const tyStr = ty;
|
5136 | return tyStr === 'event' ? 'error' : tyStr;
|
5137 | }
|
5138 | const global$3 = getGlobalObject();
|
5139 | /** Base Transport class implementation */
|
5140 | class BaseTransport {
|
5141 | constructor(options) {
|
5142 | this.options = options;
|
5143 | /** A simple buffer holding all requests. */
|
5144 | this._buffer = makePromiseBuffer(30);
|
5145 | /** Locks transport after receiving rate limits in a response */
|
5146 | this._rateLimits = {};
|
5147 | this._outcomes = {};
|
5148 | this._api = initAPIDetails(options.dsn, options._metadata, options.tunnel);
|
5149 | // eslint-disable-next-line deprecation/deprecation
|
5150 | this.url = getStoreEndpointWithUrlEncodedAuth(this._api.dsn);
|
5151 | if (this.options.sendClientReports && global$3.document) {
|
5152 | global$3.document.addEventListener('visibilitychange', () => {
|
5153 | if (global$3.document.visibilityState === 'hidden') {
|
5154 | this._flushOutcomes();
|
5155 | }
|
5156 | });
|
5157 | }
|
5158 | }
|
5159 | /**
|
5160 | * @inheritDoc
|
5161 | */
|
5162 | sendEvent(event) {
|
5163 | return this._sendRequest(eventToSentryRequest(event, this._api), event);
|
5164 | }
|
5165 | /**
|
5166 | * @inheritDoc
|
5167 | */
|
5168 | sendSession(session) {
|
5169 | return this._sendRequest(sessionToSentryRequest(session, this._api), session);
|
5170 | }
|
5171 | /**
|
5172 | * @inheritDoc
|
5173 | */
|
5174 | close(timeout) {
|
5175 | return this._buffer.drain(timeout);
|
5176 | }
|
5177 | /**
|
5178 | * @inheritDoc
|
5179 | */
|
5180 | recordLostEvent(reason, category) {
|
5181 | var _a;
|
5182 | if (!this.options.sendClientReports) {
|
5183 | return;
|
5184 | }
|
5185 | // We want to track each category (event, transaction, session) separately
|
5186 | // but still keep the distinction between different type of outcomes.
|
5187 | // We could use nested maps, but it's much easier to read and type this way.
|
5188 | // A correct type for map-based implementation if we want to go that route
|
5189 | // would be `Partial<Record<SentryRequestType, Partial<Record<Outcome, number>>>>`
|
5190 | const key = `${requestTypeToCategory(category)}:${reason}`;
|
5191 | IS_DEBUG_BUILD && logger.log(`Adding outcome: ${key}`);
|
5192 | this._outcomes[key] = (_a = this._outcomes[key], (_a !== null && _a !== void 0 ? _a : 0)) + 1;
|
5193 | }
|
5194 | /**
|
5195 | * Send outcomes as an envelope
|
5196 | */
|
5197 | _flushOutcomes() {
|
5198 | if (!this.options.sendClientReports) {
|
5199 | return;
|
5200 | }
|
5201 | const outcomes = this._outcomes;
|
5202 | this._outcomes = {};
|
5203 | // Nothing to send
|
5204 | if (!Object.keys(outcomes).length) {
|
5205 | IS_DEBUG_BUILD && logger.log('No outcomes to flush');
|
5206 | return;
|
5207 | }
|
5208 | IS_DEBUG_BUILD && logger.log(`Flushing outcomes:\n${JSON.stringify(outcomes, null, 2)}`);
|
5209 | const url = getEnvelopeEndpointWithUrlEncodedAuth(this._api.dsn, this._api.tunnel);
|
5210 | const discardedEvents = Object.keys(outcomes).map(key => {
|
5211 | const [category, reason] = key.split(':');
|
5212 | return {
|
5213 | reason,
|
5214 | category,
|
5215 | quantity: outcomes[key],
|
5216 | };
|
5217 | // TODO: Improve types on discarded_events to get rid of cast
|
5218 | });
|
5219 | const envelope = createClientReportEnvelope(discardedEvents, this._api.tunnel && dsnToString(this._api.dsn));
|
5220 | try {
|
5221 | sendReport(url, serializeEnvelope(envelope));
|
5222 | }
|
5223 | catch (e) {
|
5224 | IS_DEBUG_BUILD && logger.error(e);
|
5225 | }
|
5226 | }
|
5227 | /**
|
5228 | * Handle Sentry repsonse for promise-based transports.
|
5229 | */
|
5230 | _handleResponse({ requestType, response, headers, resolve, reject, }) {
|
5231 | const status = eventStatusFromHttpCode(response.status);
|
5232 | this._rateLimits = updateRateLimits(this._rateLimits, headers);
|
5233 | // eslint-disable-next-line deprecation/deprecation
|
5234 | if (this._isRateLimited(requestType)) {
|
5235 | IS_DEBUG_BUILD &&
|
5236 | // eslint-disable-next-line deprecation/deprecation
|
5237 | logger.warn(`Too many ${requestType} requests, backing off until: ${this._disabledUntil(requestType)}`);
|
5238 | }
|
5239 | if (status === 'success') {
|
5240 | resolve({ status });
|
5241 | return;
|
5242 | }
|
5243 | reject(response);
|
5244 | }
|
5245 | /**
|
5246 | * Gets the time that given category is disabled until for rate limiting
|
5247 | *
|
5248 | * @deprecated Please use `disabledUntil` from @sentry/utils
|
5249 | */
|
5250 | _disabledUntil(requestType) {
|
5251 | const category = requestTypeToCategory(requestType);
|
5252 | return new Date(disabledUntil(this._rateLimits, category));
|
5253 | }
|
5254 | /**
|
5255 | * Checks if a category is rate limited
|
5256 | *
|
5257 | * @deprecated Please use `isRateLimited` from @sentry/utils
|
5258 | */
|
5259 | _isRateLimited(requestType) {
|
5260 | const category = requestTypeToCategory(requestType);
|
5261 | return isRateLimited(this._rateLimits, category);
|
5262 | }
|
5263 | }
|
5264 |
|
5265 | /** `fetch` based transport */
|
5266 | class FetchTransport extends BaseTransport {
|
5267 | constructor(options, fetchImpl = getNativeFetchImplementation()) {
|
5268 | super(options);
|
5269 | this._fetch = fetchImpl;
|
5270 | }
|
5271 | /**
|
5272 | * @param sentryRequest Prepared SentryRequest to be delivered
|
5273 | * @param originalPayload Original payload used to create SentryRequest
|
5274 | */
|
5275 | _sendRequest(sentryRequest, originalPayload) {
|
5276 | // eslint-disable-next-line deprecation/deprecation
|
5277 | if (this._isRateLimited(sentryRequest.type)) {
|
5278 | this.recordLostEvent('ratelimit_backoff', sentryRequest.type);
|
5279 | return Promise.reject({
|
5280 | event: originalPayload,
|
5281 | type: sentryRequest.type,
|
5282 | // eslint-disable-next-line deprecation/deprecation
|
5283 | reason: `Transport for ${sentryRequest.type} requests locked till ${this._disabledUntil(sentryRequest.type)} due to too many requests.`,
|
5284 | status: 429,
|
5285 | });
|
5286 | }
|
5287 | const options = {
|
5288 | body: sentryRequest.body,
|
5289 | method: 'POST',
|
5290 | // Despite all stars in the sky saying that Edge supports old draft syntax, aka 'never', 'always', 'origin' and 'default'
|
5291 | // (see https://caniuse.com/#feat=referrer-policy),
|
5292 | // it doesn't. And it throws an exception instead of ignoring this parameter...
|
5293 | // REF: https://github.com/getsentry/raven-js/issues/1233
|
5294 | referrerPolicy: (supportsReferrerPolicy() ? 'origin' : ''),
|
5295 | };
|
5296 | if (this.options.fetchParameters !== undefined) {
|
5297 | Object.assign(options, this.options.fetchParameters);
|
5298 | }
|
5299 | if (this.options.headers !== undefined) {
|
5300 | options.headers = this.options.headers;
|
5301 | }
|
5302 | return this._buffer
|
5303 | .add(() => new SyncPromise((resolve, reject) => {
|
5304 | void this._fetch(sentryRequest.url, options)
|
5305 | .then(response => {
|
5306 | const headers = {
|
5307 | 'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
|
5308 | 'retry-after': response.headers.get('Retry-After'),
|
5309 | };
|
5310 | this._handleResponse({
|
5311 | requestType: sentryRequest.type,
|
5312 | response,
|
5313 | headers,
|
5314 | resolve,
|
5315 | reject,
|
5316 | });
|
5317 | })
|
5318 | .catch(reject);
|
5319 | }))
|
5320 | .then(undefined, reason => {
|
5321 | // It's either buffer rejection or any other xhr/fetch error, which are treated as NetworkError.
|
5322 | if (reason instanceof SentryError) {
|
5323 | this.recordLostEvent('queue_overflow', sentryRequest.type);
|
5324 | }
|
5325 | else {
|
5326 | this.recordLostEvent('network_error', sentryRequest.type);
|
5327 | }
|
5328 | throw reason;
|
5329 | });
|
5330 | }
|
5331 | }
|
5332 |
|
5333 | /** `XHR` based transport */
|
5334 | class XHRTransport extends BaseTransport {
|
5335 | /**
|
5336 | * @param sentryRequest Prepared SentryRequest to be delivered
|
5337 | * @param originalPayload Original payload used to create SentryRequest
|
5338 | */
|
5339 | _sendRequest(sentryRequest, originalPayload) {
|
5340 | // eslint-disable-next-line deprecation/deprecation
|
5341 | if (this._isRateLimited(sentryRequest.type)) {
|
5342 | this.recordLostEvent('ratelimit_backoff', sentryRequest.type);
|
5343 | return Promise.reject({
|
5344 | event: originalPayload,
|
5345 | type: sentryRequest.type,
|
5346 | // eslint-disable-next-line deprecation/deprecation
|
5347 | reason: `Transport for ${sentryRequest.type} requests locked till ${this._disabledUntil(sentryRequest.type)} due to too many requests.`,
|
5348 | status: 429,
|
5349 | });
|
5350 | }
|
5351 | return this._buffer
|
5352 | .add(() => new SyncPromise((resolve, reject) => {
|
5353 | const request = new XMLHttpRequest();
|
5354 | request.onreadystatechange = () => {
|
5355 | if (request.readyState === 4) {
|
5356 | const headers = {
|
5357 | 'x-sentry-rate-limits': request.getResponseHeader('X-Sentry-Rate-Limits'),
|
5358 | 'retry-after': request.getResponseHeader('Retry-After'),
|
5359 | };
|
5360 | this._handleResponse({ requestType: sentryRequest.type, response: request, headers, resolve, reject });
|
5361 | }
|
5362 | };
|
5363 | request.open('POST', sentryRequest.url);
|
5364 | for (const header in this.options.headers) {
|
5365 | if (Object.prototype.hasOwnProperty.call(this.options.headers, header)) {
|
5366 | request.setRequestHeader(header, this.options.headers[header]);
|
5367 | }
|
5368 | }
|
5369 | request.send(sentryRequest.body);
|
5370 | }))
|
5371 | .then(undefined, reason => {
|
5372 | // It's either buffer rejection or any other xhr/fetch error, which are treated as NetworkError.
|
5373 | if (reason instanceof SentryError) {
|
5374 | this.recordLostEvent('queue_overflow', sentryRequest.type);
|
5375 | }
|
5376 | else {
|
5377 | this.recordLostEvent('network_error', sentryRequest.type);
|
5378 | }
|
5379 | throw reason;
|
5380 | });
|
5381 | }
|
5382 | }
|
5383 |
|
5384 | /**
|
5385 | * Creates a Transport that uses the Fetch API to send events to Sentry.
|
5386 | */
|
5387 | function makeNewFetchTransport(options, nativeFetch = getNativeFetchImplementation()) {
|
5388 | function makeRequest(request) {
|
5389 | const requestOptions = Object.assign({ body: request.body, method: 'POST', referrerPolicy: 'origin' }, options.requestOptions);
|
5390 | return nativeFetch(options.url, requestOptions).then(response => {
|
5391 | return response.text().then(body => ({
|
5392 | body,
|
5393 | headers: {
|
5394 | 'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
|
5395 | 'retry-after': response.headers.get('Retry-After'),
|
5396 | },
|
5397 | reason: response.statusText,
|
5398 | statusCode: response.status,
|
5399 | }));
|
5400 | });
|
5401 | }
|
5402 | return createTransport({ bufferSize: options.bufferSize }, makeRequest);
|
5403 | }
|
5404 |
|
5405 | /**
|
5406 | * The DONE ready state for XmlHttpRequest
|
5407 | *
|
5408 | * Defining it here as a constant b/c XMLHttpRequest.DONE is not always defined
|
5409 | * (e.g. during testing, it is `undefined`)
|
5410 | *
|
5411 | * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState}
|
5412 | */
|
5413 | const XHR_READYSTATE_DONE = 4;
|
5414 | /**
|
5415 | * Creates a Transport that uses the XMLHttpRequest API to send events to Sentry.
|
5416 | */
|
5417 | function makeNewXHRTransport(options) {
|
5418 | function makeRequest(request) {
|
5419 | return new SyncPromise((resolve, _reject) => {
|
5420 | const xhr = new XMLHttpRequest();
|
5421 | xhr.onreadystatechange = () => {
|
5422 | if (xhr.readyState === XHR_READYSTATE_DONE) {
|
5423 | const response = {
|
5424 | body: xhr.response,
|
5425 | headers: {
|
5426 | 'x-sentry-rate-limits': xhr.getResponseHeader('X-Sentry-Rate-Limits'),
|
5427 | 'retry-after': xhr.getResponseHeader('Retry-After'),
|
5428 | },
|
5429 | reason: xhr.statusText,
|
5430 | statusCode: xhr.status,
|
5431 | };
|
5432 | resolve(response);
|
5433 | }
|
5434 | };
|
5435 | xhr.open('POST', options.url);
|
5436 | for (const header in options.headers) {
|
5437 | if (Object.prototype.hasOwnProperty.call(options.headers, header)) {
|
5438 | xhr.setRequestHeader(header, options.headers[header]);
|
5439 | }
|
5440 | }
|
5441 | xhr.send(request.body);
|
5442 | });
|
5443 | }
|
5444 | return createTransport({ bufferSize: options.bufferSize }, makeRequest);
|
5445 | }
|
5446 |
|
5447 | var index = /*#__PURE__*/Object.freeze({
|
5448 | __proto__: null,
|
5449 | BaseTransport: BaseTransport,
|
5450 | FetchTransport: FetchTransport,
|
5451 | XHRTransport: XHRTransport,
|
5452 | makeNewFetchTransport: makeNewFetchTransport,
|
5453 | makeNewXHRTransport: makeNewXHRTransport
|
5454 | });
|
5455 |
|
5456 | /**
|
5457 | * The Sentry Browser SDK Backend.
|
5458 | * @hidden
|
5459 | */
|
5460 | class BrowserBackend extends BaseBackend {
|
5461 | /**
|
5462 | * @inheritDoc
|
5463 | */
|
5464 | eventFromException(exception, hint) {
|
5465 | return eventFromException(exception, hint, this._options.attachStacktrace);
|
5466 | }
|
5467 | /**
|
5468 | * @inheritDoc
|
5469 | */
|
5470 | eventFromMessage(message, level = exports.Severity.Info, hint) {
|
5471 | return eventFromMessage(message, level, hint, this._options.attachStacktrace);
|
5472 | }
|
5473 | /**
|
5474 | * @inheritDoc
|
5475 | */
|
5476 | _setupTransport() {
|
5477 | if (!this._options.dsn) {
|
5478 | // We return the noop transport here in case there is no Dsn.
|
5479 | return super._setupTransport();
|
5480 | }
|
5481 | const transportOptions = Object.assign(Object.assign({}, this._options.transportOptions), { dsn: this._options.dsn, tunnel: this._options.tunnel, sendClientReports: this._options.sendClientReports, _metadata: this._options._metadata });
|
5482 | const api = initAPIDetails(transportOptions.dsn, transportOptions._metadata, transportOptions.tunnel);
|
5483 | const url = getEnvelopeEndpointWithUrlEncodedAuth(api.dsn, api.tunnel);
|
5484 | if (this._options.transport) {
|
5485 | return new this._options.transport(transportOptions);
|
5486 | }
|
5487 | if (supportsFetch()) {
|
5488 | const requestOptions = Object.assign({}, transportOptions.fetchParameters);
|
5489 | this._newTransport = makeNewFetchTransport({ requestOptions, url });
|
5490 | return new FetchTransport(transportOptions);
|
5491 | }
|
5492 | this._newTransport = makeNewXHRTransport({
|
5493 | url,
|
5494 | headers: transportOptions.headers,
|
5495 | });
|
5496 | return new XHRTransport(transportOptions);
|
5497 | }
|
5498 | }
|
5499 |
|
5500 | const global$2 = getGlobalObject();
|
5501 | let ignoreOnError = 0;
|
5502 | /**
|
5503 | * @hidden
|
5504 | */
|
5505 | function shouldIgnoreOnError() {
|
5506 | return ignoreOnError > 0;
|
5507 | }
|
5508 | /**
|
5509 | * @hidden
|
5510 | */
|
5511 | function ignoreNextOnError() {
|
5512 | // onerror should trigger before setTimeout
|
5513 | ignoreOnError += 1;
|
5514 | setTimeout(() => {
|
5515 | ignoreOnError -= 1;
|
5516 | });
|
5517 | }
|
5518 | /**
|
5519 | * Instruments the given function and sends an event to Sentry every time the
|
5520 | * function throws an exception.
|
5521 | *
|
5522 | * @param fn A function to wrap.
|
5523 | * @returns The wrapped function.
|
5524 | * @hidden
|
5525 | */
|
5526 | function wrap$1(fn, options = {}, before) {
|
5527 | // for future readers what this does is wrap a function and then create
|
5528 | // a bi-directional wrapping between them.
|
5529 | //
|
5530 | // example: wrapped = wrap(original);
|
5531 | // original.__sentry_wrapped__ -> wrapped
|
5532 | // wrapped.__sentry_original__ -> original
|
5533 | if (typeof fn !== 'function') {
|
5534 | return fn;
|
5535 | }
|
5536 | try {
|
5537 | // if we're dealing with a function that was previously wrapped, return
|
5538 | // the original wrapper.
|
5539 | const wrapper = fn.__sentry_wrapped__;
|
5540 | if (wrapper) {
|
5541 | return wrapper;
|
5542 | }
|
5543 | // We don't wanna wrap it twice
|
5544 | if (getOriginalFunction(fn)) {
|
5545 | return fn;
|
5546 | }
|
5547 | }
|
5548 | catch (e) {
|
5549 | // Just accessing custom props in some Selenium environments
|
5550 | // can cause a "Permission denied" exception (see raven-js#495).
|
5551 | // Bail on wrapping and return the function as-is (defers to window.onerror).
|
5552 | return fn;
|
5553 | }
|
5554 | /* eslint-disable prefer-rest-params */
|
5555 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
5556 | const sentryWrapped = function () {
|
5557 | const args = Array.prototype.slice.call(arguments);
|
5558 | try {
|
5559 | if (before && typeof before === 'function') {
|
5560 | before.apply(this, arguments);
|
5561 | }
|
5562 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
5563 | const wrappedArguments = args.map((arg) => wrap$1(arg, options));
|
5564 | // Attempt to invoke user-land function
|
5565 | // NOTE: If you are a Sentry user, and you are seeing this stack frame, it
|
5566 | // means the sentry.javascript SDK caught an error invoking your application code. This
|
5567 | // is expected behavior and NOT indicative of a bug with sentry.javascript.
|
5568 | return fn.apply(this, wrappedArguments);
|
5569 | }
|
5570 | catch (ex) {
|
5571 | ignoreNextOnError();
|
5572 | withScope((scope) => {
|
5573 | scope.addEventProcessor((event) => {
|
5574 | if (options.mechanism) {
|
5575 | addExceptionTypeValue(event, undefined, undefined);
|
5576 | addExceptionMechanism(event, options.mechanism);
|
5577 | }
|
5578 | event.extra = Object.assign(Object.assign({}, event.extra), { arguments: args });
|
5579 | return event;
|
5580 | });
|
5581 | captureException(ex);
|
5582 | });
|
5583 | throw ex;
|
5584 | }
|
5585 | };
|
5586 | /* eslint-enable prefer-rest-params */
|
5587 | // Accessing some objects may throw
|
5588 | // ref: https://github.com/getsentry/sentry-javascript/issues/1168
|
5589 | try {
|
5590 | for (const property in fn) {
|
5591 | if (Object.prototype.hasOwnProperty.call(fn, property)) {
|
5592 | sentryWrapped[property] = fn[property];
|
5593 | }
|
5594 | }
|
5595 | }
|
5596 | catch (_oO) { } // eslint-disable-line no-empty
|
5597 | // Signal that this function has been wrapped/filled already
|
5598 | // for both debugging and to prevent it to being wrapped/filled twice
|
5599 | markFunctionWrapped(sentryWrapped, fn);
|
5600 | addNonEnumerableProperty(fn, '__sentry_wrapped__', sentryWrapped);
|
5601 | // Restore original function name (not all browsers allow that)
|
5602 | try {
|
5603 | const descriptor = Object.getOwnPropertyDescriptor(sentryWrapped, 'name');
|
5604 | if (descriptor.configurable) {
|
5605 | Object.defineProperty(sentryWrapped, 'name', {
|
5606 | get() {
|
5607 | return fn.name;
|
5608 | },
|
5609 | });
|
5610 | }
|
5611 | // eslint-disable-next-line no-empty
|
5612 | }
|
5613 | catch (_oO) { }
|
5614 | return sentryWrapped;
|
5615 | }
|
5616 | /**
|
5617 | * Injects the Report Dialog script
|
5618 | * @hidden
|
5619 | */
|
5620 | function injectReportDialog(options = {}) {
|
5621 | if (!global$2.document) {
|
5622 | return;
|
5623 | }
|
5624 | if (!options.eventId) {
|
5625 | IS_DEBUG_BUILD && logger.error('Missing eventId option in showReportDialog call');
|
5626 | return;
|
5627 | }
|
5628 | if (!options.dsn) {
|
5629 | IS_DEBUG_BUILD && logger.error('Missing dsn option in showReportDialog call');
|
5630 | return;
|
5631 | }
|
5632 | const script = global$2.document.createElement('script');
|
5633 | script.async = true;
|
5634 | script.src = getReportDialogEndpoint(options.dsn, options);
|
5635 | if (options.onLoad) {
|
5636 | // eslint-disable-next-line @typescript-eslint/unbound-method
|
5637 | script.onload = options.onLoad;
|
5638 | }
|
5639 | const injectionPoint = global$2.document.head || global$2.document.body;
|
5640 | if (injectionPoint) {
|
5641 | injectionPoint.appendChild(script);
|
5642 | }
|
5643 | }
|
5644 |
|
5645 | /* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
5646 | /** Global handlers */
|
5647 | class GlobalHandlers {
|
5648 | /** JSDoc */
|
5649 | constructor(options) {
|
5650 | /**
|
5651 | * @inheritDoc
|
5652 | */
|
5653 | this.name = GlobalHandlers.id;
|
5654 | /**
|
5655 | * Stores references functions to installing handlers. Will set to undefined
|
5656 | * after they have been run so that they are not used twice.
|
5657 | */
|
5658 | this._installFunc = {
|
5659 | onerror: _installGlobalOnErrorHandler,
|
5660 | onunhandledrejection: _installGlobalOnUnhandledRejectionHandler,
|
5661 | };
|
5662 | this._options = Object.assign({ onerror: true, onunhandledrejection: true }, options);
|
5663 | }
|
5664 | /**
|
5665 | * @inheritDoc
|
5666 | */
|
5667 | setupOnce() {
|
5668 | Error.stackTraceLimit = 50;
|
5669 | const options = this._options;
|
5670 | // We can disable guard-for-in as we construct the options object above + do checks against
|
5671 | // `this._installFunc` for the property.
|
5672 | // eslint-disable-next-line guard-for-in
|
5673 | for (const key in options) {
|
5674 | const installFunc = this._installFunc[key];
|
5675 | if (installFunc && options[key]) {
|
5676 | globalHandlerLog(key);
|
5677 | installFunc();
|
5678 | this._installFunc[key] = undefined;
|
5679 | }
|
5680 | }
|
5681 | }
|
5682 | }
|
5683 | /**
|
5684 | * @inheritDoc
|
5685 | */
|
5686 | GlobalHandlers.id = 'GlobalHandlers';
|
5687 | /** JSDoc */
|
5688 | function _installGlobalOnErrorHandler() {
|
5689 | addInstrumentationHandler('error',
|
5690 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
5691 | (data) => {
|
5692 | const [hub, attachStacktrace] = getHubAndAttachStacktrace();
|
5693 | if (!hub.getIntegration(GlobalHandlers)) {
|
5694 | return;
|
5695 | }
|
5696 | const { msg, url, line, column, error } = data;
|
5697 | if (shouldIgnoreOnError() || (error && error.__sentry_own_request__)) {
|
5698 | return;
|
5699 | }
|
5700 | const event = error === undefined && isString(msg)
|
5701 | ? _eventFromIncompleteOnError(msg, url, line, column)
|
5702 | : _enhanceEventWithInitialFrame(eventFromUnknownInput(error || msg, undefined, attachStacktrace, false), url, line, column);
|
5703 | event.level = exports.Severity.Error;
|
5704 | addMechanismAndCapture(hub, error, event, 'onerror');
|
5705 | });
|
5706 | }
|
5707 | /** JSDoc */
|
5708 | function _installGlobalOnUnhandledRejectionHandler() {
|
5709 | addInstrumentationHandler('unhandledrejection',
|
5710 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
5711 | (e) => {
|
5712 | const [hub, attachStacktrace] = getHubAndAttachStacktrace();
|
5713 | if (!hub.getIntegration(GlobalHandlers)) {
|
5714 | return;
|
5715 | }
|
5716 | let error = e;
|
5717 | // dig the object of the rejection out of known event types
|
5718 | try {
|
5719 | // PromiseRejectionEvents store the object of the rejection under 'reason'
|
5720 | // see https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent
|
5721 | if ('reason' in e) {
|
5722 | error = e.reason;
|
5723 | }
|
5724 | // something, somewhere, (likely a browser extension) effectively casts PromiseRejectionEvents
|
5725 | // to CustomEvents, moving the `promise` and `reason` attributes of the PRE into
|
5726 | // the CustomEvent's `detail` attribute, since they're not part of CustomEvent's spec
|
5727 | // see https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent and
|
5728 | // https://github.com/getsentry/sentry-javascript/issues/2380
|
5729 | else if ('detail' in e && 'reason' in e.detail) {
|
5730 | error = e.detail.reason;
|
5731 | }
|
5732 | }
|
5733 | catch (_oO) {
|
5734 | // no-empty
|
5735 | }
|
5736 | if (shouldIgnoreOnError() || (error && error.__sentry_own_request__)) {
|
5737 | return true;
|
5738 | }
|
5739 | const event = isPrimitive(error)
|
5740 | ? _eventFromRejectionWithPrimitive(error)
|
5741 | : eventFromUnknownInput(error, undefined, attachStacktrace, true);
|
5742 | event.level = exports.Severity.Error;
|
5743 | addMechanismAndCapture(hub, error, event, 'onunhandledrejection');
|
5744 | return;
|
5745 | });
|
5746 | }
|
5747 | /**
|
5748 | * Create an event from a promise rejection where the `reason` is a primitive.
|
5749 | *
|
5750 | * @param reason: The `reason` property of the promise rejection
|
5751 | * @returns An Event object with an appropriate `exception` value
|
5752 | */
|
5753 | function _eventFromRejectionWithPrimitive(reason) {
|
5754 | return {
|
5755 | exception: {
|
5756 | values: [
|
5757 | {
|
5758 | type: 'UnhandledRejection',
|
5759 | // String() is needed because the Primitive type includes symbols (which can't be automatically stringified)
|
5760 | value: `Non-Error promise rejection captured with value: ${String(reason)}`,
|
5761 | },
|
5762 | ],
|
5763 | },
|
5764 | };
|
5765 | }
|
5766 | /**
|
5767 | * This function creates a stack from an old, error-less onerror handler.
|
5768 | */
|
5769 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
5770 | function _eventFromIncompleteOnError(msg, url, line, column) {
|
5771 | const ERROR_TYPES_RE = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/i;
|
5772 | // If 'message' is ErrorEvent, get real message from inside
|
5773 | let message = isErrorEvent(msg) ? msg.message : msg;
|
5774 | let name = 'Error';
|
5775 | const groups = message.match(ERROR_TYPES_RE);
|
5776 | if (groups) {
|
5777 | name = groups[1];
|
5778 | message = groups[2];
|
5779 | }
|
5780 | const event = {
|
5781 | exception: {
|
5782 | values: [
|
5783 | {
|
5784 | type: name,
|
5785 | value: message,
|
5786 | },
|
5787 | ],
|
5788 | },
|
5789 | };
|
5790 | return _enhanceEventWithInitialFrame(event, url, line, column);
|
5791 | }
|
5792 | /** JSDoc */
|
5793 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
5794 | function _enhanceEventWithInitialFrame(event, url, line, column) {
|
5795 | // event.exception
|
5796 | const e = (event.exception = event.exception || {});
|
5797 | // event.exception.values
|
5798 | const ev = (e.values = e.values || []);
|
5799 | // event.exception.values[0]
|
5800 | const ev0 = (ev[0] = ev[0] || {});
|
5801 | // event.exception.values[0].stacktrace
|
5802 | const ev0s = (ev0.stacktrace = ev0.stacktrace || {});
|
5803 | // event.exception.values[0].stacktrace.frames
|
5804 | const ev0sf = (ev0s.frames = ev0s.frames || []);
|
5805 | const colno = isNaN(parseInt(column, 10)) ? undefined : column;
|
5806 | const lineno = isNaN(parseInt(line, 10)) ? undefined : line;
|
5807 | const filename = isString(url) && url.length > 0 ? url : getLocationHref();
|
5808 | // event.exception.values[0].stacktrace.frames
|
5809 | if (ev0sf.length === 0) {
|
5810 | ev0sf.push({
|
5811 | colno,
|
5812 | filename,
|
5813 | function: '?',
|
5814 | in_app: true,
|
5815 | lineno,
|
5816 | });
|
5817 | }
|
5818 | return event;
|
5819 | }
|
5820 | function globalHandlerLog(type) {
|
5821 | IS_DEBUG_BUILD && logger.log(`Global Handler attached: ${type}`);
|
5822 | }
|
5823 | function addMechanismAndCapture(hub, error, event, type) {
|
5824 | addExceptionMechanism(event, {
|
5825 | handled: false,
|
5826 | type,
|
5827 | });
|
5828 | hub.captureEvent(event, {
|
5829 | originalException: error,
|
5830 | });
|
5831 | }
|
5832 | function getHubAndAttachStacktrace() {
|
5833 | const hub = getCurrentHub();
|
5834 | const client = hub.getClient();
|
5835 | const attachStacktrace = client && client.getOptions().attachStacktrace;
|
5836 | return [hub, attachStacktrace];
|
5837 | }
|
5838 |
|
5839 | const DEFAULT_EVENT_TARGET = [
|
5840 | 'EventTarget',
|
5841 | 'Window',
|
5842 | 'Node',
|
5843 | 'ApplicationCache',
|
5844 | 'AudioTrackList',
|
5845 | 'ChannelMergerNode',
|
5846 | 'CryptoOperation',
|
5847 | 'EventSource',
|
5848 | 'FileReader',
|
5849 | 'HTMLUnknownElement',
|
5850 | 'IDBDatabase',
|
5851 | 'IDBRequest',
|
5852 | 'IDBTransaction',
|
5853 | 'KeyOperation',
|
5854 | 'MediaController',
|
5855 | 'MessagePort',
|
5856 | 'ModalWindow',
|
5857 | 'Notification',
|
5858 | 'SVGElementInstance',
|
5859 | 'Screen',
|
5860 | 'TextTrack',
|
5861 | 'TextTrackCue',
|
5862 | 'TextTrackList',
|
5863 | 'WebSocket',
|
5864 | 'WebSocketWorker',
|
5865 | 'Worker',
|
5866 | 'XMLHttpRequest',
|
5867 | 'XMLHttpRequestEventTarget',
|
5868 | 'XMLHttpRequestUpload',
|
5869 | ];
|
5870 | /** Wrap timer functions and event targets to catch errors and provide better meta data */
|
5871 | class TryCatch {
|
5872 | /**
|
5873 | * @inheritDoc
|
5874 | */
|
5875 | constructor(options) {
|
5876 | /**
|
5877 | * @inheritDoc
|
5878 | */
|
5879 | this.name = TryCatch.id;
|
5880 | this._options = Object.assign({ XMLHttpRequest: true, eventTarget: true, requestAnimationFrame: true, setInterval: true, setTimeout: true }, options);
|
5881 | }
|
5882 | /**
|
5883 | * Wrap timer functions and event targets to catch errors
|
5884 | * and provide better metadata.
|
5885 | */
|
5886 | setupOnce() {
|
5887 | const global = getGlobalObject();
|
5888 | if (this._options.setTimeout) {
|
5889 | fill(global, 'setTimeout', _wrapTimeFunction);
|
5890 | }
|
5891 | if (this._options.setInterval) {
|
5892 | fill(global, 'setInterval', _wrapTimeFunction);
|
5893 | }
|
5894 | if (this._options.requestAnimationFrame) {
|
5895 | fill(global, 'requestAnimationFrame', _wrapRAF);
|
5896 | }
|
5897 | if (this._options.XMLHttpRequest && 'XMLHttpRequest' in global) {
|
5898 | fill(XMLHttpRequest.prototype, 'send', _wrapXHR);
|
5899 | }
|
5900 | const eventTargetOption = this._options.eventTarget;
|
5901 | if (eventTargetOption) {
|
5902 | const eventTarget = Array.isArray(eventTargetOption) ? eventTargetOption : DEFAULT_EVENT_TARGET;
|
5903 | eventTarget.forEach(_wrapEventTarget);
|
5904 | }
|
5905 | }
|
5906 | }
|
5907 | /**
|
5908 | * @inheritDoc
|
5909 | */
|
5910 | TryCatch.id = 'TryCatch';
|
5911 | /** JSDoc */
|
5912 | function _wrapTimeFunction(original) {
|
5913 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
5914 | return function (...args) {
|
5915 | const originalCallback = args[0];
|
5916 | args[0] = wrap$1(originalCallback, {
|
5917 | mechanism: {
|
5918 | data: { function: getFunctionName(original) },
|
5919 | handled: true,
|
5920 | type: 'instrument',
|
5921 | },
|
5922 | });
|
5923 | return original.apply(this, args);
|
5924 | };
|
5925 | }
|
5926 | /** JSDoc */
|
5927 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
5928 | function _wrapRAF(original) {
|
5929 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
5930 | return function (callback) {
|
5931 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
5932 | return original.apply(this, [
|
5933 | wrap$1(callback, {
|
5934 | mechanism: {
|
5935 | data: {
|
5936 | function: 'requestAnimationFrame',
|
5937 | handler: getFunctionName(original),
|
5938 | },
|
5939 | handled: true,
|
5940 | type: 'instrument',
|
5941 | },
|
5942 | }),
|
5943 | ]);
|
5944 | };
|
5945 | }
|
5946 | /** JSDoc */
|
5947 | function _wrapXHR(originalSend) {
|
5948 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
5949 | return function (...args) {
|
5950 | // eslint-disable-next-line @typescript-eslint/no-this-alias
|
5951 | const xhr = this;
|
5952 | const xmlHttpRequestProps = ['onload', 'onerror', 'onprogress', 'onreadystatechange'];
|
5953 | xmlHttpRequestProps.forEach(prop => {
|
5954 | if (prop in xhr && typeof xhr[prop] === 'function') {
|
5955 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
5956 | fill(xhr, prop, function (original) {
|
5957 | const wrapOptions = {
|
5958 | mechanism: {
|
5959 | data: {
|
5960 | function: prop,
|
5961 | handler: getFunctionName(original),
|
5962 | },
|
5963 | handled: true,
|
5964 | type: 'instrument',
|
5965 | },
|
5966 | };
|
5967 | // If Instrument integration has been called before TryCatch, get the name of original function
|
5968 | const originalFunction = getOriginalFunction(original);
|
5969 | if (originalFunction) {
|
5970 | wrapOptions.mechanism.data.handler = getFunctionName(originalFunction);
|
5971 | }
|
5972 | // Otherwise wrap directly
|
5973 | return wrap$1(original, wrapOptions);
|
5974 | });
|
5975 | }
|
5976 | });
|
5977 | return originalSend.apply(this, args);
|
5978 | };
|
5979 | }
|
5980 | /** JSDoc */
|
5981 | function _wrapEventTarget(target) {
|
5982 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
5983 | const global = getGlobalObject();
|
5984 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
5985 | const proto = global[target] && global[target].prototype;
|
5986 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-prototype-builtins
|
5987 | if (!proto || !proto.hasOwnProperty || !proto.hasOwnProperty('addEventListener')) {
|
5988 | return;
|
5989 | }
|
5990 | fill(proto, 'addEventListener', function (original) {
|
5991 | return function (eventName, fn, options) {
|
5992 | try {
|
5993 | if (typeof fn.handleEvent === 'function') {
|
5994 | fn.handleEvent = wrap$1(fn.handleEvent.bind(fn), {
|
5995 | mechanism: {
|
5996 | data: {
|
5997 | function: 'handleEvent',
|
5998 | handler: getFunctionName(fn),
|
5999 | target,
|
6000 | },
|
6001 | handled: true,
|
6002 | type: 'instrument',
|
6003 | },
|
6004 | });
|
6005 | }
|
6006 | }
|
6007 | catch (err) {
|
6008 | // can sometimes get 'Permission denied to access property "handle Event'
|
6009 | }
|
6010 | return original.apply(this, [
|
6011 | eventName,
|
6012 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
6013 | wrap$1(fn, {
|
6014 | mechanism: {
|
6015 | data: {
|
6016 | function: 'addEventListener',
|
6017 | handler: getFunctionName(fn),
|
6018 | target,
|
6019 | },
|
6020 | handled: true,
|
6021 | type: 'instrument',
|
6022 | },
|
6023 | }),
|
6024 | options,
|
6025 | ]);
|
6026 | };
|
6027 | });
|
6028 | fill(proto, 'removeEventListener', function (originalRemoveEventListener) {
|
6029 | return function (eventName, fn, options) {
|
6030 | /**
|
6031 | * There are 2 possible scenarios here:
|
6032 | *
|
6033 | * 1. Someone passes a callback, which was attached prior to Sentry initialization, or by using unmodified
|
6034 | * method, eg. `document.addEventListener.call(el, name, handler). In this case, we treat this function
|
6035 | * as a pass-through, and call original `removeEventListener` with it.
|
6036 | *
|
6037 | * 2. Someone passes a callback, which was attached after Sentry was initialized, which means that it was using
|
6038 | * our wrapped version of `addEventListener`, which internally calls `wrap` helper.
|
6039 | * This helper "wraps" whole callback inside a try/catch statement, and attached appropriate metadata to it,
|
6040 | * in order for us to make a distinction between wrapped/non-wrapped functions possible.
|
6041 | * If a function was wrapped, it has additional property of `__sentry_wrapped__`, holding the handler.
|
6042 | *
|
6043 | * When someone adds a handler prior to initialization, and then do it again, but after,
|
6044 | * then we have to detach both of them. Otherwise, if we'd detach only wrapped one, it'd be impossible
|
6045 | * to get rid of the initial handler and it'd stick there forever.
|
6046 | */
|
6047 | const wrappedEventHandler = fn;
|
6048 | try {
|
6049 | const originalEventHandler = wrappedEventHandler && wrappedEventHandler.__sentry_wrapped__;
|
6050 | if (originalEventHandler) {
|
6051 | originalRemoveEventListener.call(this, eventName, originalEventHandler, options);
|
6052 | }
|
6053 | }
|
6054 | catch (e) {
|
6055 | // ignore, accessing __sentry_wrapped__ will throw in some Selenium environments
|
6056 | }
|
6057 | return originalRemoveEventListener.call(this, eventName, wrappedEventHandler, options);
|
6058 | };
|
6059 | });
|
6060 | }
|
6061 |
|
6062 | /* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
6063 | /**
|
6064 | * Default Breadcrumbs instrumentations
|
6065 | * TODO: Deprecated - with v6, this will be renamed to `Instrument`
|
6066 | */
|
6067 | class Breadcrumbs {
|
6068 | /**
|
6069 | * @inheritDoc
|
6070 | */
|
6071 | constructor(options) {
|
6072 | /**
|
6073 | * @inheritDoc
|
6074 | */
|
6075 | this.name = Breadcrumbs.id;
|
6076 | this._options = Object.assign({ console: true, dom: true, fetch: true, history: true, sentry: true, xhr: true }, options);
|
6077 | }
|
6078 | /**
|
6079 | * Create a breadcrumb of `sentry` from the events themselves
|
6080 | */
|
6081 | addSentryBreadcrumb(event) {
|
6082 | if (!this._options.sentry) {
|
6083 | return;
|
6084 | }
|
6085 | getCurrentHub().addBreadcrumb({
|
6086 | category: `sentry.${event.type === 'transaction' ? 'transaction' : 'event'}`,
|
6087 | event_id: event.event_id,
|
6088 | level: event.level,
|
6089 | message: getEventDescription(event),
|
6090 | }, {
|
6091 | event,
|
6092 | });
|
6093 | }
|
6094 | /**
|
6095 | * Instrument browser built-ins w/ breadcrumb capturing
|
6096 | * - Console API
|
6097 | * - DOM API (click/typing)
|
6098 | * - XMLHttpRequest API
|
6099 | * - Fetch API
|
6100 | * - History API
|
6101 | */
|
6102 | setupOnce() {
|
6103 | if (this._options.console) {
|
6104 | addInstrumentationHandler('console', _consoleBreadcrumb);
|
6105 | }
|
6106 | if (this._options.dom) {
|
6107 | addInstrumentationHandler('dom', _domBreadcrumb(this._options.dom));
|
6108 | }
|
6109 | if (this._options.xhr) {
|
6110 | addInstrumentationHandler('xhr', _xhrBreadcrumb);
|
6111 | }
|
6112 | if (this._options.fetch) {
|
6113 | addInstrumentationHandler('fetch', _fetchBreadcrumb);
|
6114 | }
|
6115 | if (this._options.history) {
|
6116 | addInstrumentationHandler('history', _historyBreadcrumb);
|
6117 | }
|
6118 | }
|
6119 | }
|
6120 | /**
|
6121 | * @inheritDoc
|
6122 | */
|
6123 | Breadcrumbs.id = 'Breadcrumbs';
|
6124 | /**
|
6125 | * A HOC that creaes a function that creates breadcrumbs from DOM API calls.
|
6126 | * This is a HOC so that we get access to dom options in the closure.
|
6127 | */
|
6128 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
6129 | function _domBreadcrumb(dom) {
|
6130 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
6131 | function _innerDomBreadcrumb(handlerData) {
|
6132 | let target;
|
6133 | let keyAttrs = typeof dom === 'object' ? dom.serializeAttribute : undefined;
|
6134 | if (typeof keyAttrs === 'string') {
|
6135 | keyAttrs = [keyAttrs];
|
6136 | }
|
6137 | // Accessing event.target can throw (see getsentry/raven-js#838, #768)
|
6138 | try {
|
6139 | target = handlerData.event.target
|
6140 | ? htmlTreeAsString(handlerData.event.target, keyAttrs)
|
6141 | : htmlTreeAsString(handlerData.event, keyAttrs);
|
6142 | }
|
6143 | catch (e) {
|
6144 | target = '<unknown>';
|
6145 | }
|
6146 | if (target.length === 0) {
|
6147 | return;
|
6148 | }
|
6149 | getCurrentHub().addBreadcrumb({
|
6150 | category: `ui.${handlerData.name}`,
|
6151 | message: target,
|
6152 | }, {
|
6153 | event: handlerData.event,
|
6154 | name: handlerData.name,
|
6155 | global: handlerData.global,
|
6156 | });
|
6157 | }
|
6158 | return _innerDomBreadcrumb;
|
6159 | }
|
6160 | /**
|
6161 | * Creates breadcrumbs from console API calls
|
6162 | */
|
6163 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
6164 | function _consoleBreadcrumb(handlerData) {
|
6165 | const breadcrumb = {
|
6166 | category: 'console',
|
6167 | data: {
|
6168 | arguments: handlerData.args,
|
6169 | logger: 'console',
|
6170 | },
|
6171 | level: severityFromString(handlerData.level),
|
6172 | message: safeJoin(handlerData.args, ' '),
|
6173 | };
|
6174 | if (handlerData.level === 'assert') {
|
6175 | if (handlerData.args[0] === false) {
|
6176 | breadcrumb.message = `Assertion failed: ${safeJoin(handlerData.args.slice(1), ' ') || 'console.assert'}`;
|
6177 | breadcrumb.data.arguments = handlerData.args.slice(1);
|
6178 | }
|
6179 | else {
|
6180 | // Don't capture a breadcrumb for passed assertions
|
6181 | return;
|
6182 | }
|
6183 | }
|
6184 | getCurrentHub().addBreadcrumb(breadcrumb, {
|
6185 | input: handlerData.args,
|
6186 | level: handlerData.level,
|
6187 | });
|
6188 | }
|
6189 | /**
|
6190 | * Creates breadcrumbs from XHR API calls
|
6191 | */
|
6192 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
6193 | function _xhrBreadcrumb(handlerData) {
|
6194 | if (handlerData.endTimestamp) {
|
6195 | // We only capture complete, non-sentry requests
|
6196 | if (handlerData.xhr.__sentry_own_request__) {
|
6197 | return;
|
6198 | }
|
6199 | const { method, url, status_code, body } = handlerData.xhr.__sentry_xhr__ || {};
|
6200 | getCurrentHub().addBreadcrumb({
|
6201 | category: 'xhr',
|
6202 | data: {
|
6203 | method,
|
6204 | url,
|
6205 | status_code,
|
6206 | },
|
6207 | type: 'http',
|
6208 | }, {
|
6209 | xhr: handlerData.xhr,
|
6210 | input: body,
|
6211 | });
|
6212 | return;
|
6213 | }
|
6214 | }
|
6215 | /**
|
6216 | * Creates breadcrumbs from fetch API calls
|
6217 | */
|
6218 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
6219 | function _fetchBreadcrumb(handlerData) {
|
6220 | // We only capture complete fetch requests
|
6221 | if (!handlerData.endTimestamp) {
|
6222 | return;
|
6223 | }
|
6224 | if (handlerData.fetchData.url.match(/sentry_key/) && handlerData.fetchData.method === 'POST') {
|
6225 | // We will not create breadcrumbs for fetch requests that contain `sentry_key` (internal sentry requests)
|
6226 | return;
|
6227 | }
|
6228 | if (handlerData.error) {
|
6229 | getCurrentHub().addBreadcrumb({
|
6230 | category: 'fetch',
|
6231 | data: handlerData.fetchData,
|
6232 | level: exports.Severity.Error,
|
6233 | type: 'http',
|
6234 | }, {
|
6235 | data: handlerData.error,
|
6236 | input: handlerData.args,
|
6237 | });
|
6238 | }
|
6239 | else {
|
6240 | getCurrentHub().addBreadcrumb({
|
6241 | category: 'fetch',
|
6242 | data: Object.assign(Object.assign({}, handlerData.fetchData), { status_code: handlerData.response.status }),
|
6243 | type: 'http',
|
6244 | }, {
|
6245 | input: handlerData.args,
|
6246 | response: handlerData.response,
|
6247 | });
|
6248 | }
|
6249 | }
|
6250 | /**
|
6251 | * Creates breadcrumbs from history API calls
|
6252 | */
|
6253 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
6254 | function _historyBreadcrumb(handlerData) {
|
6255 | const global = getGlobalObject();
|
6256 | let from = handlerData.from;
|
6257 | let to = handlerData.to;
|
6258 | const parsedLoc = parseUrl(global.location.href);
|
6259 | let parsedFrom = parseUrl(from);
|
6260 | const parsedTo = parseUrl(to);
|
6261 | // Initial pushState doesn't provide `from` information
|
6262 | if (!parsedFrom.path) {
|
6263 | parsedFrom = parsedLoc;
|
6264 | }
|
6265 | // Use only the path component of the URL if the URL matches the current
|
6266 | // document (almost all the time when using pushState)
|
6267 | if (parsedLoc.protocol === parsedTo.protocol && parsedLoc.host === parsedTo.host) {
|
6268 | to = parsedTo.relative;
|
6269 | }
|
6270 | if (parsedLoc.protocol === parsedFrom.protocol && parsedLoc.host === parsedFrom.host) {
|
6271 | from = parsedFrom.relative;
|
6272 | }
|
6273 | getCurrentHub().addBreadcrumb({
|
6274 | category: 'navigation',
|
6275 | data: {
|
6276 | from,
|
6277 | to,
|
6278 | },
|
6279 | });
|
6280 | }
|
6281 |
|
6282 | const DEFAULT_KEY = 'cause';
|
6283 | const DEFAULT_LIMIT = 5;
|
6284 | /** Adds SDK info to an event. */
|
6285 | class LinkedErrors {
|
6286 | /**
|
6287 | * @inheritDoc
|
6288 | */
|
6289 | constructor(options = {}) {
|
6290 | /**
|
6291 | * @inheritDoc
|
6292 | */
|
6293 | this.name = LinkedErrors.id;
|
6294 | this._key = options.key || DEFAULT_KEY;
|
6295 | this._limit = options.limit || DEFAULT_LIMIT;
|
6296 | }
|
6297 | /**
|
6298 | * @inheritDoc
|
6299 | */
|
6300 | setupOnce() {
|
6301 | addGlobalEventProcessor((event, hint) => {
|
6302 | const self = getCurrentHub().getIntegration(LinkedErrors);
|
6303 | return self ? _handler(self._key, self._limit, event, hint) : event;
|
6304 | });
|
6305 | }
|
6306 | }
|
6307 | /**
|
6308 | * @inheritDoc
|
6309 | */
|
6310 | LinkedErrors.id = 'LinkedErrors';
|
6311 | /**
|
6312 | * @inheritDoc
|
6313 | */
|
6314 | function _handler(key, limit, event, hint) {
|
6315 | if (!event.exception || !event.exception.values || !hint || !isInstanceOf(hint.originalException, Error)) {
|
6316 | return event;
|
6317 | }
|
6318 | const linkedErrors = _walkErrorTree(limit, hint.originalException, key);
|
6319 | event.exception.values = [...linkedErrors, ...event.exception.values];
|
6320 | return event;
|
6321 | }
|
6322 | /**
|
6323 | * JSDOC
|
6324 | */
|
6325 | function _walkErrorTree(limit, error, key, stack = []) {
|
6326 | if (!isInstanceOf(error[key], Error) || stack.length + 1 >= limit) {
|
6327 | return stack;
|
6328 | }
|
6329 | const exception = exceptionFromError(error[key]);
|
6330 | return _walkErrorTree(limit, error[key], key, [exception, ...stack]);
|
6331 | }
|
6332 |
|
6333 | const global$1 = getGlobalObject();
|
6334 | /** UserAgent */
|
6335 | class UserAgent {
|
6336 | constructor() {
|
6337 | /**
|
6338 | * @inheritDoc
|
6339 | */
|
6340 | this.name = UserAgent.id;
|
6341 | }
|
6342 | /**
|
6343 | * @inheritDoc
|
6344 | */
|
6345 | setupOnce() {
|
6346 | addGlobalEventProcessor((event) => {
|
6347 | if (getCurrentHub().getIntegration(UserAgent)) {
|
6348 | // if none of the information we want exists, don't bother
|
6349 | if (!global$1.navigator && !global$1.location && !global$1.document) {
|
6350 | return event;
|
6351 | }
|
6352 | // grab as much info as exists and add it to the event
|
6353 | const url = (event.request && event.request.url) || (global$1.location && global$1.location.href);
|
6354 | const { referrer } = global$1.document || {};
|
6355 | const { userAgent } = global$1.navigator || {};
|
6356 | const headers = Object.assign(Object.assign(Object.assign({}, (event.request && event.request.headers)), (referrer && { Referer: referrer })), (userAgent && { 'User-Agent': userAgent }));
|
6357 | const request = Object.assign(Object.assign({}, (url && { url })), { headers });
|
6358 | return Object.assign(Object.assign({}, event), { request });
|
6359 | }
|
6360 | return event;
|
6361 | });
|
6362 | }
|
6363 | }
|
6364 | /**
|
6365 | * @inheritDoc
|
6366 | */
|
6367 | UserAgent.id = 'UserAgent';
|
6368 |
|
6369 | /** Deduplication filter */
|
6370 | class Dedupe {
|
6371 | constructor() {
|
6372 | /**
|
6373 | * @inheritDoc
|
6374 | */
|
6375 | this.name = Dedupe.id;
|
6376 | }
|
6377 | /**
|
6378 | * @inheritDoc
|
6379 | */
|
6380 | setupOnce(addGlobalEventProcessor, getCurrentHub) {
|
6381 | addGlobalEventProcessor((currentEvent) => {
|
6382 | const self = getCurrentHub().getIntegration(Dedupe);
|
6383 | if (self) {
|
6384 | // Juuust in case something goes wrong
|
6385 | try {
|
6386 | if (_shouldDropEvent(currentEvent, self._previousEvent)) {
|
6387 | IS_DEBUG_BUILD && logger.warn('Event dropped due to being a duplicate of previously captured event.');
|
6388 | return null;
|
6389 | }
|
6390 | }
|
6391 | catch (_oO) {
|
6392 | return (self._previousEvent = currentEvent);
|
6393 | }
|
6394 | return (self._previousEvent = currentEvent);
|
6395 | }
|
6396 | return currentEvent;
|
6397 | });
|
6398 | }
|
6399 | }
|
6400 | /**
|
6401 | * @inheritDoc
|
6402 | */
|
6403 | Dedupe.id = 'Dedupe';
|
6404 | /** JSDoc */
|
6405 | function _shouldDropEvent(currentEvent, previousEvent) {
|
6406 | if (!previousEvent) {
|
6407 | return false;
|
6408 | }
|
6409 | if (_isSameMessageEvent(currentEvent, previousEvent)) {
|
6410 | return true;
|
6411 | }
|
6412 | if (_isSameExceptionEvent(currentEvent, previousEvent)) {
|
6413 | return true;
|
6414 | }
|
6415 | return false;
|
6416 | }
|
6417 | /** JSDoc */
|
6418 | function _isSameMessageEvent(currentEvent, previousEvent) {
|
6419 | const currentMessage = currentEvent.message;
|
6420 | const previousMessage = previousEvent.message;
|
6421 | // If neither event has a message property, they were both exceptions, so bail out
|
6422 | if (!currentMessage && !previousMessage) {
|
6423 | return false;
|
6424 | }
|
6425 | // If only one event has a stacktrace, but not the other one, they are not the same
|
6426 | if ((currentMessage && !previousMessage) || (!currentMessage && previousMessage)) {
|
6427 | return false;
|
6428 | }
|
6429 | if (currentMessage !== previousMessage) {
|
6430 | return false;
|
6431 | }
|
6432 | if (!_isSameFingerprint(currentEvent, previousEvent)) {
|
6433 | return false;
|
6434 | }
|
6435 | if (!_isSameStacktrace(currentEvent, previousEvent)) {
|
6436 | return false;
|
6437 | }
|
6438 | return true;
|
6439 | }
|
6440 | /** JSDoc */
|
6441 | function _isSameExceptionEvent(currentEvent, previousEvent) {
|
6442 | const previousException = _getExceptionFromEvent(previousEvent);
|
6443 | const currentException = _getExceptionFromEvent(currentEvent);
|
6444 | if (!previousException || !currentException) {
|
6445 | return false;
|
6446 | }
|
6447 | if (previousException.type !== currentException.type || previousException.value !== currentException.value) {
|
6448 | return false;
|
6449 | }
|
6450 | if (!_isSameFingerprint(currentEvent, previousEvent)) {
|
6451 | return false;
|
6452 | }
|
6453 | if (!_isSameStacktrace(currentEvent, previousEvent)) {
|
6454 | return false;
|
6455 | }
|
6456 | return true;
|
6457 | }
|
6458 | /** JSDoc */
|
6459 | function _isSameStacktrace(currentEvent, previousEvent) {
|
6460 | let currentFrames = _getFramesFromEvent(currentEvent);
|
6461 | let previousFrames = _getFramesFromEvent(previousEvent);
|
6462 | // If neither event has a stacktrace, they are assumed to be the same
|
6463 | if (!currentFrames && !previousFrames) {
|
6464 | return true;
|
6465 | }
|
6466 | // If only one event has a stacktrace, but not the other one, they are not the same
|
6467 | if ((currentFrames && !previousFrames) || (!currentFrames && previousFrames)) {
|
6468 | return false;
|
6469 | }
|
6470 | currentFrames = currentFrames;
|
6471 | previousFrames = previousFrames;
|
6472 | // If number of frames differ, they are not the same
|
6473 | if (previousFrames.length !== currentFrames.length) {
|
6474 | return false;
|
6475 | }
|
6476 | // Otherwise, compare the two
|
6477 | for (let i = 0; i < previousFrames.length; i++) {
|
6478 | const frameA = previousFrames[i];
|
6479 | const frameB = currentFrames[i];
|
6480 | if (frameA.filename !== frameB.filename ||
|
6481 | frameA.lineno !== frameB.lineno ||
|
6482 | frameA.colno !== frameB.colno ||
|
6483 | frameA.function !== frameB.function) {
|
6484 | return false;
|
6485 | }
|
6486 | }
|
6487 | return true;
|
6488 | }
|
6489 | /** JSDoc */
|
6490 | function _isSameFingerprint(currentEvent, previousEvent) {
|
6491 | let currentFingerprint = currentEvent.fingerprint;
|
6492 | let previousFingerprint = previousEvent.fingerprint;
|
6493 | // If neither event has a fingerprint, they are assumed to be the same
|
6494 | if (!currentFingerprint && !previousFingerprint) {
|
6495 | return true;
|
6496 | }
|
6497 | // If only one event has a fingerprint, but not the other one, they are not the same
|
6498 | if ((currentFingerprint && !previousFingerprint) || (!currentFingerprint && previousFingerprint)) {
|
6499 | return false;
|
6500 | }
|
6501 | currentFingerprint = currentFingerprint;
|
6502 | previousFingerprint = previousFingerprint;
|
6503 | // Otherwise, compare the two
|
6504 | try {
|
6505 | return !!(currentFingerprint.join('') === previousFingerprint.join(''));
|
6506 | }
|
6507 | catch (_oO) {
|
6508 | return false;
|
6509 | }
|
6510 | }
|
6511 | /** JSDoc */
|
6512 | function _getExceptionFromEvent(event) {
|
6513 | return event.exception && event.exception.values && event.exception.values[0];
|
6514 | }
|
6515 | /** JSDoc */
|
6516 | function _getFramesFromEvent(event) {
|
6517 | const exception = event.exception;
|
6518 | if (exception) {
|
6519 | try {
|
6520 | // @ts-ignore Object could be undefined
|
6521 | return exception.values[0].stacktrace.frames;
|
6522 | }
|
6523 | catch (_oO) {
|
6524 | return undefined;
|
6525 | }
|
6526 | }
|
6527 | else if (event.stacktrace) {
|
6528 | return event.stacktrace.frames;
|
6529 | }
|
6530 | return undefined;
|
6531 | }
|
6532 |
|
6533 | var BrowserIntegrations = /*#__PURE__*/Object.freeze({
|
6534 | __proto__: null,
|
6535 | GlobalHandlers: GlobalHandlers,
|
6536 | TryCatch: TryCatch,
|
6537 | Breadcrumbs: Breadcrumbs,
|
6538 | LinkedErrors: LinkedErrors,
|
6539 | UserAgent: UserAgent,
|
6540 | Dedupe: Dedupe
|
6541 | });
|
6542 |
|
6543 | /**
|
6544 | * The Sentry Browser SDK Client.
|
6545 | *
|
6546 | * @see BrowserOptions for documentation on configuration options.
|
6547 | * @see SentryClient for usage documentation.
|
6548 | */
|
6549 | class BrowserClient extends BaseClient {
|
6550 | /**
|
6551 | * Creates a new Browser SDK instance.
|
6552 | *
|
6553 | * @param options Configuration options for this SDK.
|
6554 | */
|
6555 | constructor(options = {}) {
|
6556 | options._metadata = options._metadata || {};
|
6557 | options._metadata.sdk = options._metadata.sdk || {
|
6558 | name: 'sentry.javascript.browser',
|
6559 | packages: [
|
6560 | {
|
6561 | name: 'npm:@sentry/browser',
|
6562 | version: SDK_VERSION,
|
6563 | },
|
6564 | ],
|
6565 | version: SDK_VERSION,
|
6566 | };
|
6567 | super(BrowserBackend, options);
|
6568 | }
|
6569 | /**
|
6570 | * Show a report dialog to the user to send feedback to a specific event.
|
6571 | *
|
6572 | * @param options Set individual options for the dialog
|
6573 | */
|
6574 | showReportDialog(options = {}) {
|
6575 | // doesn't work without a document (React Native)
|
6576 | const document = getGlobalObject().document;
|
6577 | if (!document) {
|
6578 | return;
|
6579 | }
|
6580 | if (!this._isEnabled()) {
|
6581 | IS_DEBUG_BUILD && logger.error('Trying to call showReportDialog with Sentry Client disabled');
|
6582 | return;
|
6583 | }
|
6584 | injectReportDialog(Object.assign(Object.assign({}, options), { dsn: options.dsn || this.getDsn() }));
|
6585 | }
|
6586 | /**
|
6587 | * @inheritDoc
|
6588 | */
|
6589 | _prepareEvent(event, scope, hint) {
|
6590 | event.platform = event.platform || 'javascript';
|
6591 | return super._prepareEvent(event, scope, hint);
|
6592 | }
|
6593 | /**
|
6594 | * @inheritDoc
|
6595 | */
|
6596 | _sendEvent(event) {
|
6597 | const integration = this.getIntegration(Breadcrumbs);
|
6598 | if (integration) {
|
6599 | integration.addSentryBreadcrumb(event);
|
6600 | }
|
6601 | super._sendEvent(event);
|
6602 | }
|
6603 | }
|
6604 |
|
6605 | const defaultIntegrations = [
|
6606 | new InboundFilters(),
|
6607 | new FunctionToString(),
|
6608 | new TryCatch(),
|
6609 | new Breadcrumbs(),
|
6610 | new GlobalHandlers(),
|
6611 | new LinkedErrors(),
|
6612 | new Dedupe(),
|
6613 | new UserAgent(),
|
6614 | ];
|
6615 | /**
|
6616 | * The Sentry Browser SDK Client.
|
6617 | *
|
6618 | * To use this SDK, call the {@link init} function as early as possible when
|
6619 | * loading the web page. To set context information or send manual events, use
|
6620 | * the provided methods.
|
6621 | *
|
6622 | * @example
|
6623 | *
|
6624 | * ```
|
6625 | *
|
6626 | * import { init } from '@sentry/browser';
|
6627 | *
|
6628 | * init({
|
6629 | * dsn: '__DSN__',
|
6630 | * // ...
|
6631 | * });
|
6632 | * ```
|
6633 | *
|
6634 | * @example
|
6635 | * ```
|
6636 | *
|
6637 | * import { configureScope } from '@sentry/browser';
|
6638 | * configureScope((scope: Scope) => {
|
6639 | * scope.setExtra({ battery: 0.7 });
|
6640 | * scope.setTag({ user_mode: 'admin' });
|
6641 | * scope.setUser({ id: '4711' });
|
6642 | * });
|
6643 | * ```
|
6644 | *
|
6645 | * @example
|
6646 | * ```
|
6647 | *
|
6648 | * import { addBreadcrumb } from '@sentry/browser';
|
6649 | * addBreadcrumb({
|
6650 | * message: 'My Breadcrumb',
|
6651 | * // ...
|
6652 | * });
|
6653 | * ```
|
6654 | *
|
6655 | * @example
|
6656 | *
|
6657 | * ```
|
6658 | *
|
6659 | * import * as Sentry from '@sentry/browser';
|
6660 | * Sentry.captureMessage('Hello, world!');
|
6661 | * Sentry.captureException(new Error('Good bye'));
|
6662 | * Sentry.captureEvent({
|
6663 | * message: 'Manual',
|
6664 | * stacktrace: [
|
6665 | * // ...
|
6666 | * ],
|
6667 | * });
|
6668 | * ```
|
6669 | *
|
6670 | * @see {@link BrowserOptions} for documentation on configuration options.
|
6671 | */
|
6672 | function init(options = {}) {
|
6673 | if (options.defaultIntegrations === undefined) {
|
6674 | options.defaultIntegrations = defaultIntegrations;
|
6675 | }
|
6676 | if (options.release === undefined) {
|
6677 | const window = getGlobalObject();
|
6678 | // This supports the variable that sentry-webpack-plugin injects
|
6679 | if (window.SENTRY_RELEASE && window.SENTRY_RELEASE.id) {
|
6680 | options.release = window.SENTRY_RELEASE.id;
|
6681 | }
|
6682 | }
|
6683 | if (options.autoSessionTracking === undefined) {
|
6684 | options.autoSessionTracking = true;
|
6685 | }
|
6686 | if (options.sendClientReports === undefined) {
|
6687 | options.sendClientReports = true;
|
6688 | }
|
6689 | initAndBind(BrowserClient, options);
|
6690 | if (options.autoSessionTracking) {
|
6691 | startSessionTracking();
|
6692 | }
|
6693 | }
|
6694 | /**
|
6695 | * Present the user with a report dialog.
|
6696 | *
|
6697 | * @param options Everything is optional, we try to fetch all info need from the global scope.
|
6698 | */
|
6699 | function showReportDialog(options = {}) {
|
6700 | const hub = getCurrentHub();
|
6701 | const scope = hub.getScope();
|
6702 | if (scope) {
|
6703 | options.user = Object.assign(Object.assign({}, scope.getUser()), options.user);
|
6704 | }
|
6705 | if (!options.eventId) {
|
6706 | options.eventId = hub.lastEventId();
|
6707 | }
|
6708 | const client = hub.getClient();
|
6709 | if (client) {
|
6710 | client.showReportDialog(options);
|
6711 | }
|
6712 | }
|
6713 | /**
|
6714 | * This is the getter for lastEventId.
|
6715 | *
|
6716 | * @returns The last event id of a captured event.
|
6717 | */
|
6718 | function lastEventId() {
|
6719 | return getCurrentHub().lastEventId();
|
6720 | }
|
6721 | /**
|
6722 | * This function is here to be API compatible with the loader.
|
6723 | * @hidden
|
6724 | */
|
6725 | function forceLoad() {
|
6726 | // Noop
|
6727 | }
|
6728 | /**
|
6729 | * This function is here to be API compatible with the loader.
|
6730 | * @hidden
|
6731 | */
|
6732 | function onLoad(callback) {
|
6733 | callback();
|
6734 | }
|
6735 | /**
|
6736 | * Call `flush()` on the current client, if there is one. See {@link Client.flush}.
|
6737 | *
|
6738 | * @param timeout Maximum time in ms the client should wait to flush its event queue. Omitting this parameter will cause
|
6739 | * the client to wait until all events are sent before resolving the promise.
|
6740 | * @returns A promise which resolves to `true` if the queue successfully drains before the timeout, or `false` if it
|
6741 | * doesn't (or if there's no client defined).
|
6742 | */
|
6743 | function flush(timeout) {
|
6744 | const client = getCurrentHub().getClient();
|
6745 | if (client) {
|
6746 | return client.flush(timeout);
|
6747 | }
|
6748 | IS_DEBUG_BUILD && logger.warn('Cannot flush events. No client defined.');
|
6749 | return resolvedSyncPromise(false);
|
6750 | }
|
6751 | /**
|
6752 | * Call `close()` on the current client, if there is one. See {@link Client.close}.
|
6753 | *
|
6754 | * @param timeout Maximum time in ms the client should wait to flush its event queue before shutting down. Omitting this
|
6755 | * parameter will cause the client to wait until all events are sent before disabling itself.
|
6756 | * @returns A promise which resolves to `true` if the queue successfully drains before the timeout, or `false` if it
|
6757 | * doesn't (or if there's no client defined).
|
6758 | */
|
6759 | function close(timeout) {
|
6760 | const client = getCurrentHub().getClient();
|
6761 | if (client) {
|
6762 | return client.close(timeout);
|
6763 | }
|
6764 | IS_DEBUG_BUILD && logger.warn('Cannot flush events and disable SDK. No client defined.');
|
6765 | return resolvedSyncPromise(false);
|
6766 | }
|
6767 | /**
|
6768 | * Wrap code within a try/catch block so the SDK is able to capture errors.
|
6769 | *
|
6770 | * @param fn A function to wrap.
|
6771 | *
|
6772 | * @returns The result of wrapped function call.
|
6773 | */
|
6774 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
6775 | function wrap(fn) {
|
6776 | return wrap$1(fn)();
|
6777 | }
|
6778 | function startSessionOnHub(hub) {
|
6779 | hub.startSession({ ignoreDuration: true });
|
6780 | hub.captureSession();
|
6781 | }
|
6782 | /**
|
6783 | * Enable automatic Session Tracking for the initial page load.
|
6784 | */
|
6785 | function startSessionTracking() {
|
6786 | const window = getGlobalObject();
|
6787 | const document = window.document;
|
6788 | if (typeof document === 'undefined') {
|
6789 | IS_DEBUG_BUILD && logger.warn('Session tracking in non-browser environment with @sentry/browser is not supported.');
|
6790 | return;
|
6791 | }
|
6792 | const hub = getCurrentHub();
|
6793 | // The only way for this to be false is for there to be a version mismatch between @sentry/browser (>= 6.0.0) and
|
6794 | // @sentry/hub (< 5.27.0). In the simple case, there won't ever be such a mismatch, because the two packages are
|
6795 | // pinned at the same version in package.json, but there are edge cases where it's possible. See
|
6796 | // https://github.com/getsentry/sentry-javascript/issues/3207 and
|
6797 | // https://github.com/getsentry/sentry-javascript/issues/3234 and
|
6798 | // https://github.com/getsentry/sentry-javascript/issues/3278.
|
6799 | if (!hub.captureSession) {
|
6800 | return;
|
6801 | }
|
6802 | // The session duration for browser sessions does not track a meaningful
|
6803 | // concept that can be used as a metric.
|
6804 | // Automatically captured sessions are akin to page views, and thus we
|
6805 | // discard their duration.
|
6806 | startSessionOnHub(hub);
|
6807 | // We want to create a session for every navigation as well
|
6808 | addInstrumentationHandler('history', ({ from, to }) => {
|
6809 | // Don't create an additional session for the initial route or if the location did not change
|
6810 | if (!(from === undefined || from === to)) {
|
6811 | startSessionOnHub(getCurrentHub());
|
6812 | }
|
6813 | });
|
6814 | }
|
6815 |
|
6816 | // TODO: Remove in the next major release and rely only on @sentry/core SDK_VERSION and SdkInfo metadata
|
6817 | const SDK_NAME = 'sentry.javascript.browser';
|
6818 |
|
6819 | let windowIntegrations = {};
|
6820 | // This block is needed to add compatibility with the integrations packages when used with a CDN
|
6821 | const _window = getGlobalObject();
|
6822 | if (_window.Sentry && _window.Sentry.Integrations) {
|
6823 | windowIntegrations = _window.Sentry.Integrations;
|
6824 | }
|
6825 | const INTEGRATIONS = Object.assign(Object.assign(Object.assign({}, windowIntegrations), CoreIntegrations), BrowserIntegrations);
|
6826 |
|
6827 | exports.BrowserClient = BrowserClient;
|
6828 | exports.Hub = Hub;
|
6829 | exports.Integrations = INTEGRATIONS;
|
6830 | exports.SDK_NAME = SDK_NAME;
|
6831 | exports.SDK_VERSION = SDK_VERSION;
|
6832 | exports.Scope = Scope;
|
6833 | exports.Session = Session;
|
6834 | exports.Transports = index;
|
6835 | exports.addBreadcrumb = addBreadcrumb;
|
6836 | exports.addGlobalEventProcessor = addGlobalEventProcessor;
|
6837 | exports.captureEvent = captureEvent;
|
6838 | exports.captureException = captureException;
|
6839 | exports.captureMessage = captureMessage;
|
6840 | exports.close = close;
|
6841 | exports.configureScope = configureScope;
|
6842 | exports.defaultIntegrations = defaultIntegrations;
|
6843 | exports.eventFromException = eventFromException;
|
6844 | exports.eventFromMessage = eventFromMessage;
|
6845 | exports.flush = flush;
|
6846 | exports.forceLoad = forceLoad;
|
6847 | exports.getCurrentHub = getCurrentHub;
|
6848 | exports.getHubFromCarrier = getHubFromCarrier;
|
6849 | exports.init = init;
|
6850 | exports.injectReportDialog = injectReportDialog;
|
6851 | exports.lastEventId = lastEventId;
|
6852 | exports.makeMain = makeMain;
|
6853 | exports.onLoad = onLoad;
|
6854 | exports.setContext = setContext;
|
6855 | exports.setExtra = setExtra;
|
6856 | exports.setExtras = setExtras;
|
6857 | exports.setTag = setTag;
|
6858 | exports.setTags = setTags;
|
6859 | exports.setUser = setUser;
|
6860 | exports.showReportDialog = showReportDialog;
|
6861 | exports.startTransaction = startTransaction;
|
6862 | exports.withScope = withScope;
|
6863 | exports.wrap = wrap;
|
6864 |
|
6865 | return exports;
|
6866 |
|
6867 | })({});
|
6868 | //# sourceMappingURL=bundle.es6.js.map
|