mirror of
https://github.com/rxliuli/apps.apple.com.git
synced 2025-11-09 21:10:33 +00:00
301 lines
11 KiB
JavaScript
301 lines
11 KiB
JavaScript
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
|