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