123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397 |
- Object.defineProperty(exports, '__esModule', { value: true });
- const core = require('@sentry/core');
- const utils = require('@sentry/utils');
- const debugBuild = require('../common/debug-build.js');
- const backgroundtab = require('./backgroundtab.js');
- const index = require('./metrics/index.js');
- const request = require('./request.js');
- const types = require('./types.js');
- const BROWSER_TRACING_INTEGRATION_ID = 'BrowserTracing';
- /** Options for Browser Tracing integration */
- const DEFAULT_BROWSER_TRACING_OPTIONS = {
- ...core.TRACING_DEFAULTS,
- instrumentNavigation: true,
- instrumentPageLoad: true,
- markBackgroundSpan: true,
- enableLongTask: true,
- _experiments: {},
- ...request.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.
- *
- * We explicitly export the proper type here, as this has to be extended in some cases.
- */
- const browserTracingIntegration = ((_options = {}) => {
- const _hasSetTracePropagationTargets = debugBuild.DEBUG_BUILD
- ? !!(
- // eslint-disable-next-line deprecation/deprecation
- (_options.tracePropagationTargets || _options.tracingOrigins)
- )
- : false;
- core.addTracingExtensions();
- // 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.tracePropagationTargets && _options.tracingOrigins) {
- // eslint-disable-next-line deprecation/deprecation
- _options.tracePropagationTargets = _options.tracingOrigins;
- }
- const options = {
- ...DEFAULT_BROWSER_TRACING_OPTIONS,
- ..._options,
- };
- const _collectWebVitals = index.startTrackingWebVitals();
- if (options.enableLongTask) {
- index.startTrackingLongTasks();
- }
- if (options._experiments.enableInteractions) {
- index.startTrackingInteractions();
- }
- let latestRouteName;
- let latestRouteSource;
- /** Create routing idle transaction. */
- function _createRouteTransaction(context) {
- // eslint-disable-next-line deprecation/deprecation
- const hub = core.getCurrentHub();
- const { beforeStartSpan, idleTimeout, finalTimeout, heartbeatInterval } = options;
- const isPageloadTransaction = context.op === 'pageload';
- let expandedContext;
- if (isPageloadTransaction) {
- const sentryTrace = isPageloadTransaction ? getMetaContent('sentry-trace') : '';
- const baggage = isPageloadTransaction ? getMetaContent('baggage') : undefined;
- const { traceId, dsc, parentSpanId, sampled } = utils.propagationContextFromHeaders(sentryTrace, baggage);
- expandedContext = {
- traceId,
- parentSpanId,
- parentSampled: sampled,
- ...context,
- metadata: {
- // eslint-disable-next-line deprecation/deprecation
- ...context.metadata,
- dynamicSamplingContext: dsc,
- },
- trimEnd: true,
- };
- } else {
- expandedContext = {
- trimEnd: true,
- ...context,
- };
- }
- const finalContext = beforeStartSpan ? beforeStartSpan(expandedContext) : expandedContext;
- // If `beforeStartSpan` set a custom name, record that fact
- // eslint-disable-next-line deprecation/deprecation
- finalContext.metadata =
- finalContext.name !== expandedContext.name
- ? // eslint-disable-next-line deprecation/deprecation
- { ...finalContext.metadata, source: 'custom' }
- : // eslint-disable-next-line deprecation/deprecation
- finalContext.metadata;
- latestRouteName = finalContext.name;
- latestRouteSource = getSource(finalContext);
- if (finalContext.sampled === false) {
- debugBuild.DEBUG_BUILD && utils.logger.log(`[Tracing] Will not send ${finalContext.op} transaction because of beforeNavigate.`);
- }
- debugBuild.DEBUG_BUILD && utils.logger.log(`[Tracing] Starting ${finalContext.op} transaction on scope`);
- const { location } = types.WINDOW;
- const idleTransaction = core.startIdleTransaction(
- hub,
- finalContext,
- idleTimeout,
- finalTimeout,
- true,
- { location }, // for use in the tracesSampler
- heartbeatInterval,
- isPageloadTransaction, // should wait for finish signal if it's a pageload transaction
- );
- if (isPageloadTransaction) {
- types.WINDOW.document.addEventListener('readystatechange', () => {
- if (['interactive', 'complete'].includes(types.WINDOW.document.readyState)) {
- idleTransaction.sendAutoFinishSignal();
- }
- });
- if (['interactive', 'complete'].includes(types.WINDOW.document.readyState)) {
- idleTransaction.sendAutoFinishSignal();
- }
- }
- idleTransaction.registerBeforeFinishCallback(transaction => {
- _collectWebVitals();
- index.addPerformanceEntries(transaction);
- });
- return idleTransaction ;
- }
- return {
- name: BROWSER_TRACING_INTEGRATION_ID,
- // eslint-disable-next-line @typescript-eslint/no-empty-function
- setupOnce: () => {},
- afterAllSetup(client) {
- const clientOptions = client.getOptions();
- const { markBackgroundSpan, traceFetch, traceXHR, shouldCreateSpanForRequest, enableHTTPTimings, _experiments } =
- 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.
- // eslint-disable-next-line deprecation/deprecation
- const tracePropagationTargets = clientOptionsTracePropagationTargets || options.tracePropagationTargets;
- if (debugBuild.DEBUG_BUILD && _hasSetTracePropagationTargets && clientOptionsTracePropagationTargets) {
- utils.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.',
- );
- }
- let activeSpan;
- let startingUrl = types.WINDOW.location.href;
- if (client.on) {
- client.on('startNavigationSpan', (context) => {
- if (activeSpan) {
- debugBuild.DEBUG_BUILD && utils.logger.log(`[Tracing] Finishing current transaction with op: ${core.spanToJSON(activeSpan).op}`);
- // If there's an open transaction on the scope, we need to finish it before creating an new one.
- activeSpan.end();
- }
- activeSpan = _createRouteTransaction({
- op: 'navigation',
- ...context,
- });
- });
- client.on('startPageLoadSpan', (context) => {
- if (activeSpan) {
- debugBuild.DEBUG_BUILD && utils.logger.log(`[Tracing] Finishing current transaction with op: ${core.spanToJSON(activeSpan).op}`);
- // If there's an open transaction on the scope, we need to finish it before creating an new one.
- activeSpan.end();
- }
- activeSpan = _createRouteTransaction({
- op: 'pageload',
- ...context,
- });
- });
- }
- if (options.instrumentPageLoad && client.emit) {
- const context = {
- name: types.WINDOW.location.pathname,
- // pageload should always start at timeOrigin (and needs to be in s, not ms)
- startTimestamp: utils.browserPerformanceTimeOrigin ? utils.browserPerformanceTimeOrigin / 1000 : undefined,
- origin: 'auto.pageload.browser',
- attributes: {
- [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
- },
- };
- startBrowserTracingPageLoadSpan(client, context);
- }
- if (options.instrumentNavigation && client.emit) {
- utils.addHistoryInstrumentationHandler(({ 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;
- const context = {
- name: types.WINDOW.location.pathname,
- origin: 'auto.navigation.browser',
- attributes: {
- [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
- },
- };
- startBrowserTracingNavigationSpan(client, context);
- }
- });
- }
- if (markBackgroundSpan) {
- backgroundtab.registerBackgroundTabDetection();
- }
- if (_experiments.enableInteractions) {
- registerInteractionListener(options, latestRouteName, latestRouteSource);
- }
- request.instrumentOutgoingRequests({
- traceFetch,
- traceXHR,
- tracePropagationTargets,
- shouldCreateSpanForRequest,
- enableHTTPTimings,
- });
- },
- // TODO v8: Remove this again
- // This is private API that we use to fix converted BrowserTracing integrations in Next.js & SvelteKit
- options,
- };
- }) ;
- /**
- * Manually start a page load span.
- * This will only do something if the BrowserTracing integration has been setup.
- */
- function startBrowserTracingPageLoadSpan(client, spanOptions) {
- if (!client.emit) {
- return;
- }
- client.emit('startPageLoadSpan', spanOptions);
- const span = core.getActiveSpan();
- const op = span && core.spanToJSON(span).op;
- return op === 'pageload' ? span : undefined;
- }
- /**
- * Manually start a navigation span.
- * This will only do something if the BrowserTracing integration has been setup.
- */
- function startBrowserTracingNavigationSpan(client, spanOptions) {
- if (!client.emit) {
- return;
- }
- client.emit('startNavigationSpan', spanOptions);
- const span = core.getActiveSpan();
- const op = span && core.spanToJSON(span).op;
- return op === 'navigation' ? span : undefined;
- }
- /** 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 = utils.getDomElement(`meta[name=${metaName}]`);
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- return metaTag ? metaTag.getAttribute('content') : undefined;
- }
- /** Start listener for interaction transactions */
- function registerInteractionListener(
- options,
- latestRouteName,
- latestRouteSource,
- ) {
- let inflightInteractionTransaction;
- const registerInteractionTransaction = () => {
- const { idleTimeout, finalTimeout, heartbeatInterval } = options;
- const op = 'ui.action.click';
- // eslint-disable-next-line deprecation/deprecation
- const currentTransaction = core.getActiveTransaction();
- if (currentTransaction && currentTransaction.op && ['navigation', 'pageload'].includes(currentTransaction.op)) {
- debugBuild.DEBUG_BUILD &&
- utils.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.end();
- inflightInteractionTransaction = undefined;
- }
- if (!latestRouteName) {
- debugBuild.DEBUG_BUILD && utils.logger.warn(`[Tracing] Did not create ${op} transaction because _latestRouteName is missing.`);
- return undefined;
- }
- const { location } = types.WINDOW;
- const context = {
- name: latestRouteName,
- op,
- trimEnd: true,
- data: {
- [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: latestRouteSource || 'url',
- },
- };
- inflightInteractionTransaction = core.startIdleTransaction(
- // eslint-disable-next-line deprecation/deprecation
- core.getCurrentHub(),
- context,
- idleTimeout,
- finalTimeout,
- true,
- { location }, // for use in the tracesSampler
- heartbeatInterval,
- );
- };
- ['click'].forEach(type => {
- addEventListener(type, registerInteractionTransaction, { once: false, capture: true });
- });
- }
- function getSource(context) {
- const sourceFromAttributes = context.attributes && context.attributes[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
- // eslint-disable-next-line deprecation/deprecation
- const sourceFromData = context.data && context.data[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
- // eslint-disable-next-line deprecation/deprecation
- const sourceFromMetadata = context.metadata && context.metadata.source;
- return sourceFromAttributes || sourceFromData || sourceFromMetadata;
- }
- exports.BROWSER_TRACING_INTEGRATION_ID = BROWSER_TRACING_INTEGRATION_ID;
- exports.browserTracingIntegration = browserTracingIntegration;
- exports.getMetaContent = getMetaContent;
- exports.startBrowserTracingNavigationSpan = startBrowserTracingNavigationSpan;
- exports.startBrowserTracingPageLoadSpan = startBrowserTracingPageLoadSpan;
- //# sourceMappingURL=browserTracingIntegration.js.map
|