diff options
| author | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
|---|---|---|
| committer | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
| commit | bce557cc2dc767628bed6aac87301a1be7c5431b (patch) | |
| tree | b51a051228d01fe3306cd7626d4a96768aadb944 /shared/logger/node_modules/@sentry-internal/tracing/esm | |
init commit
Diffstat (limited to 'shared/logger/node_modules/@sentry-internal/tracing/esm')
18 files changed, 1809 insertions, 0 deletions
diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/backgroundtab.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/backgroundtab.js new file mode 100644 index 0000000..62c1d20 --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/backgroundtab.js @@ -0,0 +1,36 @@ +import { getActiveTransaction } from '@sentry/core'; +import { logger } from '@sentry/utils'; +import { WINDOW } from './types.js'; + +/** + * Add a listener that cancels and finishes a transaction when the global + * document is hidden. + */ +function registerBackgroundTabDetection() { + if (WINDOW && WINDOW.document) { + WINDOW.document.addEventListener('visibilitychange', () => { + const activeTransaction = getActiveTransaction() ; + if (WINDOW.document.hidden && activeTransaction) { + const statusType = 'cancelled'; + + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && + logger.log( + `[Tracing] Transaction: ${statusType} -> since tab moved to the background, op: ${activeTransaction.op}`, + ); + // We should not set status if it is already set, this prevent important statuses like + // error or data loss from being overwritten on transaction. + if (!activeTransaction.status) { + activeTransaction.setStatus(statusType); + } + activeTransaction.setTag('visibilitychange', 'document.hidden'); + activeTransaction.finish(); + } + }); + } else { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && + logger.warn('[Tracing] Could not set up background tab detection due to lack of global document'); + } +} + +export { registerBackgroundTabDetection }; +//# sourceMappingURL=backgroundtab.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/browsertracing.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/browsertracing.js new file mode 100644 index 0000000..3b79fb3 --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/browsertracing.js @@ -0,0 +1,300 @@ +import { TRACING_DEFAULTS, addTracingExtensions, extractTraceparentData, startIdleTransaction, getActiveTransaction } from '@sentry/core'; +import { logger, baggageHeaderToDynamicSamplingContext, getDomElement } from '@sentry/utils'; +import { registerBackgroundTabDetection } from './backgroundtab.js'; +import { startTrackingWebVitals, startTrackingLongTasks, startTrackingInteractions, addPerformanceEntries } from './metrics/index.js'; +import { defaultRequestInstrumentationOptions, instrumentOutgoingRequests } from './request.js'; +import { instrumentRoutingWithDefaults } from './router.js'; +import { WINDOW } from './types.js'; + +const BROWSER_TRACING_INTEGRATION_ID = 'BrowserTracing'; + +/** Options for Browser Tracing integration */ + +const DEFAULT_BROWSER_TRACING_OPTIONS = { + ...TRACING_DEFAULTS, + markBackgroundTransactions: true, + routingInstrumentation: instrumentRoutingWithDefaults, + startTransactionOnLocationChange: true, + startTransactionOnPageLoad: true, + enableLongTask: true, + ...defaultRequestInstrumentationOptions, +}; + +/** + * The Browser Tracing integration automatically instruments browser pageload/navigation + * actions as transactions, and captures requests, metrics and errors as spans. + * + * The integration can be configured with a variety of options, and can be extended to use + * any routing library. This integration uses {@see IdleTransaction} to create transactions. + */ +class BrowserTracing { + // This class currently doesn't have a static `id` field like the other integration classes, because it prevented + // @sentry/tracing from being treeshaken. Tree shakers do not like static fields, because they behave like side effects. + // TODO: Come up with a better plan, than using static fields on integration classes, and use that plan on all + // integrations. + + /** Browser Tracing integration options */ + + /** + * @inheritDoc + */ + __init() {this.name = BROWSER_TRACING_INTEGRATION_ID;} + + __init2() {this._hasSetTracePropagationTargets = false;} + + constructor(_options) {BrowserTracing.prototype.__init.call(this);BrowserTracing.prototype.__init2.call(this); + addTracingExtensions(); + + if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) { + this._hasSetTracePropagationTargets = !!( + _options && + // eslint-disable-next-line deprecation/deprecation + (_options.tracePropagationTargets || _options.tracingOrigins) + ); + } + + this.options = { + ...DEFAULT_BROWSER_TRACING_OPTIONS, + ..._options, + }; + + // Special case: enableLongTask can be set in _experiments + // TODO (v8): Remove this in v8 + if (this.options._experiments.enableLongTask !== undefined) { + this.options.enableLongTask = this.options._experiments.enableLongTask; + } + + // TODO (v8): remove this block after tracingOrigins is removed + // Set tracePropagationTargets to tracingOrigins if specified by the user + // In case both are specified, tracePropagationTargets takes precedence + // eslint-disable-next-line deprecation/deprecation + if (_options && !_options.tracePropagationTargets && _options.tracingOrigins) { + // eslint-disable-next-line deprecation/deprecation + this.options.tracePropagationTargets = _options.tracingOrigins; + } + + this._collectWebVitals = startTrackingWebVitals(); + if (this.options.enableLongTask) { + startTrackingLongTasks(); + } + if (this.options._experiments.enableInteractions) { + startTrackingInteractions(); + } + } + + /** + * @inheritDoc + */ + setupOnce(_, getCurrentHub) { + this._getCurrentHub = getCurrentHub; + const hub = getCurrentHub(); + const client = hub.getClient(); + const clientOptions = client && client.getOptions(); + + const { + routingInstrumentation: instrumentRouting, + startTransactionOnLocationChange, + startTransactionOnPageLoad, + markBackgroundTransactions, + traceFetch, + traceXHR, + shouldCreateSpanForRequest, + _experiments, + } = this.options; + + const clientOptionsTracePropagationTargets = clientOptions && clientOptions.tracePropagationTargets; + // There are three ways to configure tracePropagationTargets: + // 1. via top level client option `tracePropagationTargets` + // 2. via BrowserTracing option `tracePropagationTargets` + // 3. via BrowserTracing option `tracingOrigins` (deprecated) + // + // To avoid confusion, favour top level client option `tracePropagationTargets`, and fallback to + // BrowserTracing option `tracePropagationTargets` and then `tracingOrigins` (deprecated). + // This is done as it minimizes bundle size (we don't have to have undefined checks). + // + // If both 1 and either one of 2 or 3 are set (from above), we log out a warning. + const tracePropagationTargets = clientOptionsTracePropagationTargets || this.options.tracePropagationTargets; + if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && this._hasSetTracePropagationTargets && clientOptionsTracePropagationTargets) { + logger.warn( + '[Tracing] The `tracePropagationTargets` option was set in the BrowserTracing integration and top level `Sentry.init`. The top level `Sentry.init` value is being used.', + ); + } + + instrumentRouting( + (context) => { + const transaction = this._createRouteTransaction(context); + + this.options._experiments.onStartRouteTransaction && + this.options._experiments.onStartRouteTransaction(transaction, context, getCurrentHub); + + return transaction; + }, + startTransactionOnPageLoad, + startTransactionOnLocationChange, + ); + + if (markBackgroundTransactions) { + registerBackgroundTabDetection(); + } + + if (_experiments.enableInteractions) { + this._registerInteractionListener(); + } + + instrumentOutgoingRequests({ + traceFetch, + traceXHR, + tracePropagationTargets, + shouldCreateSpanForRequest, + _experiments: { + enableHTTPTimings: _experiments.enableHTTPTimings, + }, + }); + } + + /** Create routing idle transaction. */ + _createRouteTransaction(context) { + if (!this._getCurrentHub) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && + logger.warn(`[Tracing] Did not create ${context.op} transaction because _getCurrentHub is invalid.`); + return undefined; + } + + const { beforeNavigate, idleTimeout, finalTimeout, heartbeatInterval } = this.options; + + const isPageloadTransaction = context.op === 'pageload'; + + const sentryTraceMetaTagValue = isPageloadTransaction ? getMetaContent('sentry-trace') : null; + const baggageMetaTagValue = isPageloadTransaction ? getMetaContent('baggage') : null; + + const traceParentData = sentryTraceMetaTagValue ? extractTraceparentData(sentryTraceMetaTagValue) : undefined; + const dynamicSamplingContext = baggageMetaTagValue + ? baggageHeaderToDynamicSamplingContext(baggageMetaTagValue) + : undefined; + + const expandedContext = { + ...context, + ...traceParentData, + metadata: { + ...context.metadata, + dynamicSamplingContext: traceParentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, + }, + trimEnd: true, + }; + + const modifiedContext = typeof beforeNavigate === 'function' ? beforeNavigate(expandedContext) : expandedContext; + + // For backwards compatibility reasons, beforeNavigate can return undefined to "drop" the transaction (prevent it + // from being sent to Sentry). + const finalContext = modifiedContext === undefined ? { ...expandedContext, sampled: false } : modifiedContext; + + // If `beforeNavigate` set a custom name, record that fact + finalContext.metadata = + finalContext.name !== expandedContext.name + ? { ...finalContext.metadata, source: 'custom' } + : finalContext.metadata; + + this._latestRouteName = finalContext.name; + this._latestRouteSource = finalContext.metadata && finalContext.metadata.source; + + if (finalContext.sampled === false) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && + logger.log(`[Tracing] Will not send ${finalContext.op} transaction because of beforeNavigate.`); + } + + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log(`[Tracing] Starting ${finalContext.op} transaction on scope`); + + const hub = this._getCurrentHub(); + const { location } = WINDOW; + + const idleTransaction = startIdleTransaction( + hub, + finalContext, + idleTimeout, + finalTimeout, + true, + { location }, // for use in the tracesSampler + heartbeatInterval, + ); + idleTransaction.registerBeforeFinishCallback(transaction => { + this._collectWebVitals(); + addPerformanceEntries(transaction); + }); + + return idleTransaction ; + } + + /** Start listener for interaction transactions */ + _registerInteractionListener() { + let inflightInteractionTransaction; + const registerInteractionTransaction = () => { + const { idleTimeout, finalTimeout, heartbeatInterval } = this.options; + const op = 'ui.action.click'; + + const currentTransaction = getActiveTransaction(); + if (currentTransaction && currentTransaction.op && ['navigation', 'pageload'].includes(currentTransaction.op)) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && + logger.warn( + `[Tracing] Did not create ${op} transaction because a pageload or navigation transaction is in progress.`, + ); + return undefined; + } + + if (inflightInteractionTransaction) { + inflightInteractionTransaction.setFinishReason('interactionInterrupted'); + inflightInteractionTransaction.finish(); + inflightInteractionTransaction = undefined; + } + + if (!this._getCurrentHub) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn(`[Tracing] Did not create ${op} transaction because _getCurrentHub is invalid.`); + return undefined; + } + + if (!this._latestRouteName) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && + logger.warn(`[Tracing] Did not create ${op} transaction because _latestRouteName is missing.`); + return undefined; + } + + const hub = this._getCurrentHub(); + const { location } = WINDOW; + + const context = { + name: this._latestRouteName, + op, + trimEnd: true, + metadata: { + source: this._latestRouteSource || 'url', + }, + }; + + inflightInteractionTransaction = startIdleTransaction( + hub, + context, + idleTimeout, + finalTimeout, + true, + { location }, // for use in the tracesSampler + heartbeatInterval, + ); + }; + + ['click'].forEach(type => { + addEventListener(type, registerInteractionTransaction, { once: false, capture: true }); + }); + } +} + +/** Returns the value of a meta tag */ +function getMetaContent(metaName) { + // Can't specify generic to `getDomElement` because tracing can be used + // in a variety of environments, have to disable `no-unsafe-member-access` + // as a result. + const metaTag = getDomElement(`meta[name=${metaName}]`); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + return metaTag ? metaTag.getAttribute('content') : null; +} + +export { BROWSER_TRACING_INTEGRATION_ID, BrowserTracing, getMetaContent }; +//# sourceMappingURL=browsertracing.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/metrics/index.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/metrics/index.js new file mode 100644 index 0000000..5bca7af --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/metrics/index.js @@ -0,0 +1,484 @@ +import { getActiveTransaction } from '@sentry/core'; +import { browserPerformanceTimeOrigin, logger, htmlTreeAsString } from '@sentry/utils'; +import { WINDOW } from '../types.js'; +import { onCLS } from '../web-vitals/getCLS.js'; +import { onFID } from '../web-vitals/getFID.js'; +import { onLCP } from '../web-vitals/getLCP.js'; +import { getVisibilityWatcher } from '../web-vitals/lib/getVisibilityWatcher.js'; +import { observe } from '../web-vitals/lib/observe.js'; +import { _startChild, isMeasurementValue } from './utils.js'; + +/** + * Converts from milliseconds to seconds + * @param time time in ms + */ +function msToSec(time) { + return time / 1000; +} + +function getBrowserPerformanceAPI() { + // @ts-ignore we want to make sure all of these are available, even if TS is sure they are + return WINDOW && WINDOW.addEventListener && WINDOW.performance; +} + +let _performanceCursor = 0; + +let _measurements = {}; +let _lcpEntry; +let _clsEntry; + +/** + * Start tracking web vitals + * + * @returns A function that forces web vitals collection + */ +function startTrackingWebVitals() { + const performance = getBrowserPerformanceAPI(); + if (performance && browserPerformanceTimeOrigin) { + // @ts-ignore we want to make sure all of these are available, even if TS is sure they are + if (performance.mark) { + WINDOW.performance.mark('sentry-tracing-init'); + } + _trackFID(); + const clsCallback = _trackCLS(); + const lcpCallback = _trackLCP(); + + return () => { + if (clsCallback) { + clsCallback(); + } + if (lcpCallback) { + lcpCallback(); + } + }; + } + + return () => undefined; +} + +/** + * Start tracking long tasks. + */ +function startTrackingLongTasks() { + const entryHandler = (entries) => { + for (const entry of entries) { + const transaction = getActiveTransaction() ; + if (!transaction) { + return; + } + const startTime = msToSec((browserPerformanceTimeOrigin ) + entry.startTime); + const duration = msToSec(entry.duration); + + transaction.startChild({ + description: 'Main UI thread blocked', + op: 'ui.long-task', + startTimestamp: startTime, + endTimestamp: startTime + duration, + }); + } + }; + + observe('longtask', entryHandler); +} + +/** + * Start tracking interaction events. + */ +function startTrackingInteractions() { + const entryHandler = (entries) => { + for (const entry of entries) { + const transaction = getActiveTransaction() ; + if (!transaction) { + return; + } + + if (entry.name === 'click') { + const startTime = msToSec((browserPerformanceTimeOrigin ) + entry.startTime); + const duration = msToSec(entry.duration); + + transaction.startChild({ + description: htmlTreeAsString(entry.target), + op: `ui.interaction.${entry.name}`, + startTimestamp: startTime, + endTimestamp: startTime + duration, + }); + } + } + }; + + observe('event', entryHandler, { durationThreshold: 0 }); +} + +/** Starts tracking the Cumulative Layout Shift on the current page. */ +function _trackCLS() { + // See: + // https://web.dev/evolving-cls/ + // https://web.dev/cls-web-tooling/ + return onCLS(metric => { + const entry = metric.entries.pop(); + if (!entry) { + return; + } + + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Measurements] Adding CLS'); + _measurements['cls'] = { value: metric.value, unit: '' }; + _clsEntry = entry ; + }); +} + +/** Starts tracking the Largest Contentful Paint on the current page. */ +function _trackLCP() { + return onLCP(metric => { + const entry = metric.entries.pop(); + if (!entry) { + return; + } + + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Measurements] Adding LCP'); + _measurements['lcp'] = { value: metric.value, unit: 'millisecond' }; + _lcpEntry = entry ; + }); +} + +/** Starts tracking the First Input Delay on the current page. */ +function _trackFID() { + onFID(metric => { + const entry = metric.entries.pop(); + if (!entry) { + return; + } + + const timeOrigin = msToSec(browserPerformanceTimeOrigin ); + const startTime = msToSec(entry.startTime); + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Measurements] Adding FID'); + _measurements['fid'] = { value: metric.value, unit: 'millisecond' }; + _measurements['mark.fid'] = { value: timeOrigin + startTime, unit: 'second' }; + }); +} + +/** Add performance related spans to a transaction */ +function addPerformanceEntries(transaction) { + const performance = getBrowserPerformanceAPI(); + if (!performance || !WINDOW.performance.getEntries || !browserPerformanceTimeOrigin) { + // Gatekeeper if performance API not available + return; + } + + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Tracing] Adding & adjusting spans using Performance API'); + const timeOrigin = msToSec(browserPerformanceTimeOrigin); + + const performanceEntries = performance.getEntries(); + + let responseStartTimestamp; + let requestStartTimestamp; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + performanceEntries.slice(_performanceCursor).forEach((entry) => { + const startTime = msToSec(entry.startTime); + const duration = msToSec(entry.duration); + + if (transaction.op === 'navigation' && timeOrigin + startTime < transaction.startTimestamp) { + return; + } + + switch (entry.entryType) { + case 'navigation': { + _addNavigationSpans(transaction, entry, timeOrigin); + responseStartTimestamp = timeOrigin + msToSec(entry.responseStart); + requestStartTimestamp = timeOrigin + msToSec(entry.requestStart); + break; + } + case 'mark': + case 'paint': + case 'measure': { + _addMeasureSpans(transaction, entry, startTime, duration, timeOrigin); + + // capture web vitals + const firstHidden = getVisibilityWatcher(); + // Only report if the page wasn't hidden prior to the web vital. + const shouldRecord = entry.startTime < firstHidden.firstHiddenTime; + + if (entry.name === 'first-paint' && shouldRecord) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Measurements] Adding FP'); + _measurements['fp'] = { value: entry.startTime, unit: 'millisecond' }; + } + if (entry.name === 'first-contentful-paint' && shouldRecord) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Measurements] Adding FCP'); + _measurements['fcp'] = { value: entry.startTime, unit: 'millisecond' }; + } + break; + } + case 'resource': { + const resourceName = (entry.name ).replace(WINDOW.location.origin, ''); + _addResourceSpans(transaction, entry, resourceName, startTime, duration, timeOrigin); + break; + } + // Ignore other entry types. + } + }); + + _performanceCursor = Math.max(performanceEntries.length - 1, 0); + + _trackNavigator(transaction); + + // Measurements are only available for pageload transactions + if (transaction.op === 'pageload') { + // Generate TTFB (Time to First Byte), which measured as the time between the beginning of the transaction and the + // start of the response in milliseconds + if (typeof responseStartTimestamp === 'number') { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Measurements] Adding TTFB'); + _measurements['ttfb'] = { + value: (responseStartTimestamp - transaction.startTimestamp) * 1000, + unit: 'millisecond', + }; + + if (typeof requestStartTimestamp === 'number' && requestStartTimestamp <= responseStartTimestamp) { + // Capture the time spent making the request and receiving the first byte of the response. + // This is the time between the start of the request and the start of the response in milliseconds. + _measurements['ttfb.requestTime'] = { + value: (responseStartTimestamp - requestStartTimestamp) * 1000, + unit: 'millisecond', + }; + } + } + + ['fcp', 'fp', 'lcp'].forEach(name => { + if (!_measurements[name] || timeOrigin >= transaction.startTimestamp) { + return; + } + // The web vitals, fcp, fp, lcp, and ttfb, all measure relative to timeOrigin. + // Unfortunately, timeOrigin is not captured within the transaction span data, so these web vitals will need + // to be adjusted to be relative to transaction.startTimestamp. + const oldValue = _measurements[name].value; + const measurementTimestamp = timeOrigin + msToSec(oldValue); + + // normalizedValue should be in milliseconds + const normalizedValue = Math.abs((measurementTimestamp - transaction.startTimestamp) * 1000); + const delta = normalizedValue - oldValue; + + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && + logger.log(`[Measurements] Normalized ${name} from ${oldValue} to ${normalizedValue} (${delta})`); + _measurements[name].value = normalizedValue; + }); + + const fidMark = _measurements['mark.fid']; + if (fidMark && _measurements['fid']) { + // create span for FID + _startChild(transaction, { + description: 'first input delay', + endTimestamp: fidMark.value + msToSec(_measurements['fid'].value), + op: 'ui.action', + startTimestamp: fidMark.value, + }); + + // Delete mark.fid as we don't want it to be part of final payload + delete _measurements['mark.fid']; + } + + // If FCP is not recorded we should not record the cls value + // according to the new definition of CLS. + if (!('fcp' in _measurements)) { + delete _measurements.cls; + } + + Object.keys(_measurements).forEach(measurementName => { + transaction.setMeasurement( + measurementName, + _measurements[measurementName].value, + _measurements[measurementName].unit, + ); + }); + + _tagMetricInfo(transaction); + } + + _lcpEntry = undefined; + _clsEntry = undefined; + _measurements = {}; +} + +/** Create measure related spans */ +function _addMeasureSpans( + transaction, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + entry, + startTime, + duration, + timeOrigin, +) { + const measureStartTimestamp = timeOrigin + startTime; + const measureEndTimestamp = measureStartTimestamp + duration; + + _startChild(transaction, { + description: entry.name , + endTimestamp: measureEndTimestamp, + op: entry.entryType , + startTimestamp: measureStartTimestamp, + }); + + return measureStartTimestamp; +} + +/** Instrument navigation entries */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function _addNavigationSpans(transaction, entry, timeOrigin) { + ['unloadEvent', 'redirect', 'domContentLoadedEvent', 'loadEvent', 'connect'].forEach(event => { + _addPerformanceNavigationTiming(transaction, entry, event, timeOrigin); + }); + _addPerformanceNavigationTiming(transaction, entry, 'secureConnection', timeOrigin, 'TLS/SSL', 'connectEnd'); + _addPerformanceNavigationTiming(transaction, entry, 'fetch', timeOrigin, 'cache', 'domainLookupStart'); + _addPerformanceNavigationTiming(transaction, entry, 'domainLookup', timeOrigin, 'DNS'); + _addRequest(transaction, entry, timeOrigin); +} + +/** Create performance navigation related spans */ +function _addPerformanceNavigationTiming( + transaction, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + entry, + event, + timeOrigin, + description, + eventEnd, +) { + const end = eventEnd ? (entry[eventEnd] ) : (entry[`${event}End`] ); + const start = entry[`${event}Start`] ; + if (!start || !end) { + return; + } + _startChild(transaction, { + op: 'browser', + description: description || event, + startTimestamp: timeOrigin + msToSec(start), + endTimestamp: timeOrigin + msToSec(end), + }); +} + +/** Create request and response related spans */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function _addRequest(transaction, entry, timeOrigin) { + _startChild(transaction, { + op: 'browser', + description: 'request', + startTimestamp: timeOrigin + msToSec(entry.requestStart ), + endTimestamp: timeOrigin + msToSec(entry.responseEnd ), + }); + + _startChild(transaction, { + op: 'browser', + description: 'response', + startTimestamp: timeOrigin + msToSec(entry.responseStart ), + endTimestamp: timeOrigin + msToSec(entry.responseEnd ), + }); +} + +/** Create resource-related spans */ +function _addResourceSpans( + transaction, + entry, + resourceName, + startTime, + duration, + timeOrigin, +) { + // we already instrument based on fetch and xhr, so we don't need to + // duplicate spans here. + if (entry.initiatorType === 'xmlhttprequest' || entry.initiatorType === 'fetch') { + return; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const data = {}; + if ('transferSize' in entry) { + data['http.response_transfer_size'] = entry.transferSize; + } + if ('encodedBodySize' in entry) { + data['http.response_content_length'] = entry.encodedBodySize; + } + if ('decodedBodySize' in entry) { + data['http.decoded_response_content_length'] = entry.decodedBodySize; + } + if ('renderBlockingStatus' in entry) { + data['resource.render_blocking_status'] = entry.renderBlockingStatus; + } + + const startTimestamp = timeOrigin + startTime; + const endTimestamp = startTimestamp + duration; + + _startChild(transaction, { + description: resourceName, + endTimestamp, + op: entry.initiatorType ? `resource.${entry.initiatorType}` : 'resource.other', + startTimestamp, + data, + }); +} + +/** + * Capture the information of the user agent. + */ +function _trackNavigator(transaction) { + const navigator = WINDOW.navigator ; + if (!navigator) { + return; + } + + // track network connectivity + const connection = navigator.connection; + if (connection) { + if (connection.effectiveType) { + transaction.setTag('effectiveConnectionType', connection.effectiveType); + } + + if (connection.type) { + transaction.setTag('connectionType', connection.type); + } + + if (isMeasurementValue(connection.rtt)) { + _measurements['connection.rtt'] = { value: connection.rtt, unit: 'millisecond' }; + } + } + + if (isMeasurementValue(navigator.deviceMemory)) { + transaction.setTag('deviceMemory', `${navigator.deviceMemory} GB`); + } + + if (isMeasurementValue(navigator.hardwareConcurrency)) { + transaction.setTag('hardwareConcurrency', String(navigator.hardwareConcurrency)); + } +} + +/** Add LCP / CLS data to transaction to allow debugging */ +function _tagMetricInfo(transaction) { + if (_lcpEntry) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Measurements] Adding LCP Data'); + + // Capture Properties of the LCP element that contributes to the LCP. + + if (_lcpEntry.element) { + transaction.setTag('lcp.element', htmlTreeAsString(_lcpEntry.element)); + } + + if (_lcpEntry.id) { + transaction.setTag('lcp.id', _lcpEntry.id); + } + + if (_lcpEntry.url) { + // Trim URL to the first 200 characters. + transaction.setTag('lcp.url', _lcpEntry.url.trim().slice(0, 200)); + } + + transaction.setTag('lcp.size', _lcpEntry.size); + } + + // See: https://developer.mozilla.org/en-US/docs/Web/API/LayoutShift + if (_clsEntry && _clsEntry.sources) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Measurements] Adding CLS Data'); + _clsEntry.sources.forEach((source, index) => + transaction.setTag(`cls.source.${index + 1}`, htmlTreeAsString(source.node)), + ); + } +} + +export { _addMeasureSpans, _addResourceSpans, addPerformanceEntries, startTrackingInteractions, startTrackingLongTasks, startTrackingWebVitals }; +//# sourceMappingURL=index.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/metrics/utils.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/metrics/utils.js new file mode 100644 index 0000000..ecb4873 --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/metrics/utils.js @@ -0,0 +1,25 @@ +/** + * Checks if a given value is a valid measurement value. + */ +function isMeasurementValue(value) { + return typeof value === 'number' && isFinite(value); +} + +/** + * Helper function to start child on transactions. This function will make sure that the transaction will + * use the start timestamp of the created child span if it is earlier than the transactions actual + * start timestamp. + */ +function _startChild(transaction, { startTimestamp, ...ctx }) { + if (startTimestamp && transaction.startTimestamp > startTimestamp) { + transaction.startTimestamp = startTimestamp; + } + + return transaction.startChild({ + startTimestamp, + ...ctx, + }); +} + +export { _startChild, isMeasurementValue }; +//# sourceMappingURL=utils.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/request.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/request.js new file mode 100644 index 0000000..d505546 --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/request.js @@ -0,0 +1,335 @@ +import { _optionalChain } from '@sentry/utils/esm/buildPolyfills'; +import { hasTracingEnabled, getCurrentHub } from '@sentry/core'; +import { addInstrumentationHandler, browserPerformanceTimeOrigin, dynamicSamplingContextToSentryBaggageHeader, isInstanceOf, BAGGAGE_HEADER_NAME, SENTRY_XHR_DATA_KEY, stringMatchesSomePattern } from '@sentry/utils'; + +/* eslint-disable max-lines */ + +const DEFAULT_TRACE_PROPAGATION_TARGETS = ['localhost', /^\/(?!\/)/]; + +/** Options for Request Instrumentation */ + +const defaultRequestInstrumentationOptions = { + traceFetch: true, + traceXHR: true, + // TODO (v8): Remove this property + tracingOrigins: DEFAULT_TRACE_PROPAGATION_TARGETS, + tracePropagationTargets: DEFAULT_TRACE_PROPAGATION_TARGETS, + _experiments: {}, +}; + +/** Registers span creators for xhr and fetch requests */ +function instrumentOutgoingRequests(_options) { + // eslint-disable-next-line deprecation/deprecation + const { traceFetch, traceXHR, tracePropagationTargets, tracingOrigins, shouldCreateSpanForRequest, _experiments } = { + traceFetch: defaultRequestInstrumentationOptions.traceFetch, + traceXHR: defaultRequestInstrumentationOptions.traceXHR, + ..._options, + }; + + const shouldCreateSpan = + typeof shouldCreateSpanForRequest === 'function' ? shouldCreateSpanForRequest : (_) => true; + + // TODO(v8) Remove tracingOrigins here + // The only reason we're passing it in here is because this instrumentOutgoingRequests function is publicly exported + // and we don't want to break the API. We can remove it in v8. + const shouldAttachHeadersWithTargets = (url) => + shouldAttachHeaders(url, tracePropagationTargets || tracingOrigins); + + const spans = {}; + + if (traceFetch) { + addInstrumentationHandler('fetch', (handlerData) => { + const createdSpan = fetchCallback(handlerData, shouldCreateSpan, shouldAttachHeadersWithTargets, spans); + if (_optionalChain([_experiments, 'optionalAccess', _2 => _2.enableHTTPTimings]) && createdSpan) { + addHTTPTimings(createdSpan); + } + }); + } + + if (traceXHR) { + addInstrumentationHandler('xhr', (handlerData) => { + const createdSpan = xhrCallback(handlerData, shouldCreateSpan, shouldAttachHeadersWithTargets, spans); + if (_optionalChain([_experiments, 'optionalAccess', _3 => _3.enableHTTPTimings]) && createdSpan) { + addHTTPTimings(createdSpan); + } + }); + } +} + +/** + * Creates a temporary observer to listen to the next fetch/xhr resourcing timings, + * so that when timings hit their per-browser limit they don't need to be removed. + * + * @param span A span that has yet to be finished, must contain `url` on data. + */ +function addHTTPTimings(span) { + const url = span.data.url; + const observer = new PerformanceObserver(list => { + const entries = list.getEntries() ; + entries.forEach(entry => { + if ((entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest') && entry.name.endsWith(url)) { + const spanData = resourceTimingEntryToSpanData(entry); + spanData.forEach(data => span.setData(...data)); + observer.disconnect(); + } + }); + }); + observer.observe({ + entryTypes: ['resource'], + }); +} + +function resourceTimingEntryToSpanData(resourceTiming) { + const version = resourceTiming.nextHopProtocol.split('/')[1] || 'none'; + + const timingSpanData = []; + if (version) { + timingSpanData.push(['network.protocol.version', version]); + } + + if (!browserPerformanceTimeOrigin) { + return timingSpanData; + } + return [ + ...timingSpanData, + ['http.request.connect_start', (browserPerformanceTimeOrigin + resourceTiming.connectStart) / 1000], + ['http.request.request_start', (browserPerformanceTimeOrigin + resourceTiming.requestStart) / 1000], + ['http.request.response_start', (browserPerformanceTimeOrigin + resourceTiming.responseStart) / 1000], + ]; +} + +/** + * A function that determines whether to attach tracing headers to a request. + * This was extracted from `instrumentOutgoingRequests` to make it easier to test shouldAttachHeaders. + * We only export this fuction for testing purposes. + */ +function shouldAttachHeaders(url, tracePropagationTargets) { + return stringMatchesSomePattern(url, tracePropagationTargets || DEFAULT_TRACE_PROPAGATION_TARGETS); +} + +/** + * Create and track fetch request spans + * + * @returns Span if a span was created, otherwise void. + */ +function fetchCallback( + handlerData, + shouldCreateSpan, + shouldAttachHeaders, + spans, +) { + if (!hasTracingEnabled() || !(handlerData.fetchData && shouldCreateSpan(handlerData.fetchData.url))) { + return; + } + + if (handlerData.endTimestamp) { + const spanId = handlerData.fetchData.__span; + if (!spanId) return; + + const span = spans[spanId]; + if (span) { + if (handlerData.response) { + // TODO (kmclb) remove this once types PR goes through + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + span.setHttpStatus(handlerData.response.status); + + const contentLength = + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + handlerData.response && handlerData.response.headers && handlerData.response.headers.get('content-length'); + + const contentLengthNum = parseInt(contentLength); + if (contentLengthNum > 0) { + span.setData('http.response_content_length', contentLengthNum); + } + } else if (handlerData.error) { + span.setStatus('internal_error'); + } + span.finish(); + + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete spans[spanId]; + } + return; + } + + const currentSpan = getCurrentHub().getScope().getSpan(); + const activeTransaction = currentSpan && currentSpan.transaction; + + if (currentSpan && activeTransaction) { + const { method, url } = handlerData.fetchData; + const span = currentSpan.startChild({ + data: { + url, + type: 'fetch', + 'http.method': method, + }, + description: `${method} ${url}`, + op: 'http.client', + }); + + handlerData.fetchData.__span = span.spanId; + spans[span.spanId] = span; + + const request = handlerData.args[0]; + + // In case the user hasn't set the second argument of a fetch call we default it to `{}`. + handlerData.args[1] = handlerData.args[1] || {}; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const options = handlerData.args[1]; + + if (shouldAttachHeaders(handlerData.fetchData.url)) { + options.headers = addTracingHeadersToFetchRequest( + request, + activeTransaction.getDynamicSamplingContext(), + span, + options, + ); + } + return span; + } +} + +/** + * Adds sentry-trace and baggage headers to the various forms of fetch headers + */ +function addTracingHeadersToFetchRequest( + request, // unknown is actually type Request but we can't export DOM types from this package, + dynamicSamplingContext, + span, + options + +, +) { + const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); + const sentryTraceHeader = span.toTraceparent(); + + const headers = + typeof Request !== 'undefined' && isInstanceOf(request, Request) ? (request ).headers : options.headers; + + if (!headers) { + return { 'sentry-trace': sentryTraceHeader, baggage: sentryBaggageHeader }; + } else if (typeof Headers !== 'undefined' && isInstanceOf(headers, Headers)) { + const newHeaders = new Headers(headers ); + + newHeaders.append('sentry-trace', sentryTraceHeader); + + if (sentryBaggageHeader) { + // If the same header is appended multiple times the browser will merge the values into a single request header. + // Its therefore safe to simply push a "baggage" entry, even though there might already be another baggage header. + newHeaders.append(BAGGAGE_HEADER_NAME, sentryBaggageHeader); + } + + return newHeaders ; + } else if (Array.isArray(headers)) { + const newHeaders = [...headers, ['sentry-trace', sentryTraceHeader]]; + + if (sentryBaggageHeader) { + // If there are multiple entries with the same key, the browser will merge the values into a single request header. + // Its therefore safe to simply push a "baggage" entry, even though there might already be another baggage header. + newHeaders.push([BAGGAGE_HEADER_NAME, sentryBaggageHeader]); + } + + return newHeaders ; + } else { + const existingBaggageHeader = 'baggage' in headers ? headers.baggage : undefined; + const newBaggageHeaders = []; + + if (Array.isArray(existingBaggageHeader)) { + newBaggageHeaders.push(...existingBaggageHeader); + } else if (existingBaggageHeader) { + newBaggageHeaders.push(existingBaggageHeader); + } + + if (sentryBaggageHeader) { + newBaggageHeaders.push(sentryBaggageHeader); + } + + return { + ...(headers ), + 'sentry-trace': sentryTraceHeader, + baggage: newBaggageHeaders.length > 0 ? newBaggageHeaders.join(',') : undefined, + }; + } +} + +/** + * Create and track xhr request spans + * + * @returns Span if a span was created, otherwise void. + */ +function xhrCallback( + handlerData, + shouldCreateSpan, + shouldAttachHeaders, + spans, +) { + const xhr = handlerData.xhr; + const sentryXhrData = xhr && xhr[SENTRY_XHR_DATA_KEY]; + + if ( + !hasTracingEnabled() || + (xhr && xhr.__sentry_own_request__) || + !(xhr && sentryXhrData && shouldCreateSpan(sentryXhrData.url)) + ) { + return; + } + + // check first if the request has finished and is tracked by an existing span which should now end + if (handlerData.endTimestamp) { + const spanId = xhr.__sentry_xhr_span_id__; + if (!spanId) return; + + const span = spans[spanId]; + if (span) { + span.setHttpStatus(sentryXhrData.status_code); + span.finish(); + + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete spans[spanId]; + } + return; + } + + const currentSpan = getCurrentHub().getScope().getSpan(); + const activeTransaction = currentSpan && currentSpan.transaction; + + if (currentSpan && activeTransaction) { + const span = currentSpan.startChild({ + data: { + ...sentryXhrData.data, + type: 'xhr', + 'http.method': sentryXhrData.method, + url: sentryXhrData.url, + }, + description: `${sentryXhrData.method} ${sentryXhrData.url}`, + op: 'http.client', + }); + + xhr.__sentry_xhr_span_id__ = span.spanId; + spans[xhr.__sentry_xhr_span_id__] = span; + + if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url)) { + try { + xhr.setRequestHeader('sentry-trace', span.toTraceparent()); + + const dynamicSamplingContext = activeTransaction.getDynamicSamplingContext(); + const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); + + if (sentryBaggageHeader) { + // From MDN: "If this method is called several times with the same header, the values are merged into one single request header." + // We can therefore simply set a baggage header without checking what was there before + // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader + xhr.setRequestHeader(BAGGAGE_HEADER_NAME, sentryBaggageHeader); + } + } catch (_) { + // Error: InvalidStateError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED. + } + } + + return span; + } +} + +export { DEFAULT_TRACE_PROPAGATION_TARGETS, addTracingHeadersToFetchRequest, defaultRequestInstrumentationOptions, instrumentOutgoingRequests, shouldAttachHeaders }; +//# sourceMappingURL=request.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/router.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/router.js new file mode 100644 index 0000000..8c70bef --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/router.js @@ -0,0 +1,64 @@ +import { logger, browserPerformanceTimeOrigin, addInstrumentationHandler } from '@sentry/utils'; +import { WINDOW } from './types.js'; + +/** + * Default function implementing pageload and navigation transactions + */ +function instrumentRoutingWithDefaults( + customStartTransaction, + startTransactionOnPageLoad = true, + startTransactionOnLocationChange = true, +) { + if (!WINDOW || !WINDOW.location) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('Could not initialize routing instrumentation due to invalid location'); + return; + } + + let startingUrl = WINDOW.location.href; + + let activeTransaction; + if (startTransactionOnPageLoad) { + activeTransaction = customStartTransaction({ + name: WINDOW.location.pathname, + // pageload should always start at timeOrigin (and needs to be in s, not ms) + startTimestamp: browserPerformanceTimeOrigin ? browserPerformanceTimeOrigin / 1000 : undefined, + op: 'pageload', + metadata: { source: 'url' }, + }); + } + + if (startTransactionOnLocationChange) { + addInstrumentationHandler('history', ({ to, from }) => { + /** + * This early return is there to account for some cases where a navigation transaction starts right after + * long-running pageload. We make sure that if `from` is undefined and a valid `startingURL` exists, we don't + * create an uneccessary navigation transaction. + * + * This was hard to duplicate, but this behavior stopped as soon as this fix was applied. This issue might also + * only be caused in certain development environments where the usage of a hot module reloader is causing + * errors. + */ + if (from === undefined && startingUrl && startingUrl.indexOf(to) !== -1) { + startingUrl = undefined; + return; + } + + if (from !== to) { + startingUrl = undefined; + if (activeTransaction) { + (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log(`[Tracing] Finishing current transaction with op: ${activeTransaction.op}`); + // If there's an open transaction on the scope, we need to finish it before creating an new one. + activeTransaction.finish(); + } + activeTransaction = customStartTransaction({ + name: WINDOW.location.pathname, + op: 'navigation', + metadata: { source: 'url' }, + }); + } + }); + } +} + +export { instrumentRoutingWithDefaults }; +//# sourceMappingURL=router.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/types.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/types.js new file mode 100644 index 0000000..b01c87e --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/types.js @@ -0,0 +1,6 @@ +import { GLOBAL_OBJ } from '@sentry/utils'; + +const WINDOW = GLOBAL_OBJ ; + +export { WINDOW }; +//# sourceMappingURL=types.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/getCLS.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/getCLS.js new file mode 100644 index 0000000..22f1922 --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/getCLS.js @@ -0,0 +1,105 @@ +import { bindReporter } from './lib/bindReporter.js'; +import { initMetric } from './lib/initMetric.js'; +import { observe } from './lib/observe.js'; +import { onHidden } from './lib/onHidden.js'; + +/* + * Copyright 2020 Google LLC + * + * 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 + * + * https://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. + */ + +/** + * Calculates the [CLS](https://web.dev/cls/) value for the current page and + * calls the `callback` function once the value is ready to be reported, along + * with all `layout-shift` performance entries that were used in the metric + * value calculation. The reported value is a `double` (corresponding to a + * [layout shift score](https://web.dev/cls/#layout-shift-score)). + * + * If the `reportAllChanges` configuration option is set to `true`, the + * `callback` function will be called as soon as the value is initially + * determined as well as any time the value changes throughout the page + * lifespan. + * + * _**Important:** CLS should be continually monitored for changes throughout + * the entire lifespan of a page—including if the user returns to the page after + * it's been hidden/backgrounded. However, since browsers often [will not fire + * additional callbacks once the user has backgrounded a + * page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden), + * `callback` is always called when the page's visibility state changes to + * hidden. As a result, the `callback` function might be called multiple times + * during the same page load._ + */ +const onCLS = (onReport) => { + const metric = initMetric('CLS', 0); + let report; + + let sessionValue = 0; + let sessionEntries = []; + + // const handleEntries = (entries: Metric['entries']) => { + const handleEntries = (entries) => { + entries.forEach(entry => { + // Only count layout shifts without recent user input. + if (!entry.hadRecentInput) { + const firstSessionEntry = sessionEntries[0]; + const lastSessionEntry = sessionEntries[sessionEntries.length - 1]; + + // If the entry occurred less than 1 second after the previous entry and + // less than 5 seconds after the first entry in the session, include the + // entry in the current session. Otherwise, start a new session. + if ( + sessionValue && + sessionEntries.length !== 0 && + entry.startTime - lastSessionEntry.startTime < 1000 && + entry.startTime - firstSessionEntry.startTime < 5000 + ) { + sessionValue += entry.value; + sessionEntries.push(entry); + } else { + sessionValue = entry.value; + sessionEntries = [entry]; + } + + // If the current session value is larger than the current CLS value, + // update CLS and the entries contributing to it. + if (sessionValue > metric.value) { + metric.value = sessionValue; + metric.entries = sessionEntries; + if (report) { + report(); + } + } + } + }); + }; + + const po = observe('layout-shift', handleEntries); + if (po) { + report = bindReporter(onReport, metric); + + const stopListening = () => { + handleEntries(po.takeRecords() ); + report(true); + }; + + onHidden(stopListening); + + return stopListening; + } + + return; +}; + +export { onCLS }; +//# sourceMappingURL=getCLS.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/getFID.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/getFID.js new file mode 100644 index 0000000..fc135c8 --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/getFID.js @@ -0,0 +1,63 @@ +import { bindReporter } from './lib/bindReporter.js'; +import { getVisibilityWatcher } from './lib/getVisibilityWatcher.js'; +import { initMetric } from './lib/initMetric.js'; +import { observe } from './lib/observe.js'; +import { onHidden } from './lib/onHidden.js'; + +/* + * Copyright 2020 Google LLC + * + * 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 + * + * https://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. + */ + +/** + * Calculates the [FID](https://web.dev/fid/) value for the current page and + * calls the `callback` function once the value is ready, along with the + * relevant `first-input` performance entry used to determine the value. The + * reported value is a `DOMHighResTimeStamp`. + * + * _**Important:** since FID is only reported after the user interacts with the + * page, it's possible that it will not be reported for some page loads._ + */ +const onFID = (onReport) => { + const visibilityWatcher = getVisibilityWatcher(); + const metric = initMetric('FID'); + // eslint-disable-next-line prefer-const + let report; + + const handleEntry = (entry) => { + // Only report if the page wasn't hidden prior to the first input. + if (entry.startTime < visibilityWatcher.firstHiddenTime) { + metric.value = entry.processingStart - entry.startTime; + metric.entries.push(entry); + report(true); + } + }; + + const handleEntries = (entries) => { + (entries ).forEach(handleEntry); + }; + + const po = observe('first-input', handleEntries); + report = bindReporter(onReport, metric); + + if (po) { + onHidden(() => { + handleEntries(po.takeRecords() ); + po.disconnect(); + }, true); + } +}; + +export { onFID }; +//# sourceMappingURL=getFID.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/getLCP.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/getLCP.js new file mode 100644 index 0000000..0d3c610 --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/getLCP.js @@ -0,0 +1,85 @@ +import { bindReporter } from './lib/bindReporter.js'; +import { getActivationStart } from './lib/getActivationStart.js'; +import { getVisibilityWatcher } from './lib/getVisibilityWatcher.js'; +import { initMetric } from './lib/initMetric.js'; +import { observe } from './lib/observe.js'; +import { onHidden } from './lib/onHidden.js'; + +/* + * Copyright 2020 Google LLC + * + * 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 + * + * https://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. + */ + +const reportedMetricIDs = {}; + +/** + * Calculates the [LCP](https://web.dev/lcp/) value for the current page and + * calls the `callback` function once the value is ready (along with the + * relevant `largest-contentful-paint` performance entry used to determine the + * value). The reported value is a `DOMHighResTimeStamp`. + */ +const onLCP = (onReport) => { + const visibilityWatcher = getVisibilityWatcher(); + const metric = initMetric('LCP'); + let report; + + const handleEntries = (entries) => { + const lastEntry = entries[entries.length - 1] ; + if (lastEntry) { + // The startTime attribute returns the value of the renderTime if it is + // not 0, and the value of the loadTime otherwise. The activationStart + // reference is used because LCP should be relative to page activation + // rather than navigation start if the page was prerendered. + const value = Math.max(lastEntry.startTime - getActivationStart(), 0); + + // Only report if the page wasn't hidden prior to LCP. + if (value < visibilityWatcher.firstHiddenTime) { + metric.value = value; + metric.entries = [lastEntry]; + report(); + } + } + }; + + const po = observe('largest-contentful-paint', handleEntries); + + if (po) { + report = bindReporter(onReport, metric); + + const stopListening = () => { + if (!reportedMetricIDs[metric.id]) { + handleEntries(po.takeRecords() ); + po.disconnect(); + reportedMetricIDs[metric.id] = true; + report(true); + } + }; + + // Stop listening after input. Note: while scrolling is an input that + // stop LCP observation, it's unreliable since it can be programmatically + // generated. See: https://github.com/GoogleChrome/web-vitals/issues/75 + ['keydown', 'click'].forEach(type => { + addEventListener(type, stopListening, { once: true, capture: true }); + }); + + onHidden(stopListening, true); + + return stopListening; + } + + return; +}; + +export { onLCP }; +//# sourceMappingURL=getLCP.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/bindReporter.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/bindReporter.js new file mode 100644 index 0000000..dc66278 --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/bindReporter.js @@ -0,0 +1,28 @@ +const bindReporter = ( + callback, + metric, + reportAllChanges, +) => { + let prevValue; + let delta; + return (forceReport) => { + if (metric.value >= 0) { + if (forceReport || reportAllChanges) { + delta = metric.value - (prevValue || 0); + + // Report the metric if there's a non-zero delta or if no previous + // value exists (which can happen in the case of the document becoming + // hidden when the metric value is 0). + // See: https://github.com/GoogleChrome/web-vitals/issues/14 + if (delta || prevValue === undefined) { + prevValue = metric.value; + metric.delta = delta; + callback(metric); + } + } + } + }; +}; + +export { bindReporter }; +//# sourceMappingURL=bindReporter.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/generateUniqueID.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/generateUniqueID.js new file mode 100644 index 0000000..dfde3bb --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/generateUniqueID.js @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Google LLC + * + * 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 + * + * https://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. + */ + +/** + * Performantly generate a unique, 30-char string by combining a version + * number, the current timestamp with a 13-digit number integer. + * @return {string} + */ +const generateUniqueID = () => { + return `v3-${Date.now()}-${Math.floor(Math.random() * (9e12 - 1)) + 1e12}`; +}; + +export { generateUniqueID }; +//# sourceMappingURL=generateUniqueID.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/getActivationStart.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/getActivationStart.js new file mode 100644 index 0000000..e7b7f65 --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/getActivationStart.js @@ -0,0 +1,25 @@ +import { getNavigationEntry } from './getNavigationEntry.js'; + +/* + * Copyright 2022 Google LLC + * + * 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 + * + * https://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. + */ + +const getActivationStart = () => { + const navEntry = getNavigationEntry(); + return (navEntry && navEntry.activationStart) || 0; +}; + +export { getActivationStart }; +//# sourceMappingURL=getActivationStart.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/getNavigationEntry.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/getNavigationEntry.js new file mode 100644 index 0000000..89a65a5 --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/getNavigationEntry.js @@ -0,0 +1,53 @@ +import { WINDOW } from '../../types.js'; + +/* + * Copyright 2022 Google LLC + * + * 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 + * + * https://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. + */ + +const getNavigationEntryFromPerformanceTiming = () => { + // eslint-disable-next-line deprecation/deprecation + const timing = WINDOW.performance.timing; + // eslint-disable-next-line deprecation/deprecation + const type = WINDOW.performance.navigation.type; + + const navigationEntry = { + entryType: 'navigation', + startTime: 0, + type: type == 2 ? 'back_forward' : type === 1 ? 'reload' : 'navigate', + }; + + for (const key in timing) { + if (key !== 'navigationStart' && key !== 'toJSON') { + // eslint-disable-next-line deprecation/deprecation + navigationEntry[key] = Math.max((timing[key ] ) - timing.navigationStart, 0); + } + } + return navigationEntry ; +}; + +const getNavigationEntry = () => { + if (WINDOW.__WEB_VITALS_POLYFILL__) { + return ( + WINDOW.performance && + ((performance.getEntriesByType && performance.getEntriesByType('navigation')[0]) || + getNavigationEntryFromPerformanceTiming()) + ); + } else { + return WINDOW.performance && performance.getEntriesByType && performance.getEntriesByType('navigation')[0]; + } +}; + +export { getNavigationEntry }; +//# sourceMappingURL=getNavigationEntry.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/getVisibilityWatcher.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/getVisibilityWatcher.js new file mode 100644 index 0000000..fb86cc2 --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/getVisibilityWatcher.js @@ -0,0 +1,54 @@ +import { WINDOW } from '../../types.js'; +import { onHidden } from './onHidden.js'; + +/* + * Copyright 2020 Google LLC + * + * 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 + * + * https://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. + */ + +let firstHiddenTime = -1; + +const initHiddenTime = () => { + // If the document is hidden and not prerendering, assume it was always + // hidden and the page was loaded in the background. + return WINDOW.document.visibilityState === 'hidden' && !WINDOW.document.prerendering ? 0 : Infinity; +}; + +const trackChanges = () => { + // Update the time if/when the document becomes hidden. + onHidden(({ timeStamp }) => { + firstHiddenTime = timeStamp; + }, true); +}; + +const getVisibilityWatcher = ( + +) => { + if (firstHiddenTime < 0) { + // If the document is hidden when this code runs, assume it was hidden + // since navigation start. This isn't a perfect heuristic, but it's the + // best we can do until an API is available to support querying past + // visibilityState. + firstHiddenTime = initHiddenTime(); + trackChanges(); + } + return { + get firstHiddenTime() { + return firstHiddenTime; + }, + }; +}; + +export { getVisibilityWatcher }; +//# sourceMappingURL=getVisibilityWatcher.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/initMetric.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/initMetric.js new file mode 100644 index 0000000..498c63d --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/initMetric.js @@ -0,0 +1,46 @@ +import { WINDOW } from '../../types.js'; +import { generateUniqueID } from './generateUniqueID.js'; +import { getActivationStart } from './getActivationStart.js'; +import { getNavigationEntry } from './getNavigationEntry.js'; + +/* + * Copyright 2020 Google LLC + * + * 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 + * + * https://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. + */ + +const initMetric = (name, value) => { + const navEntry = getNavigationEntry(); + let navigationType = 'navigate'; + + if (navEntry) { + if (WINDOW.document.prerendering || getActivationStart() > 0) { + navigationType = 'prerender'; + } else { + navigationType = navEntry.type.replace(/_/g, '-') ; + } + } + + return { + name, + value: typeof value === 'undefined' ? -1 : value, + rating: 'good', // Will be updated if the value changes. + delta: 0, + entries: [], + id: generateUniqueID(), + navigationType, + }; +}; + +export { initMetric }; +//# sourceMappingURL=initMetric.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/observe.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/observe.js new file mode 100644 index 0000000..94b7351 --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/observe.js @@ -0,0 +1,37 @@ +/** + * Takes a performance entry type and a callback function, and creates a + * `PerformanceObserver` instance that will observe the specified entry type + * with buffering enabled and call the callback _for each entry_. + * + * This function also feature-detects entry support and wraps the logic in a + * try/catch to avoid errors in unsupporting browsers. + */ +const observe = ( + type, + callback, + opts, +) => { + try { + if (PerformanceObserver.supportedEntryTypes.includes(type)) { + const po = new PerformanceObserver(list => { + callback(list.getEntries() ); + }); + po.observe( + Object.assign( + { + type, + buffered: true, + }, + opts || {}, + ) , + ); + return po; + } + } catch (e) { + // Do nothing. + } + return; +}; + +export { observe }; +//# sourceMappingURL=observe.js.map diff --git a/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/onHidden.js b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/onHidden.js new file mode 100644 index 0000000..78bb128 --- /dev/null +++ b/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/onHidden.js @@ -0,0 +1,36 @@ +import { WINDOW } from '../../types.js'; + +/* + * Copyright 2020 Google LLC + * + * 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 + * + * https://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. + */ + +const onHidden = (cb, once) => { + const onHiddenOrPageHide = (event) => { + if (event.type === 'pagehide' || WINDOW.document.visibilityState === 'hidden') { + cb(event); + if (once) { + removeEventListener('visibilitychange', onHiddenOrPageHide, true); + removeEventListener('pagehide', onHiddenOrPageHide, true); + } + } + }; + addEventListener('visibilitychange', onHiddenOrPageHide, true); + // Some browsers have buggy implementations of visibilitychange, + // so we use pagehide in addition, just to be safe. + addEventListener('pagehide', onHiddenOrPageHide, true); +}; + +export { onHidden }; +//# sourceMappingURL=onHidden.js.map |
