From bce557cc2dc767628bed6aac87301a1be7c5431b Mon Sep 17 00:00:00 2001 From: rxliuli Date: Tue, 4 Nov 2025 05:03:50 +0800 Subject: init commit --- .../node_modules/@sentry/browser/esm/client.js | 139 +++++++ .../@sentry/browser/esm/eventbuilder.js | 304 ++++++++++++++ .../node_modules/@sentry/browser/esm/helpers.js | 154 ++++++++ .../node_modules/@sentry/browser/esm/index.js | 39 ++ .../browser/esm/integrations/breadcrumbs.js | 320 +++++++++++++++ .../@sentry/browser/esm/integrations/dedupe.js | 211 ++++++++++ .../browser/esm/integrations/globalhandlers.js | 248 ++++++++++++ .../browser/esm/integrations/httpcontext.js | 47 +++ .../browser/esm/integrations/linkederrors.js | 87 ++++ .../@sentry/browser/esm/integrations/trycatch.js | 281 +++++++++++++ .../@sentry/browser/esm/profiling/hubextensions.js | 240 +++++++++++ .../@sentry/browser/esm/profiling/integration.js | 81 ++++ .../@sentry/browser/esm/profiling/utils.js | 438 +++++++++++++++++++++ .../logger/node_modules/@sentry/browser/esm/sdk.js | 293 ++++++++++++++ .../@sentry/browser/esm/stack-parsers.js | 168 ++++++++ .../@sentry/browser/esm/transports/fetch.js | 64 +++ .../@sentry/browser/esm/transports/offline.js | 133 +++++++ .../@sentry/browser/esm/transports/utils.js | 85 ++++ .../@sentry/browser/esm/transports/xhr.js | 52 +++ .../@sentry/browser/esm/userfeedback.js | 41 ++ 20 files changed, 3425 insertions(+) create mode 100644 shared/logger/node_modules/@sentry/browser/esm/client.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/eventbuilder.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/helpers.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/index.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/integrations/breadcrumbs.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/integrations/dedupe.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/integrations/globalhandlers.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/integrations/httpcontext.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/integrations/linkederrors.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/integrations/trycatch.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/profiling/hubextensions.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/profiling/integration.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/profiling/utils.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/sdk.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/stack-parsers.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/transports/fetch.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/transports/offline.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/transports/utils.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/transports/xhr.js create mode 100644 shared/logger/node_modules/@sentry/browser/esm/userfeedback.js (limited to 'shared/logger/node_modules/@sentry/browser/esm') diff --git a/shared/logger/node_modules/@sentry/browser/esm/client.js b/shared/logger/node_modules/@sentry/browser/esm/client.js new file mode 100644 index 0000000..a171d99 --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/client.js @@ -0,0 +1,139 @@ +import { BaseClient, SDK_VERSION } from '@sentry/core'; +import { getSDKSource, logger, createClientReportEnvelope, dsnToString } from '@sentry/utils'; +import { eventFromException, eventFromMessage } from './eventbuilder.js'; +import { WINDOW } from './helpers.js'; +import { BREADCRUMB_INTEGRATION_ID } from './integrations/breadcrumbs.js'; +import { createUserFeedbackEnvelope } from './userfeedback.js'; + +/** + * Configuration options for the Sentry Browser SDK. + * @see @sentry/types Options for more information. + */ + +/** + * The Sentry Browser SDK Client. + * + * @see BrowserOptions for documentation on configuration options. + * @see SentryClient for usage documentation. + */ +class BrowserClient extends BaseClient { + /** + * Creates a new Browser SDK instance. + * + * @param options Configuration options for this SDK. + */ + constructor(options) { + const sdkSource = WINDOW.SENTRY_SDK_SOURCE || getSDKSource(); + + options._metadata = options._metadata || {}; + options._metadata.sdk = options._metadata.sdk || { + name: 'sentry.javascript.browser', + packages: [ + { + name: `${sdkSource}:@sentry/browser`, + version: SDK_VERSION, + }, + ], + version: SDK_VERSION, + }; + + super(options); + + if (options.sendClientReports && WINDOW.document) { + WINDOW.document.addEventListener('visibilitychange', () => { + if (WINDOW.document.visibilityState === 'hidden') { + this._flushOutcomes(); + } + }); + } + } + + /** + * @inheritDoc + */ + eventFromException(exception, hint) { + return eventFromException(this._options.stackParser, exception, hint, this._options.attachStacktrace); + } + + /** + * @inheritDoc + */ + eventFromMessage( + message, + // eslint-disable-next-line deprecation/deprecation + level = 'info', + hint, + ) { + return eventFromMessage(this._options.stackParser, message, level, hint, this._options.attachStacktrace); + } + + /** + * @inheritDoc + */ + sendEvent(event, hint) { + // We only want to add the sentry event breadcrumb when the user has the breadcrumb integration installed and + // activated its `sentry` option. + // We also do not want to use the `Breadcrumbs` class here directly, because we do not want it to be included in + // bundles, if it is not used by the SDK. + // This all sadly is a bit ugly, but we currently don't have a "pre-send" hook on the integrations so we do it this + // way for now. + const breadcrumbIntegration = this.getIntegrationById(BREADCRUMB_INTEGRATION_ID) ; + // We check for definedness of `addSentryBreadcrumb` in case users provided their own integration with id + // "Breadcrumbs" that does not have this function. + if (breadcrumbIntegration && breadcrumbIntegration.addSentryBreadcrumb) { + breadcrumbIntegration.addSentryBreadcrumb(event); + } + + super.sendEvent(event, hint); + } + + /** + * Sends user feedback to Sentry. + */ + captureUserFeedback(feedback) { + if (!this._isEnabled()) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('SDK not enabled, will not capture user feedback.'); + return; + } + + const envelope = createUserFeedbackEnvelope(feedback, { + metadata: this.getSdkMetadata(), + dsn: this.getDsn(), + tunnel: this.getOptions().tunnel, + }); + void this._sendEnvelope(envelope); + } + + /** + * @inheritDoc + */ + _prepareEvent(event, hint, scope) { + event.platform = event.platform || 'javascript'; + return super._prepareEvent(event, hint, scope); + } + + /** + * Sends client reports as an envelope. + */ + _flushOutcomes() { + const outcomes = this._clearOutcomes(); + + if (outcomes.length === 0) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('No outcomes to send'); + return; + } + + if (!this._dsn) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('No dsn provided, will not send outcomes'); + return; + } + + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('Sending outcomes:', outcomes); + + const envelope = createClientReportEnvelope(outcomes, this._options.tunnel && dsnToString(this._dsn)); + void this._sendEnvelope(envelope); + } +} + +export { BrowserClient }; +//# sourceMappingURL=client.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/eventbuilder.js b/shared/logger/node_modules/@sentry/browser/esm/eventbuilder.js new file mode 100644 index 0000000..08e5653 --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/eventbuilder.js @@ -0,0 +1,304 @@ +import { getCurrentHub } from '@sentry/core'; +import { addExceptionMechanism, resolvedSyncPromise, isErrorEvent, isDOMError, isDOMException, addExceptionTypeValue, isError, isPlainObject, isEvent, normalizeToSize, extractExceptionKeysForMessage } from '@sentry/utils'; + +/** + * This function creates an exception from a JavaScript Error + */ +function exceptionFromError(stackParser, ex) { + // Get the frames first since Opera can lose the stack if we touch anything else first + const frames = parseStackFrames(stackParser, ex); + + const exception = { + type: ex && ex.name, + value: extractMessage(ex), + }; + + if (frames.length) { + exception.stacktrace = { frames }; + } + + if (exception.type === undefined && exception.value === '') { + exception.value = 'Unrecoverable error caught'; + } + + return exception; +} + +/** + * @hidden + */ +function eventFromPlainObject( + stackParser, + exception, + syntheticException, + isUnhandledRejection, +) { + const hub = getCurrentHub(); + const client = hub.getClient(); + const normalizeDepth = client && client.getOptions().normalizeDepth; + + const event = { + exception: { + values: [ + { + type: isEvent(exception) ? exception.constructor.name : isUnhandledRejection ? 'UnhandledRejection' : 'Error', + value: getNonErrorObjectExceptionValue(exception, { isUnhandledRejection }), + }, + ], + }, + extra: { + __serialized__: normalizeToSize(exception, normalizeDepth), + }, + }; + + if (syntheticException) { + const frames = parseStackFrames(stackParser, syntheticException); + if (frames.length) { + // event.exception.values[0] has been set above + (event.exception ).values[0].stacktrace = { frames }; + } + } + + return event; +} + +/** + * @hidden + */ +function eventFromError(stackParser, ex) { + return { + exception: { + values: [exceptionFromError(stackParser, ex)], + }, + }; +} + +/** Parses stack frames from an error */ +function parseStackFrames( + stackParser, + ex, +) { + // Access and store the stacktrace property before doing ANYTHING + // else to it because Opera is not very good at providing it + // reliably in other circumstances. + const stacktrace = ex.stacktrace || ex.stack || ''; + + const popSize = getPopSize(ex); + + try { + return stackParser(stacktrace, popSize); + } catch (e) { + // no-empty + } + + return []; +} + +// Based on our own mapping pattern - https://github.com/getsentry/sentry/blob/9f08305e09866c8bd6d0c24f5b0aabdd7dd6c59c/src/sentry/lang/javascript/errormapping.py#L83-L108 +const reactMinifiedRegexp = /Minified React error #\d+;/i; + +function getPopSize(ex) { + if (ex) { + if (typeof ex.framesToPop === 'number') { + return ex.framesToPop; + } + + if (reactMinifiedRegexp.test(ex.message)) { + return 1; + } + } + + return 0; +} + +/** + * There are cases where stacktrace.message is an Event object + * https://github.com/getsentry/sentry-javascript/issues/1949 + * In this specific case we try to extract stacktrace.message.error.message + */ +function extractMessage(ex) { + const message = ex && ex.message; + if (!message) { + return 'No error message'; + } + if (message.error && typeof message.error.message === 'string') { + return message.error.message; + } + return message; +} + +/** + * Creates an {@link Event} from all inputs to `captureException` and non-primitive inputs to `captureMessage`. + * @hidden + */ +function eventFromException( + stackParser, + exception, + hint, + attachStacktrace, +) { + const syntheticException = (hint && hint.syntheticException) || undefined; + const event = eventFromUnknownInput(stackParser, exception, syntheticException, attachStacktrace); + addExceptionMechanism(event); // defaults to { type: 'generic', handled: true } + event.level = 'error'; + if (hint && hint.event_id) { + event.event_id = hint.event_id; + } + return resolvedSyncPromise(event); +} + +/** + * Builds and Event from a Message + * @hidden + */ +function eventFromMessage( + stackParser, + message, + // eslint-disable-next-line deprecation/deprecation + level = 'info', + hint, + attachStacktrace, +) { + const syntheticException = (hint && hint.syntheticException) || undefined; + const event = eventFromString(stackParser, message, syntheticException, attachStacktrace); + event.level = level; + if (hint && hint.event_id) { + event.event_id = hint.event_id; + } + return resolvedSyncPromise(event); +} + +/** + * @hidden + */ +function eventFromUnknownInput( + stackParser, + exception, + syntheticException, + attachStacktrace, + isUnhandledRejection, +) { + let event; + + if (isErrorEvent(exception ) && (exception ).error) { + // If it is an ErrorEvent with `error` property, extract it to get actual Error + const errorEvent = exception ; + return eventFromError(stackParser, errorEvent.error ); + } + + // If it is a `DOMError` (which is a legacy API, but still supported in some browsers) then we just extract the name + // and message, as it doesn't provide anything else. According to the spec, all `DOMExceptions` should also be + // `Error`s, but that's not the case in IE11, so in that case we treat it the same as we do a `DOMError`. + // + // https://developer.mozilla.org/en-US/docs/Web/API/DOMError + // https://developer.mozilla.org/en-US/docs/Web/API/DOMException + // https://webidl.spec.whatwg.org/#es-DOMException-specialness + if (isDOMError(exception) || isDOMException(exception )) { + const domException = exception ; + + if ('stack' in (exception )) { + event = eventFromError(stackParser, exception ); + } else { + const name = domException.name || (isDOMError(domException) ? 'DOMError' : 'DOMException'); + const message = domException.message ? `${name}: ${domException.message}` : name; + event = eventFromString(stackParser, message, syntheticException, attachStacktrace); + addExceptionTypeValue(event, message); + } + if ('code' in domException) { + // eslint-disable-next-line deprecation/deprecation + event.tags = { ...event.tags, 'DOMException.code': `${domException.code}` }; + } + + return event; + } + if (isError(exception)) { + // we have a real Error object, do nothing + return eventFromError(stackParser, exception); + } + if (isPlainObject(exception) || isEvent(exception)) { + // If it's a plain object or an instance of `Event` (the built-in JS kind, not this SDK's `Event` type), serialize + // it manually. This will allow us to group events based on top-level keys which is much better than creating a new + // group on any key/value change. + const objectException = exception ; + event = eventFromPlainObject(stackParser, objectException, syntheticException, isUnhandledRejection); + addExceptionMechanism(event, { + synthetic: true, + }); + return event; + } + + // If none of previous checks were valid, then it means that it's not: + // - an instance of DOMError + // - an instance of DOMException + // - an instance of Event + // - an instance of Error + // - a valid ErrorEvent (one with an error property) + // - a plain Object + // + // So bail out and capture it as a simple message: + event = eventFromString(stackParser, exception , syntheticException, attachStacktrace); + addExceptionTypeValue(event, `${exception}`, undefined); + addExceptionMechanism(event, { + synthetic: true, + }); + + return event; +} + +/** + * @hidden + */ +function eventFromString( + stackParser, + input, + syntheticException, + attachStacktrace, +) { + const event = { + message: input, + }; + + if (attachStacktrace && syntheticException) { + const frames = parseStackFrames(stackParser, syntheticException); + if (frames.length) { + event.exception = { + values: [{ value: input, stacktrace: { frames } }], + }; + } + } + + return event; +} + +function getNonErrorObjectExceptionValue( + exception, + { isUnhandledRejection }, +) { + const keys = extractExceptionKeysForMessage(exception); + const captureType = isUnhandledRejection ? 'promise rejection' : 'exception'; + + // Some ErrorEvent instances do not have an `error` property, which is why they are not handled before + // We still want to try to get a decent message for these cases + if (isErrorEvent(exception)) { + return `Event \`ErrorEvent\` captured as ${captureType} with message \`${exception.message}\``; + } + + if (isEvent(exception)) { + const className = getObjectClassName(exception); + return `Event \`${className}\` (type=${exception.type}) captured as ${captureType}`; + } + + return `Object captured as ${captureType} with keys: ${keys}`; +} + +function getObjectClassName(obj) { + try { + const prototype = Object.getPrototypeOf(obj); + return prototype ? prototype.constructor.name : undefined; + } catch (e) { + // ignore errors here + } +} + +export { eventFromError, eventFromException, eventFromMessage, eventFromPlainObject, eventFromString, eventFromUnknownInput, exceptionFromError, parseStackFrames }; +//# sourceMappingURL=eventbuilder.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/helpers.js b/shared/logger/node_modules/@sentry/browser/esm/helpers.js new file mode 100644 index 0000000..cfa1229 --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/helpers.js @@ -0,0 +1,154 @@ +import { withScope, captureException } from '@sentry/core'; +import { GLOBAL_OBJ, getOriginalFunction, markFunctionWrapped, addNonEnumerableProperty, addExceptionTypeValue, addExceptionMechanism } from '@sentry/utils'; + +const WINDOW = GLOBAL_OBJ ; + +let ignoreOnError = 0; + +/** + * @hidden + */ +function shouldIgnoreOnError() { + return ignoreOnError > 0; +} + +/** + * @hidden + */ +function ignoreNextOnError() { + // onerror should trigger before setTimeout + ignoreOnError++; + setTimeout(() => { + ignoreOnError--; + }); +} + +/** + * Instruments the given function and sends an event to Sentry every time the + * function throws an exception. + * + * @param fn A function to wrap. It is generally safe to pass an unbound function, because the returned wrapper always + * has a correct `this` context. + * @returns The wrapped function. + * @hidden + */ +function wrap( + fn, + options + + = {}, + before, + // eslint-disable-next-line @typescript-eslint/no-explicit-any +) { + // for future readers what this does is wrap a function and then create + // a bi-directional wrapping between them. + // + // example: wrapped = wrap(original); + // original.__sentry_wrapped__ -> wrapped + // wrapped.__sentry_original__ -> original + + if (typeof fn !== 'function') { + return fn; + } + + try { + // if we're dealing with a function that was previously wrapped, return + // the original wrapper. + const wrapper = fn.__sentry_wrapped__; + if (wrapper) { + return wrapper; + } + + // We don't wanna wrap it twice + if (getOriginalFunction(fn)) { + return fn; + } + } catch (e) { + // Just accessing custom props in some Selenium environments + // can cause a "Permission denied" exception (see raven-js#495). + // Bail on wrapping and return the function as-is (defers to window.onerror). + return fn; + } + + /* eslint-disable prefer-rest-params */ + // It is important that `sentryWrapped` is not an arrow function to preserve the context of `this` + const sentryWrapped = function () { + const args = Array.prototype.slice.call(arguments); + + try { + if (before && typeof before === 'function') { + before.apply(this, arguments); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + const wrappedArguments = args.map((arg) => wrap(arg, options)); + + // Attempt to invoke user-land function + // NOTE: If you are a Sentry user, and you are seeing this stack frame, it + // means the sentry.javascript SDK caught an error invoking your application code. This + // is expected behavior and NOT indicative of a bug with sentry.javascript. + return fn.apply(this, wrappedArguments); + } catch (ex) { + ignoreNextOnError(); + + withScope((scope) => { + scope.addEventProcessor((event) => { + if (options.mechanism) { + addExceptionTypeValue(event, undefined, undefined); + addExceptionMechanism(event, options.mechanism); + } + + event.extra = { + ...event.extra, + arguments: args, + }; + + return event; + }); + + captureException(ex); + }); + + throw ex; + } + }; + /* eslint-enable prefer-rest-params */ + + // Accessing some objects may throw + // ref: https://github.com/getsentry/sentry-javascript/issues/1168 + try { + for (const property in fn) { + if (Object.prototype.hasOwnProperty.call(fn, property)) { + sentryWrapped[property] = fn[property]; + } + } + } catch (_oO) {} // eslint-disable-line no-empty + + // Signal that this function has been wrapped/filled already + // for both debugging and to prevent it to being wrapped/filled twice + markFunctionWrapped(sentryWrapped, fn); + + addNonEnumerableProperty(fn, '__sentry_wrapped__', sentryWrapped); + + // Restore original function name (not all browsers allow that) + try { + const descriptor = Object.getOwnPropertyDescriptor(sentryWrapped, 'name') ; + if (descriptor.configurable) { + Object.defineProperty(sentryWrapped, 'name', { + get() { + return fn.name; + }, + }); + } + // eslint-disable-next-line no-empty + } catch (_oO) {} + + return sentryWrapped; +} + +/** + * All properties the report dialog supports + */ + +export { WINDOW, ignoreNextOnError, shouldIgnoreOnError, wrap }; +//# sourceMappingURL=helpers.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/index.js b/shared/logger/node_modules/@sentry/browser/esm/index.js new file mode 100644 index 0000000..188f9c0 --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/index.js @@ -0,0 +1,39 @@ +import { Integrations } from '@sentry/core'; +export { FunctionToString, Hub, InboundFilters, SDK_VERSION, Scope, addBreadcrumb, addGlobalEventProcessor, addTracingExtensions, captureEvent, captureException, captureMessage, configureScope, createTransport, extractTraceparentData, getActiveTransaction, getCurrentHub, getHubFromCarrier, makeMain, makeMultiplexedTransport, setContext, setExtra, setExtras, setTag, setTags, setUser, spanStatusfromHttpCode, startTransaction, trace, withScope } from '@sentry/core'; +import { WINDOW } from './helpers.js'; +export { WINDOW } from './helpers.js'; +export { BrowserClient } from './client.js'; +export { makeFetchTransport } from './transports/fetch.js'; +export { makeXHRTransport } from './transports/xhr.js'; +export { chromeStackLineParser, defaultStackLineParsers, defaultStackParser, geckoStackLineParser, opera10StackLineParser, opera11StackLineParser, winjsStackLineParser } from './stack-parsers.js'; +export { eventFromException, eventFromMessage } from './eventbuilder.js'; +export { createUserFeedbackEnvelope } from './userfeedback.js'; +export { captureUserFeedback, close, defaultIntegrations, flush, forceLoad, init, lastEventId, onLoad, showReportDialog, wrap } from './sdk.js'; +import * as index from './integrations/index.js'; +export { Replay } from '@sentry/replay'; +export { BrowserTracing, defaultRequestInstrumentationOptions, instrumentOutgoingRequests } from '@sentry-internal/tracing'; +export { makeBrowserOfflineTransport } from './transports/offline.js'; +export { onProfilingStartRouteTransaction } from './profiling/hubextensions.js'; +export { BrowserProfilingIntegration } from './profiling/integration.js'; +export { GlobalHandlers } from './integrations/globalhandlers.js'; +export { TryCatch } from './integrations/trycatch.js'; +export { Breadcrumbs } from './integrations/breadcrumbs.js'; +export { LinkedErrors } from './integrations/linkederrors.js'; +export { HttpContext } from './integrations/httpcontext.js'; +export { Dedupe } from './integrations/dedupe.js'; + +let windowIntegrations = {}; + +// This block is needed to add compatibility with the integrations packages when used with a CDN +if (WINDOW.Sentry && WINDOW.Sentry.Integrations) { + windowIntegrations = WINDOW.Sentry.Integrations; +} + +const INTEGRATIONS = { + ...windowIntegrations, + ...Integrations, + ...index, +}; + +export { INTEGRATIONS as Integrations }; +//# sourceMappingURL=index.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/integrations/breadcrumbs.js b/shared/logger/node_modules/@sentry/browser/esm/integrations/breadcrumbs.js new file mode 100644 index 0000000..4117cc4 --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/integrations/breadcrumbs.js @@ -0,0 +1,320 @@ +import { getCurrentHub } from '@sentry/core'; +import { addInstrumentationHandler, getEventDescription, severityLevelFromString, safeJoin, SENTRY_XHR_DATA_KEY, parseUrl, logger, htmlTreeAsString } from '@sentry/utils'; +import { WINDOW } from '../helpers.js'; + +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ + +/** maxStringLength gets capped to prevent 100 breadcrumbs exceeding 1MB event payload size */ +const MAX_ALLOWED_STRING_LENGTH = 1024; + +const BREADCRUMB_INTEGRATION_ID = 'Breadcrumbs'; + +/** + * Default Breadcrumbs instrumentations + * TODO: Deprecated - with v6, this will be renamed to `Instrument` + */ +class Breadcrumbs { + /** + * @inheritDoc + */ + static __initStatic() {this.id = BREADCRUMB_INTEGRATION_ID;} + + /** + * @inheritDoc + */ + __init() {this.name = Breadcrumbs.id;} + + /** + * Options of the breadcrumbs integration. + */ + // This field is public, because we use it in the browser client to check if the `sentry` option is enabled. + + /** + * @inheritDoc + */ + constructor(options) {Breadcrumbs.prototype.__init.call(this); + this.options = { + console: true, + dom: true, + fetch: true, + history: true, + sentry: true, + xhr: true, + ...options, + }; + } + + /** + * Instrument browser built-ins w/ breadcrumb capturing + * - Console API + * - DOM API (click/typing) + * - XMLHttpRequest API + * - Fetch API + * - History API + */ + setupOnce() { + if (this.options.console) { + addInstrumentationHandler('console', _consoleBreadcrumb); + } + if (this.options.dom) { + addInstrumentationHandler('dom', _domBreadcrumb(this.options.dom)); + } + if (this.options.xhr) { + addInstrumentationHandler('xhr', _xhrBreadcrumb); + } + if (this.options.fetch) { + addInstrumentationHandler('fetch', _fetchBreadcrumb); + } + if (this.options.history) { + addInstrumentationHandler('history', _historyBreadcrumb); + } + } + + /** + * Adds a breadcrumb for Sentry events or transactions if this option is enabled. + */ + addSentryBreadcrumb(event) { + if (this.options.sentry) { + getCurrentHub().addBreadcrumb( + { + category: `sentry.${event.type === 'transaction' ? 'transaction' : 'event'}`, + event_id: event.event_id, + level: event.level, + message: getEventDescription(event), + }, + { + event, + }, + ); + } + } +} Breadcrumbs.__initStatic(); + +/** + * A HOC that creaes a function that creates breadcrumbs from DOM API calls. + * This is a HOC so that we get access to dom options in the closure. + */ +function _domBreadcrumb(dom) { + function _innerDomBreadcrumb(handlerData) { + let target; + let keyAttrs = typeof dom === 'object' ? dom.serializeAttribute : undefined; + + let maxStringLength = + typeof dom === 'object' && typeof dom.maxStringLength === 'number' ? dom.maxStringLength : undefined; + if (maxStringLength && maxStringLength > MAX_ALLOWED_STRING_LENGTH) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && + logger.warn( + `\`dom.maxStringLength\` cannot exceed ${MAX_ALLOWED_STRING_LENGTH}, but a value of ${maxStringLength} was configured. Sentry will use ${MAX_ALLOWED_STRING_LENGTH} instead.`, + ); + maxStringLength = MAX_ALLOWED_STRING_LENGTH; + } + + if (typeof keyAttrs === 'string') { + keyAttrs = [keyAttrs]; + } + + // Accessing event.target can throw (see getsentry/raven-js#838, #768) + try { + const event = handlerData.event ; + target = _isEvent(event) + ? htmlTreeAsString(event.target, { keyAttrs, maxStringLength }) + : htmlTreeAsString(event, { keyAttrs, maxStringLength }); + } catch (e) { + target = ''; + } + + if (target.length === 0) { + return; + } + + getCurrentHub().addBreadcrumb( + { + category: `ui.${handlerData.name}`, + message: target, + }, + { + event: handlerData.event, + name: handlerData.name, + global: handlerData.global, + }, + ); + } + + return _innerDomBreadcrumb; +} + +/** + * Creates breadcrumbs from console API calls + */ +function _consoleBreadcrumb(handlerData) { + // This is a hack to fix a Vue3-specific bug that causes an infinite loop of + // console warnings. This happens when a Vue template is rendered with + // an undeclared variable, which we try to stringify, ultimately causing + // Vue to issue another warning which repeats indefinitely. + // see: https://github.com/getsentry/sentry-javascript/pull/6010 + // see: https://github.com/getsentry/sentry-javascript/issues/5916 + for (let i = 0; i < handlerData.args.length; i++) { + if (handlerData.args[i] === 'ref=Ref<') { + handlerData.args[i + 1] = 'viewRef'; + break; + } + } + const breadcrumb = { + category: 'console', + data: { + arguments: handlerData.args, + logger: 'console', + }, + level: severityLevelFromString(handlerData.level), + message: safeJoin(handlerData.args, ' '), + }; + + if (handlerData.level === 'assert') { + if (handlerData.args[0] === false) { + breadcrumb.message = `Assertion failed: ${safeJoin(handlerData.args.slice(1), ' ') || 'console.assert'}`; + breadcrumb.data.arguments = handlerData.args.slice(1); + } else { + // Don't capture a breadcrumb for passed assertions + return; + } + } + + getCurrentHub().addBreadcrumb(breadcrumb, { + input: handlerData.args, + level: handlerData.level, + }); +} + +/** + * Creates breadcrumbs from XHR API calls + */ +function _xhrBreadcrumb(handlerData) { + const { startTimestamp, endTimestamp } = handlerData; + + const sentryXhrData = handlerData.xhr[SENTRY_XHR_DATA_KEY]; + + // We only capture complete, non-sentry requests + if (!startTimestamp || !endTimestamp || !sentryXhrData) { + return; + } + + const { method, url, status_code, body } = sentryXhrData; + + const data = { + method, + url, + status_code, + }; + + const hint = { + xhr: handlerData.xhr, + input: body, + startTimestamp, + endTimestamp, + }; + + getCurrentHub().addBreadcrumb( + { + category: 'xhr', + data, + type: 'http', + }, + hint, + ); +} + +/** + * Creates breadcrumbs from fetch API calls + */ +function _fetchBreadcrumb(handlerData) { + const { startTimestamp, endTimestamp } = handlerData; + + // We only capture complete fetch requests + if (!endTimestamp) { + return; + } + + if (handlerData.fetchData.url.match(/sentry_key/) && handlerData.fetchData.method === 'POST') { + // We will not create breadcrumbs for fetch requests that contain `sentry_key` (internal sentry requests) + return; + } + + if (handlerData.error) { + const data = handlerData.fetchData; + const hint = { + data: handlerData.error, + input: handlerData.args, + startTimestamp, + endTimestamp, + }; + + getCurrentHub().addBreadcrumb( + { + category: 'fetch', + data, + level: 'error', + type: 'http', + }, + hint, + ); + } else { + const data = { + ...handlerData.fetchData, + status_code: handlerData.response && handlerData.response.status, + }; + const hint = { + input: handlerData.args, + response: handlerData.response, + startTimestamp, + endTimestamp, + }; + getCurrentHub().addBreadcrumb( + { + category: 'fetch', + data, + type: 'http', + }, + hint, + ); + } +} + +/** + * Creates breadcrumbs from history API calls + */ +function _historyBreadcrumb(handlerData) { + let from = handlerData.from; + let to = handlerData.to; + const parsedLoc = parseUrl(WINDOW.location.href); + let parsedFrom = parseUrl(from); + const parsedTo = parseUrl(to); + + // Initial pushState doesn't provide `from` information + if (!parsedFrom.path) { + parsedFrom = parsedLoc; + } + + // Use only the path component of the URL if the URL matches the current + // document (almost all the time when using pushState) + if (parsedLoc.protocol === parsedTo.protocol && parsedLoc.host === parsedTo.host) { + to = parsedTo.relative; + } + if (parsedLoc.protocol === parsedFrom.protocol && parsedLoc.host === parsedFrom.host) { + from = parsedFrom.relative; + } + + getCurrentHub().addBreadcrumb({ + category: 'navigation', + data: { + from, + to, + }, + }); +} + +function _isEvent(event) { + return !!event && !!(event ).target; +} + +export { BREADCRUMB_INTEGRATION_ID, Breadcrumbs }; +//# sourceMappingURL=breadcrumbs.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/integrations/dedupe.js b/shared/logger/node_modules/@sentry/browser/esm/integrations/dedupe.js new file mode 100644 index 0000000..591a552 --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/integrations/dedupe.js @@ -0,0 +1,211 @@ +import { logger } from '@sentry/utils'; + +/** Deduplication filter */ +class Dedupe {constructor() { Dedupe.prototype.__init.call(this); } + /** + * @inheritDoc + */ + static __initStatic() {this.id = 'Dedupe';} + + /** + * @inheritDoc + */ + __init() {this.name = Dedupe.id;} + + /** + * @inheritDoc + */ + + /** + * @inheritDoc + */ + setupOnce(addGlobalEventProcessor, getCurrentHub) { + const eventProcessor = currentEvent => { + // We want to ignore any non-error type events, e.g. transactions or replays + // These should never be deduped, and also not be compared against as _previousEvent. + if (currentEvent.type) { + return currentEvent; + } + + const self = getCurrentHub().getIntegration(Dedupe); + if (self) { + // Juuust in case something goes wrong + try { + if (_shouldDropEvent(currentEvent, self._previousEvent)) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('Event dropped due to being a duplicate of previously captured event.'); + return null; + } + } catch (_oO) { + return (self._previousEvent = currentEvent); + } + + return (self._previousEvent = currentEvent); + } + return currentEvent; + }; + + eventProcessor.id = this.name; + addGlobalEventProcessor(eventProcessor); + } +} Dedupe.__initStatic(); + +/** JSDoc */ +function _shouldDropEvent(currentEvent, previousEvent) { + if (!previousEvent) { + return false; + } + + if (_isSameMessageEvent(currentEvent, previousEvent)) { + return true; + } + + if (_isSameExceptionEvent(currentEvent, previousEvent)) { + return true; + } + + return false; +} + +/** JSDoc */ +function _isSameMessageEvent(currentEvent, previousEvent) { + const currentMessage = currentEvent.message; + const previousMessage = previousEvent.message; + + // If neither event has a message property, they were both exceptions, so bail out + if (!currentMessage && !previousMessage) { + return false; + } + + // If only one event has a stacktrace, but not the other one, they are not the same + if ((currentMessage && !previousMessage) || (!currentMessage && previousMessage)) { + return false; + } + + if (currentMessage !== previousMessage) { + return false; + } + + if (!_isSameFingerprint(currentEvent, previousEvent)) { + return false; + } + + if (!_isSameStacktrace(currentEvent, previousEvent)) { + return false; + } + + return true; +} + +/** JSDoc */ +function _isSameExceptionEvent(currentEvent, previousEvent) { + const previousException = _getExceptionFromEvent(previousEvent); + const currentException = _getExceptionFromEvent(currentEvent); + + if (!previousException || !currentException) { + return false; + } + + if (previousException.type !== currentException.type || previousException.value !== currentException.value) { + return false; + } + + if (!_isSameFingerprint(currentEvent, previousEvent)) { + return false; + } + + if (!_isSameStacktrace(currentEvent, previousEvent)) { + return false; + } + + return true; +} + +/** JSDoc */ +function _isSameStacktrace(currentEvent, previousEvent) { + let currentFrames = _getFramesFromEvent(currentEvent); + let previousFrames = _getFramesFromEvent(previousEvent); + + // If neither event has a stacktrace, they are assumed to be the same + if (!currentFrames && !previousFrames) { + return true; + } + + // If only one event has a stacktrace, but not the other one, they are not the same + if ((currentFrames && !previousFrames) || (!currentFrames && previousFrames)) { + return false; + } + + currentFrames = currentFrames ; + previousFrames = previousFrames ; + + // If number of frames differ, they are not the same + if (previousFrames.length !== currentFrames.length) { + return false; + } + + // Otherwise, compare the two + for (let i = 0; i < previousFrames.length; i++) { + const frameA = previousFrames[i]; + const frameB = currentFrames[i]; + + if ( + frameA.filename !== frameB.filename || + frameA.lineno !== frameB.lineno || + frameA.colno !== frameB.colno || + frameA.function !== frameB.function + ) { + return false; + } + } + + return true; +} + +/** JSDoc */ +function _isSameFingerprint(currentEvent, previousEvent) { + let currentFingerprint = currentEvent.fingerprint; + let previousFingerprint = previousEvent.fingerprint; + + // If neither event has a fingerprint, they are assumed to be the same + if (!currentFingerprint && !previousFingerprint) { + return true; + } + + // If only one event has a fingerprint, but not the other one, they are not the same + if ((currentFingerprint && !previousFingerprint) || (!currentFingerprint && previousFingerprint)) { + return false; + } + + currentFingerprint = currentFingerprint ; + previousFingerprint = previousFingerprint ; + + // Otherwise, compare the two + try { + return !!(currentFingerprint.join('') === previousFingerprint.join('')); + } catch (_oO) { + return false; + } +} + +/** JSDoc */ +function _getExceptionFromEvent(event) { + return event.exception && event.exception.values && event.exception.values[0]; +} + +/** JSDoc */ +function _getFramesFromEvent(event) { + const exception = event.exception; + + if (exception) { + try { + // @ts-ignore Object could be undefined + return exception.values[0].stacktrace.frames; + } catch (_oO) { + return undefined; + } + } + return undefined; +} + +export { Dedupe }; +//# sourceMappingURL=dedupe.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/integrations/globalhandlers.js b/shared/logger/node_modules/@sentry/browser/esm/integrations/globalhandlers.js new file mode 100644 index 0000000..65c343c --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/integrations/globalhandlers.js @@ -0,0 +1,248 @@ +import { getCurrentHub } from '@sentry/core'; +import { addInstrumentationHandler, isString, isPrimitive, isErrorEvent, getLocationHref, logger, addExceptionMechanism } from '@sentry/utils'; +import { eventFromUnknownInput } from '../eventbuilder.js'; +import { shouldIgnoreOnError } from '../helpers.js'; + +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ + +/** Global handlers */ +class GlobalHandlers { + /** + * @inheritDoc + */ + static __initStatic() {this.id = 'GlobalHandlers';} + + /** + * @inheritDoc + */ + __init() {this.name = GlobalHandlers.id;} + + /** JSDoc */ + + /** + * Stores references functions to installing handlers. Will set to undefined + * after they have been run so that they are not used twice. + */ + __init2() {this._installFunc = { + onerror: _installGlobalOnErrorHandler, + onunhandledrejection: _installGlobalOnUnhandledRejectionHandler, + };} + + /** JSDoc */ + constructor(options) {GlobalHandlers.prototype.__init.call(this);GlobalHandlers.prototype.__init2.call(this); + this._options = { + onerror: true, + onunhandledrejection: true, + ...options, + }; + } + /** + * @inheritDoc + */ + setupOnce() { + Error.stackTraceLimit = 50; + const options = this._options; + + // We can disable guard-for-in as we construct the options object above + do checks against + // `this._installFunc` for the property. + // eslint-disable-next-line guard-for-in + for (const key in options) { + const installFunc = this._installFunc[key ]; + if (installFunc && options[key ]) { + globalHandlerLog(key); + installFunc(); + this._installFunc[key ] = undefined; + } + } + } +} GlobalHandlers.__initStatic(); + +/** JSDoc */ +function _installGlobalOnErrorHandler() { + addInstrumentationHandler( + 'error', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (data) => { + const [hub, stackParser, attachStacktrace] = getHubAndOptions(); + if (!hub.getIntegration(GlobalHandlers)) { + return; + } + const { msg, url, line, column, error } = data; + if (shouldIgnoreOnError() || (error && error.__sentry_own_request__)) { + return; + } + + const event = + error === undefined && isString(msg) + ? _eventFromIncompleteOnError(msg, url, line, column) + : _enhanceEventWithInitialFrame( + eventFromUnknownInput(stackParser, error || msg, undefined, attachStacktrace, false), + url, + line, + column, + ); + + event.level = 'error'; + + addMechanismAndCapture(hub, error, event, 'onerror'); + }, + ); +} + +/** JSDoc */ +function _installGlobalOnUnhandledRejectionHandler() { + addInstrumentationHandler( + 'unhandledrejection', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (e) => { + const [hub, stackParser, attachStacktrace] = getHubAndOptions(); + if (!hub.getIntegration(GlobalHandlers)) { + return; + } + let error = e; + + // dig the object of the rejection out of known event types + try { + // PromiseRejectionEvents store the object of the rejection under 'reason' + // see https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent + if ('reason' in e) { + error = e.reason; + } + // something, somewhere, (likely a browser extension) effectively casts PromiseRejectionEvents + // to CustomEvents, moving the `promise` and `reason` attributes of the PRE into + // the CustomEvent's `detail` attribute, since they're not part of CustomEvent's spec + // see https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent and + // https://github.com/getsentry/sentry-javascript/issues/2380 + else if ('detail' in e && 'reason' in e.detail) { + error = e.detail.reason; + } + } catch (_oO) { + // no-empty + } + + if (shouldIgnoreOnError() || (error && error.__sentry_own_request__)) { + return true; + } + + const event = isPrimitive(error) + ? _eventFromRejectionWithPrimitive(error) + : eventFromUnknownInput(stackParser, error, undefined, attachStacktrace, true); + + event.level = 'error'; + + addMechanismAndCapture(hub, error, event, 'onunhandledrejection'); + return; + }, + ); +} + +/** + * Create an event from a promise rejection where the `reason` is a primitive. + * + * @param reason: The `reason` property of the promise rejection + * @returns An Event object with an appropriate `exception` value + */ +function _eventFromRejectionWithPrimitive(reason) { + return { + exception: { + values: [ + { + type: 'UnhandledRejection', + // String() is needed because the Primitive type includes symbols (which can't be automatically stringified) + value: `Non-Error promise rejection captured with value: ${String(reason)}`, + }, + ], + }, + }; +} + +/** + * This function creates a stack from an old, error-less onerror handler. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function _eventFromIncompleteOnError(msg, url, line, column) { + const ERROR_TYPES_RE = + /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/i; + + // If 'message' is ErrorEvent, get real message from inside + let message = isErrorEvent(msg) ? msg.message : msg; + let name = 'Error'; + + const groups = message.match(ERROR_TYPES_RE); + if (groups) { + name = groups[1]; + message = groups[2]; + } + + const event = { + exception: { + values: [ + { + type: name, + value: message, + }, + ], + }, + }; + + return _enhanceEventWithInitialFrame(event, url, line, column); +} + +/** JSDoc */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function _enhanceEventWithInitialFrame(event, url, line, column) { + // event.exception + const e = (event.exception = event.exception || {}); + // event.exception.values + const ev = (e.values = e.values || []); + // event.exception.values[0] + const ev0 = (ev[0] = ev[0] || {}); + // event.exception.values[0].stacktrace + const ev0s = (ev0.stacktrace = ev0.stacktrace || {}); + // event.exception.values[0].stacktrace.frames + const ev0sf = (ev0s.frames = ev0s.frames || []); + + const colno = isNaN(parseInt(column, 10)) ? undefined : column; + const lineno = isNaN(parseInt(line, 10)) ? undefined : line; + const filename = isString(url) && url.length > 0 ? url : getLocationHref(); + + // event.exception.values[0].stacktrace.frames + if (ev0sf.length === 0) { + ev0sf.push({ + colno, + filename, + function: '?', + in_app: true, + lineno, + }); + } + + return event; +} + +function globalHandlerLog(type) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log(`Global Handler attached: ${type}`); +} + +function addMechanismAndCapture(hub, error, event, type) { + addExceptionMechanism(event, { + handled: false, + type, + }); + hub.captureEvent(event, { + originalException: error, + }); +} + +function getHubAndOptions() { + const hub = getCurrentHub(); + const client = hub.getClient(); + const options = (client && client.getOptions()) || { + stackParser: () => [], + attachStacktrace: false, + }; + return [hub, options.stackParser, options.attachStacktrace]; +} + +export { GlobalHandlers }; +//# sourceMappingURL=globalhandlers.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/integrations/httpcontext.js b/shared/logger/node_modules/@sentry/browser/esm/integrations/httpcontext.js new file mode 100644 index 0000000..014a182 --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/integrations/httpcontext.js @@ -0,0 +1,47 @@ +import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core'; +import { WINDOW } from '../helpers.js'; + +/** HttpContext integration collects information about HTTP request headers */ +class HttpContext {constructor() { HttpContext.prototype.__init.call(this); } + /** + * @inheritDoc + */ + static __initStatic() {this.id = 'HttpContext';} + + /** + * @inheritDoc + */ + __init() {this.name = HttpContext.id;} + + /** + * @inheritDoc + */ + setupOnce() { + addGlobalEventProcessor((event) => { + if (getCurrentHub().getIntegration(HttpContext)) { + // if none of the information we want exists, don't bother + if (!WINDOW.navigator && !WINDOW.location && !WINDOW.document) { + return event; + } + + // grab as much info as exists and add it to the event + const url = (event.request && event.request.url) || (WINDOW.location && WINDOW.location.href); + const { referrer } = WINDOW.document || {}; + const { userAgent } = WINDOW.navigator || {}; + + const headers = { + ...(event.request && event.request.headers), + ...(referrer && { Referer: referrer }), + ...(userAgent && { 'User-Agent': userAgent }), + }; + const request = { ...event.request, ...(url && { url }), headers }; + + return { ...event, request }; + } + return event; + }); + } +} HttpContext.__initStatic(); + +export { HttpContext }; +//# sourceMappingURL=httpcontext.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/integrations/linkederrors.js b/shared/logger/node_modules/@sentry/browser/esm/integrations/linkederrors.js new file mode 100644 index 0000000..d44a5e9 --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/integrations/linkederrors.js @@ -0,0 +1,87 @@ +import { getCurrentHub, addGlobalEventProcessor } from '@sentry/core'; +import { isInstanceOf } from '@sentry/utils'; +import { exceptionFromError } from '../eventbuilder.js'; + +const DEFAULT_KEY = 'cause'; +const DEFAULT_LIMIT = 5; + +/** Adds SDK info to an event. */ +class LinkedErrors { + /** + * @inheritDoc + */ + static __initStatic() {this.id = 'LinkedErrors';} + + /** + * @inheritDoc + */ + __init() {this.name = LinkedErrors.id;} + + /** + * @inheritDoc + */ + + /** + * @inheritDoc + */ + + /** + * @inheritDoc + */ + constructor(options = {}) {LinkedErrors.prototype.__init.call(this); + this._key = options.key || DEFAULT_KEY; + this._limit = options.limit || DEFAULT_LIMIT; + } + + /** + * @inheritDoc + */ + setupOnce() { + const client = getCurrentHub().getClient(); + if (!client) { + return; + } + addGlobalEventProcessor((event, hint) => { + const self = getCurrentHub().getIntegration(LinkedErrors); + return self ? _handler(client.getOptions().stackParser, self._key, self._limit, event, hint) : event; + }); + } +} LinkedErrors.__initStatic(); + +/** + * @inheritDoc + */ +function _handler( + parser, + key, + limit, + event, + hint, +) { + if (!event.exception || !event.exception.values || !hint || !isInstanceOf(hint.originalException, Error)) { + return event; + } + const linkedErrors = _walkErrorTree(parser, limit, hint.originalException , key); + event.exception.values = [...linkedErrors, ...event.exception.values]; + return event; +} + +/** + * JSDOC + */ +function _walkErrorTree( + parser, + limit, + error, + key, + stack = [], +) { + if (!isInstanceOf(error[key], Error) || stack.length + 1 >= limit) { + return stack; + } + const exception = exceptionFromError(parser, error[key]); + return _walkErrorTree(parser, limit, error[key], key, [exception, ...stack]); +} + +export { LinkedErrors, _handler, _walkErrorTree }; +//# sourceMappingURL=linkederrors.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/integrations/trycatch.js b/shared/logger/node_modules/@sentry/browser/esm/integrations/trycatch.js new file mode 100644 index 0000000..69e112f --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/integrations/trycatch.js @@ -0,0 +1,281 @@ +import { fill, getFunctionName, getOriginalFunction } from '@sentry/utils'; +import { WINDOW, wrap } from '../helpers.js'; + +const DEFAULT_EVENT_TARGET = [ + 'EventTarget', + 'Window', + 'Node', + 'ApplicationCache', + 'AudioTrackList', + 'ChannelMergerNode', + 'CryptoOperation', + 'EventSource', + 'FileReader', + 'HTMLUnknownElement', + 'IDBDatabase', + 'IDBRequest', + 'IDBTransaction', + 'KeyOperation', + 'MediaController', + 'MessagePort', + 'ModalWindow', + 'Notification', + 'SVGElementInstance', + 'Screen', + 'TextTrack', + 'TextTrackCue', + 'TextTrackList', + 'WebSocket', + 'WebSocketWorker', + 'Worker', + 'XMLHttpRequest', + 'XMLHttpRequestEventTarget', + 'XMLHttpRequestUpload', +]; + +/** Wrap timer functions and event targets to catch errors and provide better meta data */ +class TryCatch { + /** + * @inheritDoc + */ + static __initStatic() {this.id = 'TryCatch';} + + /** + * @inheritDoc + */ + __init() {this.name = TryCatch.id;} + + /** JSDoc */ + + /** + * @inheritDoc + */ + constructor(options) {TryCatch.prototype.__init.call(this); + this._options = { + XMLHttpRequest: true, + eventTarget: true, + requestAnimationFrame: true, + setInterval: true, + setTimeout: true, + ...options, + }; + } + + /** + * Wrap timer functions and event targets to catch errors + * and provide better metadata. + */ + setupOnce() { + if (this._options.setTimeout) { + fill(WINDOW, 'setTimeout', _wrapTimeFunction); + } + + if (this._options.setInterval) { + fill(WINDOW, 'setInterval', _wrapTimeFunction); + } + + if (this._options.requestAnimationFrame) { + fill(WINDOW, 'requestAnimationFrame', _wrapRAF); + } + + if (this._options.XMLHttpRequest && 'XMLHttpRequest' in WINDOW) { + fill(XMLHttpRequest.prototype, 'send', _wrapXHR); + } + + const eventTargetOption = this._options.eventTarget; + if (eventTargetOption) { + const eventTarget = Array.isArray(eventTargetOption) ? eventTargetOption : DEFAULT_EVENT_TARGET; + eventTarget.forEach(_wrapEventTarget); + } + } +} TryCatch.__initStatic(); + +/** JSDoc */ +function _wrapTimeFunction(original) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function ( ...args) { + const originalCallback = args[0]; + args[0] = wrap(originalCallback, { + mechanism: { + data: { function: getFunctionName(original) }, + handled: true, + type: 'instrument', + }, + }); + return original.apply(this, args); + }; +} + +/** JSDoc */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function _wrapRAF(original) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function ( callback) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + return original.apply(this, [ + wrap(callback, { + mechanism: { + data: { + function: 'requestAnimationFrame', + handler: getFunctionName(original), + }, + handled: true, + type: 'instrument', + }, + }), + ]); + }; +} + +/** JSDoc */ +function _wrapXHR(originalSend) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function ( ...args) { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const xhr = this; + const xmlHttpRequestProps = ['onload', 'onerror', 'onprogress', 'onreadystatechange']; + + xmlHttpRequestProps.forEach(prop => { + if (prop in xhr && typeof xhr[prop] === 'function') { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + fill(xhr, prop, function (original) { + const wrapOptions = { + mechanism: { + data: { + function: prop, + handler: getFunctionName(original), + }, + handled: true, + type: 'instrument', + }, + }; + + // If Instrument integration has been called before TryCatch, get the name of original function + const originalFunction = getOriginalFunction(original); + if (originalFunction) { + wrapOptions.mechanism.data.handler = getFunctionName(originalFunction); + } + + // Otherwise wrap directly + return wrap(original, wrapOptions); + }); + } + }); + + return originalSend.apply(this, args); + }; +} + +/** JSDoc */ +function _wrapEventTarget(target) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const globalObject = WINDOW ; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const proto = globalObject[target] && globalObject[target].prototype; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-prototype-builtins + if (!proto || !proto.hasOwnProperty || !proto.hasOwnProperty('addEventListener')) { + return; + } + + fill(proto, 'addEventListener', function (original) + + { + return function ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + + eventName, + fn, + options, + ) { + try { + if (typeof fn.handleEvent === 'function') { + // ESlint disable explanation: + // First, it is generally safe to call `wrap` with an unbound function. Furthermore, using `.bind()` would + // introduce a bug here, because bind returns a new function that doesn't have our + // flags(like __sentry_original__) attached. `wrap` checks for those flags to avoid unnecessary wrapping. + // Without those flags, every call to addEventListener wraps the function again, causing a memory leak. + // eslint-disable-next-line @typescript-eslint/unbound-method + fn.handleEvent = wrap(fn.handleEvent, { + mechanism: { + data: { + function: 'handleEvent', + handler: getFunctionName(fn), + target, + }, + handled: true, + type: 'instrument', + }, + }); + } + } catch (err) { + // can sometimes get 'Permission denied to access property "handle Event' + } + + return original.apply(this, [ + eventName, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + wrap(fn , { + mechanism: { + data: { + function: 'addEventListener', + handler: getFunctionName(fn), + target, + }, + handled: true, + type: 'instrument', + }, + }), + options, + ]); + }; + }); + + fill( + proto, + 'removeEventListener', + function ( + originalRemoveEventListener, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ) { + return function ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + + eventName, + fn, + options, + ) { + /** + * There are 2 possible scenarios here: + * + * 1. Someone passes a callback, which was attached prior to Sentry initialization, or by using unmodified + * method, eg. `document.addEventListener.call(el, name, handler). In this case, we treat this function + * as a pass-through, and call original `removeEventListener` with it. + * + * 2. Someone passes a callback, which was attached after Sentry was initialized, which means that it was using + * our wrapped version of `addEventListener`, which internally calls `wrap` helper. + * This helper "wraps" whole callback inside a try/catch statement, and attached appropriate metadata to it, + * in order for us to make a distinction between wrapped/non-wrapped functions possible. + * If a function was wrapped, it has additional property of `__sentry_wrapped__`, holding the handler. + * + * When someone adds a handler prior to initialization, and then do it again, but after, + * then we have to detach both of them. Otherwise, if we'd detach only wrapped one, it'd be impossible + * to get rid of the initial handler and it'd stick there forever. + */ + const wrappedEventHandler = fn ; + try { + const originalEventHandler = wrappedEventHandler && wrappedEventHandler.__sentry_wrapped__; + if (originalEventHandler) { + originalRemoveEventListener.call(this, eventName, originalEventHandler, options); + } + } catch (e) { + // ignore, accessing __sentry_wrapped__ will throw in some Selenium environments + } + return originalRemoveEventListener.call(this, eventName, wrappedEventHandler, options); + }; + }, + ); +} + +export { TryCatch }; +//# sourceMappingURL=trycatch.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/profiling/hubextensions.js b/shared/logger/node_modules/@sentry/browser/esm/profiling/hubextensions.js new file mode 100644 index 0000000..c1cd92d --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/profiling/hubextensions.js @@ -0,0 +1,240 @@ +import { getCurrentHub } from '@sentry/core'; +import { logger, uuid4 } from '@sentry/utils'; +import { WINDOW } from '../helpers.js'; +import { isValidSampleRate, addProfileToMap } from './utils.js'; + +/* eslint-disable complexity */ + +const MAX_PROFILE_DURATION_MS = 30000; +// Keep a flag value to avoid re-initializing the profiler constructor. If it fails +// once, it will always fail and this allows us to early return. +let PROFILING_CONSTRUCTOR_FAILED = false; + +/** + * Check if profiler constructor is available. + * @param maybeProfiler + */ +function isJSProfilerSupported(maybeProfiler) { + return typeof maybeProfiler === 'function'; +} + +/** + * Safety wrapper for startTransaction for the unlikely case that transaction starts before tracing is imported - + * if that happens we want to avoid throwing an error from profiling code. + * see https://github.com/getsentry/sentry-javascript/issues/4731. + * + * @experimental + */ +function onProfilingStartRouteTransaction(transaction) { + if (!transaction) { + if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) { + logger.log('[Profiling] Transaction is undefined, skipping profiling'); + } + return transaction; + } + + return wrapTransactionWithProfiling(transaction); +} + +/** + * Wraps startTransaction and stopTransaction with profiling related logic. + * startProfiling is called after the call to startTransaction in order to avoid our own code from + * being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction. + */ +function wrapTransactionWithProfiling(transaction) { + // Feature support check first + const JSProfilerConstructor = WINDOW.Profiler; + + if (!isJSProfilerSupported(JSProfilerConstructor)) { + if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) { + logger.log( + '[Profiling] Profiling is not supported by this browser, Profiler interface missing on window object.', + ); + } + return transaction; + } + + // If constructor failed once, it will always fail, so we can early return. + if (PROFILING_CONSTRUCTOR_FAILED) { + if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) { + logger.log('[Profiling] Profiling has been disabled for the duration of the current user session.'); + } + return transaction; + } + + const client = getCurrentHub().getClient(); + const options = client && client.getOptions(); + if (!options) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Profiling] Profiling disabled, no options found.'); + return transaction; + } + + // @ts-ignore profilesSampleRate is not part of the browser options yet + const profilesSampleRate = options.profilesSampleRate; + + // Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The + // only valid values are booleans or numbers between 0 and 1.) + if (!isValidSampleRate(profilesSampleRate)) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('[Profiling] Discarding profile because of invalid sample rate.'); + return transaction; + } + + // if the function returned 0 (or false), or if `profileSampleRate` is 0, it's a sign the profile should be dropped + if (!profilesSampleRate) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && + logger.log( + '[Profiling] Discarding profile because a negative sampling decision was inherited or profileSampleRate is set to 0', + ); + return transaction; + } + + // Now we roll the dice. Math.random is inclusive of 0, but not of 1, so strict < is safe here. In case sampleRate is + // a boolean, the < comparison will cause it to be automatically cast to 1 if it's true and 0 if it's false. + const sampled = profilesSampleRate === true ? true : Math.random() < profilesSampleRate; + // Check if we should sample this profile + if (!sampled) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && + logger.log( + `[Profiling] Discarding profile because it's not included in the random sample (sampling rate = ${Number( + profilesSampleRate, + )})`, + ); + return transaction; + } + + // From initial testing, it seems that the minimum value for sampleInterval is 10ms. + const samplingIntervalMS = 10; + // Start the profiler + const maxSamples = Math.floor(MAX_PROFILE_DURATION_MS / samplingIntervalMS); + let profiler; + + // Attempt to initialize the profiler constructor, if it fails, we disable profiling for the current user session. + // This is likely due to a missing 'Document-Policy': 'js-profiling' header. We do not want to throw an error if this happens + // as we risk breaking the user's application, so just disable profiling and log an error. + try { + profiler = new JSProfilerConstructor({ sampleInterval: samplingIntervalMS, maxBufferSize: maxSamples }); + } catch (e) { + if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) { + logger.log( + "[Profiling] Failed to initialize the Profiling constructor, this is likely due to a missing 'Document-Policy': 'js-profiling' header.", + ); + logger.log('[Profiling] Disabling profiling for current user session.'); + } + PROFILING_CONSTRUCTOR_FAILED = true; + } + + // We failed to construct the profiler, fallback to original transaction - there is no need to log + // anything as we already did that in the try/catch block. + if (!profiler) { + return transaction; + } + + if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) { + logger.log(`[Profiling] started profiling transaction: ${transaction.name || transaction.description}`); + } + + // We create "unique" transaction names to avoid concurrent transactions with same names + // from being ignored by the profiler. From here on, only this transaction name should be used when + // calling the profiler methods. Note: we log the original name to the user to avoid confusion. + const profileId = uuid4(); + + /** + * Idempotent handler for profile stop + */ + async function onProfileHandler() { + // Check if the profile exists and return it the behavior has to be idempotent as users may call transaction.finish multiple times. + if (!transaction) { + return null; + } + // Satisfy the type checker, but profiler will always be defined here. + if (!profiler) { + return null; + } + + // This is temporary - we will use the collected span data to evaluate + // if deferring txn.finish until profiler resolves is a viable approach. + const stopProfilerSpan = transaction.startChild({ description: 'profiler.stop', op: 'profiler' }); + + return profiler + .stop() + .then((p) => { + stopProfilerSpan.finish(); + + if (maxDurationTimeoutID) { + WINDOW.clearTimeout(maxDurationTimeoutID); + maxDurationTimeoutID = undefined; + } + + if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) { + logger.log(`[Profiling] stopped profiling of transaction: ${transaction.name || transaction.description}`); + } + + // In case of an overlapping transaction, stopProfiling may return null and silently ignore the overlapping profile. + if (!p) { + if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) { + logger.log( + `[Profiling] profiler returned null profile for: ${transaction.name || transaction.description}`, + 'this may indicate an overlapping transaction or a call to stopProfiling with a profile title that was never started', + ); + } + return null; + } + + addProfileToMap(profileId, p); + return null; + }) + .catch(error => { + stopProfilerSpan.finish(); + if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) { + logger.log('[Profiling] error while stopping profiler:', error); + } + return null; + }); + } + + // Enqueue a timeout to prevent profiles from running over max duration. + let maxDurationTimeoutID = WINDOW.setTimeout(() => { + if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) { + logger.log( + '[Profiling] max profile duration elapsed, stopping profiling for:', + transaction.name || transaction.description, + ); + } + // If the timeout exceeds, we want to stop profiling, but not finish the transaction + void onProfileHandler(); + }, MAX_PROFILE_DURATION_MS); + + // We need to reference the original finish call to avoid creating an infinite loop + const originalFinish = transaction.finish.bind(transaction); + + /** + * Wraps startTransaction and stopTransaction with profiling related logic. + * startProfiling is called after the call to startTransaction in order to avoid our own code from + * being profiled. Because of that same reason, stopProfiling is called before the call to stopTransaction. + */ + function profilingWrappedTransactionFinish() { + if (!transaction) { + return originalFinish(); + } + // onProfileHandler should always return the same profile even if this is called multiple times. + // Always call onProfileHandler to ensure stopProfiling is called and the timeout is cleared. + void onProfileHandler().then( + () => { + transaction.setContext('profile', { profile_id: profileId }); + originalFinish(); + }, + () => { + // If onProfileHandler fails, we still want to call the original finish method. + originalFinish(); + }, + ); + + return transaction; + } + + transaction.finish = profilingWrappedTransactionFinish; + return transaction; +} + +export { MAX_PROFILE_DURATION_MS, onProfilingStartRouteTransaction, wrapTransactionWithProfiling }; +//# sourceMappingURL=hubextensions.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/profiling/integration.js b/shared/logger/node_modules/@sentry/browser/esm/profiling/integration.js new file mode 100644 index 0000000..f09e4ed --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/profiling/integration.js @@ -0,0 +1,81 @@ +import { logger } from '@sentry/utils'; +import { wrapTransactionWithProfiling } from './hubextensions.js'; +import { PROFILE_MAP, findProfiledTransactionsFromEnvelope, createProfilingEvent, addProfilesToEnvelope } from './utils.js'; + +/** + * Browser profiling integration. Stores any event that has contexts["profile"]["profile_id"] + * This exists because we do not want to await async profiler.stop calls as transaction.finish is called + * in a synchronous context. Instead, we handle sending the profile async from the promise callback and + * rely on being able to pull the event from the cache when we need to construct the envelope. This makes the + * integration less reliable as we might be dropping profiles when the cache is full. + * + * @experimental + */ +class BrowserProfilingIntegration {constructor() { BrowserProfilingIntegration.prototype.__init.call(this);BrowserProfilingIntegration.prototype.__init2.call(this); } + __init() {this.name = 'BrowserProfilingIntegration';} + __init2() {this.getCurrentHub = undefined;} + + /** + * @inheritDoc + */ + setupOnce(addGlobalEventProcessor, getCurrentHub) { + this.getCurrentHub = getCurrentHub; + const client = this.getCurrentHub().getClient() ; + + if (client && typeof client.on === 'function') { + client.on('startTransaction', (transaction) => { + wrapTransactionWithProfiling(transaction); + }); + + client.on('beforeEnvelope', (envelope) => { + // if not profiles are in queue, there is nothing to add to the envelope. + if (!PROFILE_MAP['size']) { + return; + } + + const profiledTransactionEvents = findProfiledTransactionsFromEnvelope(envelope); + if (!profiledTransactionEvents.length) { + return; + } + + const profilesToAddToEnvelope = []; + + for (const profiledTransaction of profiledTransactionEvents) { + const context = profiledTransaction && profiledTransaction.contexts; + const profile_id = context && context['profile'] && (context['profile']['profile_id'] ); + + if (!profile_id) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && + logger.log('[Profiling] cannot find profile for a transaction without a profile context'); + continue; + } + + // Remove the profile from the transaction context before sending, relay will take care of the rest. + if (context && context['profile']) { + delete context.profile; + } + + const profile = PROFILE_MAP.get(profile_id); + if (!profile) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`); + continue; + } + + PROFILE_MAP.delete(profile_id); + const profileEvent = createProfilingEvent(profile_id, profile, profiledTransaction ); + + if (profileEvent) { + profilesToAddToEnvelope.push(profileEvent); + } + } + + addProfilesToEnvelope(envelope, profilesToAddToEnvelope); + }); + } else { + logger.warn('[Profiling] Client does not support hooks, profiling will be disabled'); + } + } +} + +export { BrowserProfilingIntegration }; +//# sourceMappingURL=integration.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/profiling/utils.js b/shared/logger/node_modules/@sentry/browser/esm/profiling/utils.js new file mode 100644 index 0000000..e8beae2 --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/profiling/utils.js @@ -0,0 +1,438 @@ +import { DEFAULT_ENVIRONMENT, getCurrentHub } from '@sentry/core'; +import { forEachEnvelopeItem, logger, uuid4, GLOBAL_OBJ } from '@sentry/utils'; +import { WINDOW } from '../helpers.js'; + +/* eslint-disable max-lines */ + +const MS_TO_NS = 1e6; +// Use 0 as main thread id which is identical to threadId in node:worker_threads +// where main logs 0 and workers seem to log in increments of 1 +const THREAD_ID_STRING = String(0); +const THREAD_NAME = 'main'; + +// Machine properties (eval only once) +let OS_PLATFORM = ''; +let OS_PLATFORM_VERSION = ''; +let OS_ARCH = ''; +let OS_BROWSER = (WINDOW.navigator && WINDOW.navigator.userAgent) || ''; +let OS_MODEL = ''; +const OS_LOCALE = + (WINDOW.navigator && WINDOW.navigator.language) || + (WINDOW.navigator && WINDOW.navigator.languages && WINDOW.navigator.languages[0]) || + ''; + +function isUserAgentData(data) { + return typeof data === 'object' && data !== null && 'getHighEntropyValues' in data; +} + +// @ts-ignore userAgentData is not part of the navigator interface yet +const userAgentData = WINDOW.navigator && WINDOW.navigator.userAgentData; + +if (isUserAgentData(userAgentData)) { + userAgentData + .getHighEntropyValues(['architecture', 'model', 'platform', 'platformVersion', 'fullVersionList']) + .then((ua) => { + OS_PLATFORM = ua.platform || ''; + OS_ARCH = ua.architecture || ''; + OS_MODEL = ua.model || ''; + OS_PLATFORM_VERSION = ua.platformVersion || ''; + + if (ua.fullVersionList && ua.fullVersionList.length > 0) { + const firstUa = ua.fullVersionList[ua.fullVersionList.length - 1]; + OS_BROWSER = `${firstUa.brand} ${firstUa.version}`; + } + }) + .catch(e => void e); +} + +function isProcessedJSSelfProfile(profile) { + return !('thread_metadata' in profile); +} + +// Enriches the profile with threadId of the current thread. +// This is done in node as we seem to not be able to get the info from C native code. +/** + * + */ +function enrichWithThreadInformation(profile) { + if (!isProcessedJSSelfProfile(profile)) { + return profile; + } + + return convertJSSelfProfileToSampledFormat(profile); +} + +// Profile is marked as optional because it is deleted from the metadata +// by the integration before the event is processed by other integrations. + +function getTraceId(event) { + const traceId = event && event.contexts && event.contexts['trace'] && event.contexts['trace']['trace_id']; + // Log a warning if the profile has an invalid traceId (should be uuidv4). + // All profiles and transactions are rejected if this is the case and we want to + // warn users that this is happening if they enable debug flag + if (typeof traceId === 'string' && traceId.length !== 32) { + if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) { + logger.log(`[Profiling] Invalid traceId: ${traceId} on profiled event`); + } + } + if (typeof traceId !== 'string') { + return ''; + } + + return traceId; +} +/** + * Creates a profiling event envelope from a Sentry event. If profile does not pass + * validation, returns null. + * @param event + * @param dsn + * @param metadata + * @param tunnel + * @returns {EventEnvelope | null} + */ + +/** + * Creates a profiling event envelope from a Sentry event. + */ +function createProfilePayload( + event, + processedProfile, + profile_id, +) { + if (event.type !== 'transaction') { + // createProfilingEventEnvelope should only be called for transactions, + // we type guard this behavior with isProfiledTransactionEvent. + throw new TypeError('Profiling events may only be attached to transactions, this should never occur.'); + } + + if (processedProfile === undefined || processedProfile === null) { + throw new TypeError( + `Cannot construct profiling event envelope without a valid profile. Got ${processedProfile} instead.`, + ); + } + + const traceId = getTraceId(event); + const enrichedThreadProfile = enrichWithThreadInformation(processedProfile); + const transactionStartMs = typeof event.start_timestamp === 'number' ? event.start_timestamp * 1000 : Date.now(); + const transactionEndMs = typeof event.timestamp === 'number' ? event.timestamp * 1000 : Date.now(); + + const profile = { + event_id: profile_id, + timestamp: new Date(transactionStartMs).toISOString(), + platform: 'javascript', + version: '1', + release: event.release || '', + environment: event.environment || DEFAULT_ENVIRONMENT, + runtime: { + name: 'javascript', + version: WINDOW.navigator.userAgent, + }, + os: { + name: OS_PLATFORM, + version: OS_PLATFORM_VERSION, + build_number: OS_BROWSER, + }, + device: { + locale: OS_LOCALE, + model: OS_MODEL, + manufacturer: OS_BROWSER, + architecture: OS_ARCH, + is_emulator: false, + }, + debug_meta: { + images: applyDebugMetadata(processedProfile.resources), + }, + profile: enrichedThreadProfile, + transactions: [ + { + name: event.transaction || '', + id: event.event_id || uuid4(), + trace_id: traceId, + active_thread_id: THREAD_ID_STRING, + relative_start_ns: '0', + relative_end_ns: ((transactionEndMs - transactionStartMs) * 1e6).toFixed(0), + }, + ], + }; + + return profile; +} + +/** + * Converts a JSSelfProfile to a our sampled format. + * Does not currently perform stack indexing. + */ +function convertJSSelfProfileToSampledFormat(input) { + let EMPTY_STACK_ID = undefined; + let STACK_ID = 0; + + // Initialize the profile that we will fill with data + const profile = { + samples: [], + stacks: [], + frames: [], + thread_metadata: { + [THREAD_ID_STRING]: { name: THREAD_NAME }, + }, + }; + + if (!input.samples.length) { + return profile; + } + + // We assert samples.length > 0 above and timestamp should always be present + const start = input.samples[0].timestamp; + + for (let i = 0; i < input.samples.length; i++) { + const jsSample = input.samples[i]; + + // If sample has no stack, add an empty sample + if (jsSample.stackId === undefined) { + if (EMPTY_STACK_ID === undefined) { + EMPTY_STACK_ID = STACK_ID; + profile.stacks[EMPTY_STACK_ID] = []; + STACK_ID++; + } + + profile['samples'][i] = { + // convert ms timestamp to ns + elapsed_since_start_ns: ((jsSample.timestamp - start) * MS_TO_NS).toFixed(0), + stack_id: EMPTY_STACK_ID, + thread_id: THREAD_ID_STRING, + }; + continue; + } + + let stackTop = input.stacks[jsSample.stackId]; + + // Functions in top->down order (root is last) + // We follow the stackTop.parentId trail and collect each visited frameId + const stack = []; + + while (stackTop) { + stack.push(stackTop.frameId); + + const frame = input.frames[stackTop.frameId]; + + // If our frame has not been indexed yet, index it + if (profile.frames[stackTop.frameId] === undefined) { + profile.frames[stackTop.frameId] = { + function: frame.name, + file: frame.resourceId ? input.resources[frame.resourceId] : undefined, + line: frame.line, + column: frame.column, + }; + } + + stackTop = stackTop.parentId === undefined ? undefined : input.stacks[stackTop.parentId]; + } + + const sample = { + // convert ms timestamp to ns + elapsed_since_start_ns: ((jsSample.timestamp - start) * MS_TO_NS).toFixed(0), + stack_id: STACK_ID, + thread_id: THREAD_ID_STRING, + }; + + profile['stacks'][STACK_ID] = stack; + profile['samples'][i] = sample; + STACK_ID++; + } + + return profile; +} + +/** + * Adds items to envelope if they are not already present - mutates the envelope. + * @param envelope + */ +function addProfilesToEnvelope(envelope, profiles) { + if (!profiles.length) { + return envelope; + } + + for (const profile of profiles) { + // @ts-ignore untyped envelope + envelope[1].push([{ type: 'profile' }, profile]); + } + return envelope; +} + +/** + * Finds transactions with profile_id context in the envelope + * @param envelope + * @returns + */ +function findProfiledTransactionsFromEnvelope(envelope) { + const events = []; + + forEachEnvelopeItem(envelope, (item, type) => { + if (type !== 'transaction') { + return; + } + + for (let j = 1; j < item.length; j++) { + const event = item[j] ; + + if (event && event.contexts && event.contexts['profile'] && event.contexts['profile']['profile_id']) { + events.push(item[j] ); + } + } + }); + + return events; +} + +const debugIdStackParserCache = new WeakMap(); +/** + * Applies debug meta data to an event from a list of paths to resources (sourcemaps) + */ +function applyDebugMetadata(resource_paths) { + const debugIdMap = GLOBAL_OBJ._sentryDebugIds; + + if (!debugIdMap) { + return []; + } + + const hub = getCurrentHub(); + if (!hub) { + return []; + } + const client = hub.getClient(); + if (!client) { + return []; + } + const options = client.getOptions(); + if (!options) { + return []; + } + const stackParser = options.stackParser; + if (!stackParser) { + return []; + } + + let debugIdStackFramesCache; + const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser); + if (cachedDebugIdStackFrameCache) { + debugIdStackFramesCache = cachedDebugIdStackFrameCache; + } else { + debugIdStackFramesCache = new Map(); + debugIdStackParserCache.set(stackParser, debugIdStackFramesCache); + } + + // Build a map of filename -> debug_id + const filenameDebugIdMap = Object.keys(debugIdMap).reduce((acc, debugIdStackTrace) => { + let parsedStack; + + const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace); + if (cachedParsedStack) { + parsedStack = cachedParsedStack; + } else { + parsedStack = stackParser(debugIdStackTrace); + debugIdStackFramesCache.set(debugIdStackTrace, parsedStack); + } + + for (let i = parsedStack.length - 1; i >= 0; i--) { + const stackFrame = parsedStack[i]; + const file = stackFrame && stackFrame.filename; + + if (stackFrame && file) { + acc[file] = debugIdMap[debugIdStackTrace] ; + break; + } + } + return acc; + }, {}); + + const images = []; + for (const path of resource_paths) { + if (path && filenameDebugIdMap[path]) { + images.push({ + type: 'sourcemap', + code_file: path, + debug_id: filenameDebugIdMap[path] , + }); + } + } + + return images; +} + +/** + * Checks the given sample rate to make sure it is valid type and value (a boolean, or a number between 0 and 1). + */ +function isValidSampleRate(rate) { + // we need to check NaN explicitly because it's of type 'number' and therefore wouldn't get caught by this typecheck + if ((typeof rate !== 'number' && typeof rate !== 'boolean') || (typeof rate === 'number' && isNaN(rate))) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && + logger.warn( + `[Profiling] Invalid sample rate. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify( + rate, + )} of type ${JSON.stringify(typeof rate)}.`, + ); + return false; + } + + // Boolean sample rates are always valid + if (rate === true || rate === false) { + return true; + } + + // in case sampleRate is a boolean, it will get automatically cast to 1 if it's true and 0 if it's false + if (rate < 0 || rate > 1) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && + logger.warn(`[Profiling] Invalid sample rate. Sample rate must be between 0 and 1. Got ${rate}.`); + return false; + } + return true; +} + +function isValidProfile(profile) { + if (profile.samples.length < 2) { + if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) { + // Log a warning if the profile has less than 2 samples so users can know why + // they are not seeing any profiling data and we cant avoid the back and forth + // of asking them to provide us with a dump of the profile data. + logger.log('[Profiling] Discarding profile because it contains less than 2 samples'); + } + return false; + } + + if (!profile.frames.length) { + if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) { + logger.log('[Profiling] Discarding profile because it contains no frames'); + } + return false; + } + + return true; +} + +/** + * Creates a profiling envelope item, if the profile does not pass validation, returns null. + * @param event + * @returns {Profile | null} + */ +function createProfilingEvent(profile_id, profile, event) { + if (!isValidProfile(profile)) { + return null; + } + + return createProfilePayload(event, profile, profile_id); +} + +const PROFILE_MAP = new Map(); +/** + * + */ +function addProfileToMap(profile_id, profile) { + PROFILE_MAP.set(profile_id, profile); + + if (PROFILE_MAP.size > 30) { + const last = PROFILE_MAP.keys().next().value; + PROFILE_MAP.delete(last); + } +} + +export { PROFILE_MAP, addProfileToMap, addProfilesToEnvelope, applyDebugMetadata, convertJSSelfProfileToSampledFormat, createProfilePayload, createProfilingEvent, enrichWithThreadInformation, findProfiledTransactionsFromEnvelope, isValidSampleRate }; +//# sourceMappingURL=utils.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/sdk.js b/shared/logger/node_modules/@sentry/browser/esm/sdk.js new file mode 100644 index 0000000..2b4edf2 --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/sdk.js @@ -0,0 +1,293 @@ +import { Integrations, getIntegrationsToSetup, initAndBind, getReportDialogEndpoint, getCurrentHub } from '@sentry/core'; +import { stackParserFromStackParserOptions, supportsFetch, logger, resolvedSyncPromise, addInstrumentationHandler } from '@sentry/utils'; +import { BrowserClient } from './client.js'; +import { WINDOW, wrap as wrap$1 } from './helpers.js'; +import { GlobalHandlers } from './integrations/globalhandlers.js'; +import { TryCatch } from './integrations/trycatch.js'; +import { Breadcrumbs } from './integrations/breadcrumbs.js'; +import { LinkedErrors } from './integrations/linkederrors.js'; +import { HttpContext } from './integrations/httpcontext.js'; +import { Dedupe } from './integrations/dedupe.js'; +import { defaultStackParser } from './stack-parsers.js'; +import { makeFetchTransport } from './transports/fetch.js'; +import { makeXHRTransport } from './transports/xhr.js'; + +const defaultIntegrations = [ + new Integrations.InboundFilters(), + new Integrations.FunctionToString(), + new TryCatch(), + new Breadcrumbs(), + new GlobalHandlers(), + new LinkedErrors(), + new Dedupe(), + new HttpContext(), +]; + +/** + * A magic string that build tooling can leverage in order to inject a release value into the SDK. + */ + +/** + * The Sentry Browser SDK Client. + * + * To use this SDK, call the {@link init} function as early as possible when + * loading the web page. To set context information or send manual events, use + * the provided methods. + * + * @example + * + * ``` + * + * import { init } from '@sentry/browser'; + * + * init({ + * dsn: '__DSN__', + * // ... + * }); + * ``` + * + * @example + * ``` + * + * import { configureScope } from '@sentry/browser'; + * configureScope((scope: Scope) => { + * scope.setExtra({ battery: 0.7 }); + * scope.setTag({ user_mode: 'admin' }); + * scope.setUser({ id: '4711' }); + * }); + * ``` + * + * @example + * ``` + * + * import { addBreadcrumb } from '@sentry/browser'; + * addBreadcrumb({ + * message: 'My Breadcrumb', + * // ... + * }); + * ``` + * + * @example + * + * ``` + * + * import * as Sentry from '@sentry/browser'; + * Sentry.captureMessage('Hello, world!'); + * Sentry.captureException(new Error('Good bye')); + * Sentry.captureEvent({ + * message: 'Manual', + * stacktrace: [ + * // ... + * ], + * }); + * ``` + * + * @see {@link BrowserOptions} for documentation on configuration options. + */ +function init(options = {}) { + if (options.defaultIntegrations === undefined) { + options.defaultIntegrations = defaultIntegrations; + } + if (options.release === undefined) { + // This allows build tooling to find-and-replace __SENTRY_RELEASE__ to inject a release value + if (typeof __SENTRY_RELEASE__ === 'string') { + options.release = __SENTRY_RELEASE__; + } + + // This supports the variable that sentry-webpack-plugin injects + if (WINDOW.SENTRY_RELEASE && WINDOW.SENTRY_RELEASE.id) { + options.release = WINDOW.SENTRY_RELEASE.id; + } + } + if (options.autoSessionTracking === undefined) { + options.autoSessionTracking = true; + } + if (options.sendClientReports === undefined) { + options.sendClientReports = true; + } + + const clientOptions = { + ...options, + stackParser: stackParserFromStackParserOptions(options.stackParser || defaultStackParser), + integrations: getIntegrationsToSetup(options), + transport: options.transport || (supportsFetch() ? makeFetchTransport : makeXHRTransport), + }; + + initAndBind(BrowserClient, clientOptions); + + if (options.autoSessionTracking) { + startSessionTracking(); + } +} + +/** + * Present the user with a report dialog. + * + * @param options Everything is optional, we try to fetch all info need from the global scope. + */ +function showReportDialog(options = {}, hub = getCurrentHub()) { + // doesn't work without a document (React Native) + if (!WINDOW.document) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error('Global document not defined in showReportDialog call'); + return; + } + + const { client, scope } = hub.getStackTop(); + const dsn = options.dsn || (client && client.getDsn()); + if (!dsn) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error('DSN not configured for showReportDialog call'); + return; + } + + if (scope) { + options.user = { + ...scope.getUser(), + ...options.user, + }; + } + + if (!options.eventId) { + options.eventId = hub.lastEventId(); + } + + const script = WINDOW.document.createElement('script'); + script.async = true; + script.src = getReportDialogEndpoint(dsn, options); + + if (options.onLoad) { + script.onload = options.onLoad; + } + + const injectionPoint = WINDOW.document.head || WINDOW.document.body; + if (injectionPoint) { + injectionPoint.appendChild(script); + } else { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error('Not injecting report dialog. No injection point found in HTML'); + } +} + +/** + * This is the getter for lastEventId. + * + * @returns The last event id of a captured event. + */ +function lastEventId() { + return getCurrentHub().lastEventId(); +} + +/** + * This function is here to be API compatible with the loader. + * @hidden + */ +function forceLoad() { + // Noop +} + +/** + * This function is here to be API compatible with the loader. + * @hidden + */ +function onLoad(callback) { + callback(); +} + +/** + * Call `flush()` on the current client, if there is one. See {@link Client.flush}. + * + * @param timeout Maximum time in ms the client should wait to flush its event queue. Omitting this parameter will cause + * the client to wait until all events are sent before resolving the promise. + * @returns A promise which resolves to `true` if the queue successfully drains before the timeout, or `false` if it + * doesn't (or if there's no client defined). + */ +function flush(timeout) { + const client = getCurrentHub().getClient(); + if (client) { + return client.flush(timeout); + } + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('Cannot flush events. No client defined.'); + return resolvedSyncPromise(false); +} + +/** + * Call `close()` on the current client, if there is one. See {@link Client.close}. + * + * @param timeout Maximum time in ms the client should wait to flush its event queue before shutting down. Omitting this + * parameter will cause the client to wait until all events are sent before disabling itself. + * @returns A promise which resolves to `true` if the queue successfully drains before the timeout, or `false` if it + * doesn't (or if there's no client defined). + */ +function close(timeout) { + const client = getCurrentHub().getClient(); + if (client) { + return client.close(timeout); + } + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('Cannot flush events and disable SDK. No client defined.'); + return resolvedSyncPromise(false); +} + +/** + * Wrap code within a try/catch block so the SDK is able to capture errors. + * + * @param fn A function to wrap. + * + * @returns The result of wrapped function call. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function wrap(fn) { + return wrap$1(fn)(); +} + +function startSessionOnHub(hub) { + hub.startSession({ ignoreDuration: true }); + hub.captureSession(); +} + +/** + * Enable automatic Session Tracking for the initial page load. + */ +function startSessionTracking() { + if (typeof WINDOW.document === 'undefined') { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && + logger.warn('Session tracking in non-browser environment with @sentry/browser is not supported.'); + return; + } + + const hub = getCurrentHub(); + + // The only way for this to be false is for there to be a version mismatch between @sentry/browser (>= 6.0.0) and + // @sentry/hub (< 5.27.0). In the simple case, there won't ever be such a mismatch, because the two packages are + // pinned at the same version in package.json, but there are edge cases where it's possible. See + // https://github.com/getsentry/sentry-javascript/issues/3207 and + // https://github.com/getsentry/sentry-javascript/issues/3234 and + // https://github.com/getsentry/sentry-javascript/issues/3278. + if (!hub.captureSession) { + return; + } + + // The session duration for browser sessions does not track a meaningful + // concept that can be used as a metric. + // Automatically captured sessions are akin to page views, and thus we + // discard their duration. + startSessionOnHub(hub); + + // We want to create a session for every navigation as well + addInstrumentationHandler('history', ({ from, to }) => { + // Don't create an additional session for the initial route or if the location did not change + if (!(from === undefined || from === to)) { + startSessionOnHub(getCurrentHub()); + } + }); +} + +/** + * Captures user feedback and sends it to Sentry. + */ +function captureUserFeedback(feedback) { + const client = getCurrentHub().getClient(); + if (client) { + client.captureUserFeedback(feedback); + } +} + +export { captureUserFeedback, close, defaultIntegrations, flush, forceLoad, init, lastEventId, onLoad, showReportDialog, wrap }; +//# sourceMappingURL=sdk.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/stack-parsers.js b/shared/logger/node_modules/@sentry/browser/esm/stack-parsers.js new file mode 100644 index 0000000..4f3da89 --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/stack-parsers.js @@ -0,0 +1,168 @@ +import { createStackParser } from '@sentry/utils'; + +// global reference to slice +const UNKNOWN_FUNCTION = '?'; + +const OPERA10_PRIORITY = 10; +const OPERA11_PRIORITY = 20; +const CHROME_PRIORITY = 30; +const WINJS_PRIORITY = 40; +const GECKO_PRIORITY = 50; + +function createFrame(filename, func, lineno, colno) { + const frame = { + filename, + function: func, + in_app: true, // All browser frames are considered in_app + }; + + if (lineno !== undefined) { + frame.lineno = lineno; + } + + if (colno !== undefined) { + frame.colno = colno; + } + + return frame; +} + +// Chromium based browsers: Chrome, Brave, new Opera, new Edge +const chromeRegex = + /^\s*at (?:(.+?\)(?: \[.+\])?|.*?) ?\((?:address at )?)?(?:async )?((?:|[-a-z]+:|.*bundle|\/)?.*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i; +const chromeEvalRegex = /\((\S*)(?::(\d+))(?::(\d+))\)/; + +const chrome = line => { + const parts = chromeRegex.exec(line); + + if (parts) { + const isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line + + if (isEval) { + const subMatch = chromeEvalRegex.exec(parts[2]); + + if (subMatch) { + // throw out eval line/column and use top-most line/column number + parts[2] = subMatch[1]; // url + parts[3] = subMatch[2]; // line + parts[4] = subMatch[3]; // column + } + } + + // Kamil: One more hack won't hurt us right? Understanding and adding more rules on top of these regexps right now + // would be way too time consuming. (TODO: Rewrite whole RegExp to be more readable) + const [func, filename] = extractSafariExtensionDetails(parts[1] || UNKNOWN_FUNCTION, parts[2]); + + return createFrame(filename, func, parts[3] ? +parts[3] : undefined, parts[4] ? +parts[4] : undefined); + } + + return; +}; + +const chromeStackLineParser = [CHROME_PRIORITY, chrome]; + +// gecko regex: `(?:bundle|\d+\.js)`: `bundle` is for react native, `\d+\.js` also but specifically for ram bundles because it +// generates filenames without a prefix like `file://` the filenames in the stacktrace are just 42.js +// We need this specific case for now because we want no other regex to match. +const geckoREgex = + /^\s*(.*?)(?:\((.*?)\))?(?:^|@)?((?:[-a-z]+)?:\/.*?|\[native code\]|[^@]*(?:bundle|\d+\.js)|\/[\w\-. /=]+)(?::(\d+))?(?::(\d+))?\s*$/i; +const geckoEvalRegex = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i; + +const gecko = line => { + const parts = geckoREgex.exec(line); + + if (parts) { + const isEval = parts[3] && parts[3].indexOf(' > eval') > -1; + if (isEval) { + const subMatch = geckoEvalRegex.exec(parts[3]); + + if (subMatch) { + // throw out eval line/column and use top-most line number + parts[1] = parts[1] || 'eval'; + parts[3] = subMatch[1]; + parts[4] = subMatch[2]; + parts[5] = ''; // no column when eval + } + } + + let filename = parts[3]; + let func = parts[1] || UNKNOWN_FUNCTION; + [func, filename] = extractSafariExtensionDetails(func, filename); + + return createFrame(filename, func, parts[4] ? +parts[4] : undefined, parts[5] ? +parts[5] : undefined); + } + + return; +}; + +const geckoStackLineParser = [GECKO_PRIORITY, gecko]; + +const winjsRegex = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:[-a-z]+):.*?):(\d+)(?::(\d+))?\)?\s*$/i; + +const winjs = line => { + const parts = winjsRegex.exec(line); + + return parts + ? createFrame(parts[2], parts[1] || UNKNOWN_FUNCTION, +parts[3], parts[4] ? +parts[4] : undefined) + : undefined; +}; + +const winjsStackLineParser = [WINJS_PRIORITY, winjs]; + +const opera10Regex = / line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i; + +const opera10 = line => { + const parts = opera10Regex.exec(line); + return parts ? createFrame(parts[2], parts[3] || UNKNOWN_FUNCTION, +parts[1]) : undefined; +}; + +const opera10StackLineParser = [OPERA10_PRIORITY, opera10]; + +const opera11Regex = + / line (\d+), column (\d+)\s*(?:in (?:]+)>|([^)]+))\(.*\))? in (.*):\s*$/i; + +const opera11 = line => { + const parts = opera11Regex.exec(line); + return parts ? createFrame(parts[5], parts[3] || parts[4] || UNKNOWN_FUNCTION, +parts[1], +parts[2]) : undefined; +}; + +const opera11StackLineParser = [OPERA11_PRIORITY, opera11]; + +const defaultStackLineParsers = [chromeStackLineParser, geckoStackLineParser, winjsStackLineParser]; + +const defaultStackParser = createStackParser(...defaultStackLineParsers); + +/** + * Safari web extensions, starting version unknown, can produce "frames-only" stacktraces. + * What it means, is that instead of format like: + * + * Error: wat + * at function@url:row:col + * at function@url:row:col + * at function@url:row:col + * + * it produces something like: + * + * function@url:row:col + * function@url:row:col + * function@url:row:col + * + * Because of that, it won't be captured by `chrome` RegExp and will fall into `Gecko` branch. + * This function is extracted so that we can use it in both places without duplicating the logic. + * Unfortunately "just" changing RegExp is too complicated now and making it pass all tests + * and fix this case seems like an impossible, or at least way too time-consuming task. + */ +const extractSafariExtensionDetails = (func, filename) => { + const isSafariExtension = func.indexOf('safari-extension') !== -1; + const isSafariWebExtension = func.indexOf('safari-web-extension') !== -1; + + return isSafariExtension || isSafariWebExtension + ? [ + func.indexOf('@') !== -1 ? func.split('@')[0] : UNKNOWN_FUNCTION, + isSafariExtension ? `safari-extension:${filename}` : `safari-web-extension:${filename}`, + ] + : [func, filename]; +}; + +export { chromeStackLineParser, defaultStackLineParsers, defaultStackParser, geckoStackLineParser, opera10StackLineParser, opera11StackLineParser, winjsStackLineParser }; +//# sourceMappingURL=stack-parsers.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/transports/fetch.js b/shared/logger/node_modules/@sentry/browser/esm/transports/fetch.js new file mode 100644 index 0000000..bb836e3 --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/transports/fetch.js @@ -0,0 +1,64 @@ +import { createTransport } from '@sentry/core'; +import { rejectedSyncPromise } from '@sentry/utils'; +import { getNativeFetchImplementation, clearCachedFetchImplementation } from './utils.js'; + +/** + * Creates a Transport that uses the Fetch API to send events to Sentry. + */ +function makeFetchTransport( + options, + nativeFetch = getNativeFetchImplementation(), +) { + let pendingBodySize = 0; + let pendingCount = 0; + + function makeRequest(request) { + const requestSize = request.body.length; + pendingBodySize += requestSize; + pendingCount++; + + const requestOptions = { + body: request.body, + method: 'POST', + referrerPolicy: 'origin', + headers: options.headers, + // Outgoing requests are usually cancelled when navigating to a different page, causing a "TypeError: Failed to + // fetch" error and sending a "network_error" client-outcome - in Chrome, the request status shows "(cancelled)". + // The `keepalive` flag keeps outgoing requests alive, even when switching pages. We want this since we're + // frequently sending events right before the user is switching pages (eg. whenfinishing navigation transactions). + // Gotchas: + // - `keepalive` isn't supported by Firefox + // - As per spec (https://fetch.spec.whatwg.org/#http-network-or-cache-fetch): + // If the sum of contentLength and inflightKeepaliveBytes is greater than 64 kibibytes, then return a network error. + // We will therefore only activate the flag when we're below that limit. + // There is also a limit of requests that can be open at the same time, so we also limit this to 15 + // See https://github.com/getsentry/sentry-javascript/pull/7553 for details + keepalive: pendingBodySize <= 60000 && pendingCount < 15, + ...options.fetchOptions, + }; + + try { + return nativeFetch(options.url, requestOptions).then(response => { + pendingBodySize -= requestSize; + pendingCount--; + return { + statusCode: response.status, + headers: { + 'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'), + 'retry-after': response.headers.get('Retry-After'), + }, + }; + }); + } catch (e) { + clearCachedFetchImplementation(); + pendingBodySize -= requestSize; + pendingCount--; + return rejectedSyncPromise(e); + } + } + + return createTransport(options, makeRequest); +} + +export { makeFetchTransport }; +//# sourceMappingURL=fetch.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/transports/offline.js b/shared/logger/node_modules/@sentry/browser/esm/transports/offline.js new file mode 100644 index 0000000..c60a02d --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/transports/offline.js @@ -0,0 +1,133 @@ +import { makeOfflineTransport } from '@sentry/core'; +import { serializeEnvelope, parseEnvelope } from '@sentry/utils'; + +// 'Store', 'promisifyRequest' and 'createStore' were originally copied from the 'idb-keyval' package before being +// modified and simplified: https://github.com/jakearchibald/idb-keyval +// +// At commit: 0420a704fd6cbb4225429c536b1f61112d012fca +// Original licence: + +// Copyright 2016, Jake Archibald +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +function promisifyRequest(request) { + return new Promise((resolve, reject) => { + // @ts-ignore - file size hacks + request.oncomplete = request.onsuccess = () => resolve(request.result); + // @ts-ignore - file size hacks + request.onabort = request.onerror = () => reject(request.error); + }); +} + +/** Create or open an IndexedDb store */ +function createStore(dbName, storeName) { + const request = indexedDB.open(dbName); + request.onupgradeneeded = () => request.result.createObjectStore(storeName); + const dbp = promisifyRequest(request); + + return callback => dbp.then(db => callback(db.transaction(storeName, 'readwrite').objectStore(storeName))); +} + +function keys(store) { + return promisifyRequest(store.getAllKeys() ); +} + +/** Insert into the store */ +function insert(store, value, maxQueueSize) { + return store(store => { + return keys(store).then(keys => { + if (keys.length >= maxQueueSize) { + return; + } + + // We insert with an incremented key so that the entries are popped in order + store.put(value, Math.max(...keys, 0) + 1); + return promisifyRequest(store.transaction); + }); + }); +} + +/** Pop the oldest value from the store */ +function pop(store) { + return store(store => { + return keys(store).then(keys => { + if (keys.length === 0) { + return undefined; + } + + return promisifyRequest(store.get(keys[0])).then(value => { + store.delete(keys[0]); + return promisifyRequest(store.transaction).then(() => value); + }); + }); + }); +} + +function createIndexedDbStore(options) { + let store; + + // Lazily create the store only when it's needed + function getStore() { + if (store == undefined) { + store = createStore(options.dbName || 'sentry-offline', options.storeName || 'queue'); + } + + return store; + } + + return { + insert: async (env) => { + try { + const serialized = await serializeEnvelope(env, options.textEncoder); + await insert(getStore(), serialized, options.maxQueueSize || 30); + } catch (_) { + // + } + }, + pop: async () => { + try { + const deserialized = await pop(getStore()); + if (deserialized) { + return parseEnvelope( + deserialized, + options.textEncoder || new TextEncoder(), + options.textDecoder || new TextDecoder(), + ); + } + } catch (_) { + // + } + + return undefined; + }, + }; +} + +function makeIndexedDbOfflineTransport( + createTransport, +) { + return options => createTransport({ ...options, createStore: createIndexedDbStore }); +} + +/** + * Creates a transport that uses IndexedDb to store events when offline. + */ +function makeBrowserOfflineTransport( + createTransport, +) { + return makeIndexedDbOfflineTransport(makeOfflineTransport(createTransport)); +} + +export { createStore, insert, makeBrowserOfflineTransport, pop }; +//# sourceMappingURL=offline.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/transports/utils.js b/shared/logger/node_modules/@sentry/browser/esm/transports/utils.js new file mode 100644 index 0000000..bc50f0c --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/transports/utils.js @@ -0,0 +1,85 @@ +import { isNativeFetch, logger } from '@sentry/utils'; +import { WINDOW } from '../helpers.js'; + +let cachedFetchImpl = undefined; + +/** + * A special usecase for incorrectly wrapped Fetch APIs in conjunction with ad-blockers. + * Whenever someone wraps the Fetch API and returns the wrong promise chain, + * this chain becomes orphaned and there is no possible way to capture it's rejections + * other than allowing it bubble up to this very handler. eg. + * + * const f = window.fetch; + * window.fetch = function () { + * const p = f.apply(this, arguments); + * + * p.then(function() { + * console.log('hi.'); + * }); + * + * return p; + * } + * + * `p.then(function () { ... })` is producing a completely separate promise chain, + * however, what's returned is `p` - the result of original `fetch` call. + * + * This mean, that whenever we use the Fetch API to send our own requests, _and_ + * some ad-blocker blocks it, this orphaned chain will _always_ reject, + * effectively causing another event to be captured. + * This makes a whole process become an infinite loop, which we need to somehow + * deal with, and break it in one way or another. + * + * To deal with this issue, we are making sure that we _always_ use the real + * browser Fetch API, instead of relying on what `window.fetch` exposes. + * The only downside to this would be missing our own requests as breadcrumbs, + * but because we are already not doing this, it should be just fine. + * + * Possible failed fetch error messages per-browser: + * + * Chrome: Failed to fetch + * Edge: Failed to Fetch + * Firefox: NetworkError when attempting to fetch resource + * Safari: resource blocked by content blocker + */ +function getNativeFetchImplementation() { + if (cachedFetchImpl) { + return cachedFetchImpl; + } + + /* eslint-disable @typescript-eslint/unbound-method */ + + // Fast path to avoid DOM I/O + if (isNativeFetch(WINDOW.fetch)) { + return (cachedFetchImpl = WINDOW.fetch.bind(WINDOW)); + } + + const document = WINDOW.document; + let fetchImpl = WINDOW.fetch; + // eslint-disable-next-line deprecation/deprecation + if (document && typeof document.createElement === 'function') { + try { + const sandbox = document.createElement('iframe'); + sandbox.hidden = true; + document.head.appendChild(sandbox); + const contentWindow = sandbox.contentWindow; + if (contentWindow && contentWindow.fetch) { + fetchImpl = contentWindow.fetch; + } + document.head.removeChild(sandbox); + } catch (e) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && + logger.warn('Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ', e); + } + } + + return (cachedFetchImpl = fetchImpl.bind(WINDOW)); + /* eslint-enable @typescript-eslint/unbound-method */ +} + +/** Clears cached fetch impl */ +function clearCachedFetchImplementation() { + cachedFetchImpl = undefined; +} + +export { clearCachedFetchImplementation, getNativeFetchImplementation }; +//# sourceMappingURL=utils.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/transports/xhr.js b/shared/logger/node_modules/@sentry/browser/esm/transports/xhr.js new file mode 100644 index 0000000..4cb34a0 --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/transports/xhr.js @@ -0,0 +1,52 @@ +import { createTransport } from '@sentry/core'; +import { SyncPromise } from '@sentry/utils'; + +/** + * The DONE ready state for XmlHttpRequest + * + * Defining it here as a constant b/c XMLHttpRequest.DONE is not always defined + * (e.g. during testing, it is `undefined`) + * + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState} + */ +const XHR_READYSTATE_DONE = 4; + +/** + * Creates a Transport that uses the XMLHttpRequest API to send events to Sentry. + */ +function makeXHRTransport(options) { + function makeRequest(request) { + return new SyncPromise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + + xhr.onerror = reject; + + xhr.onreadystatechange = () => { + if (xhr.readyState === XHR_READYSTATE_DONE) { + resolve({ + statusCode: xhr.status, + headers: { + 'x-sentry-rate-limits': xhr.getResponseHeader('X-Sentry-Rate-Limits'), + 'retry-after': xhr.getResponseHeader('Retry-After'), + }, + }); + } + }; + + xhr.open('POST', options.url); + + for (const header in options.headers) { + if (Object.prototype.hasOwnProperty.call(options.headers, header)) { + xhr.setRequestHeader(header, options.headers[header]); + } + } + + xhr.send(request.body); + }); + } + + return createTransport(options, makeRequest); +} + +export { makeXHRTransport }; +//# sourceMappingURL=xhr.js.map diff --git a/shared/logger/node_modules/@sentry/browser/esm/userfeedback.js b/shared/logger/node_modules/@sentry/browser/esm/userfeedback.js new file mode 100644 index 0000000..4765c98 --- /dev/null +++ b/shared/logger/node_modules/@sentry/browser/esm/userfeedback.js @@ -0,0 +1,41 @@ +import { dsnToString, createEnvelope } from '@sentry/utils'; + +/** + * Creates an envelope from a user feedback. + */ +function createUserFeedbackEnvelope( + feedback, + { + metadata, + tunnel, + dsn, + } + +, +) { + const headers = { + event_id: feedback.event_id, + sent_at: new Date().toISOString(), + ...(metadata && + metadata.sdk && { + sdk: { + name: metadata.sdk.name, + version: metadata.sdk.version, + }, + }), + ...(!!tunnel && !!dsn && { dsn: dsnToString(dsn) }), + }; + const item = createUserFeedbackEnvelopeItem(feedback); + + return createEnvelope(headers, [item]); +} + +function createUserFeedbackEnvelopeItem(feedback) { + const feedbackHeaders = { + type: 'user_report', + }; + return [feedbackHeaders, feedback]; +} + +export { createUserFeedbackEnvelope }; +//# sourceMappingURL=userfeedback.js.map -- cgit v1.2.3