@sentry/browser-utils 11.1.0 → 11.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/index.js +7 -0
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/instrumentation/dom.js +25 -9
- package/build/cjs/instrumentation/dom.js.map +1 -1
- package/build/cjs/routing.js +76 -0
- package/build/cjs/routing.js.map +1 -0
- package/build/cjs/web-vitals/emitSpan.js +1 -1
- package/build/cjs/web-vitals/emitSpan.js.map +1 -1
- package/build/cjs/web-vitals/spans.js +1 -1
- package/build/cjs/web-vitals/spans.js.map +1 -1
- package/build/esm/index.js +1 -0
- package/build/esm/index.js.map +1 -1
- package/build/esm/instrumentation/dom.js +25 -9
- package/build/esm/instrumentation/dom.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/build/esm/routing.js +69 -0
- package/build/esm/routing.js.map +1 -0
- package/build/esm/web-vitals/emitSpan.js +3 -3
- package/build/esm/web-vitals/emitSpan.js.map +1 -1
- package/build/esm/web-vitals/spans.js +3 -3
- package/build/esm/web-vitals/spans.js.map +1 -1
- package/build/types/index.d.ts +2 -0
- package/build/types/index.d.ts.map +1 -1
- package/build/types/instrumentation/dom.d.ts.map +1 -1
- package/build/types/routing.d.ts +87 -0
- package/build/types/routing.d.ts.map +1 -0
- package/build/types/web-vitals/spans.d.ts.map +1 -1
- package/package.json +3 -3
package/build/cjs/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const elementTiming = require('./performance/elementTiming.js');
|
|
|
8
8
|
const interactions = require('./performance/interactions.js');
|
|
9
9
|
const isBotUserAgent = require('./isBotUserAgent.js');
|
|
10
10
|
const getLocationHref = require('./getLocationHref.js');
|
|
11
|
+
const routing = require('./routing.js');
|
|
11
12
|
const userTiming = require('./performance/userTiming.js');
|
|
12
13
|
const utils = require('./performance/utils.js');
|
|
13
14
|
const spans = require('./web-vitals/spans.js');
|
|
@@ -46,6 +47,12 @@ exports.startTrackingElementTiming = elementTiming.startTrackingElementTiming;
|
|
|
46
47
|
exports.interactionsIntegration = interactions.interactionsIntegration;
|
|
47
48
|
exports.isBotUserAgent = isBotUserAgent.isBotUserAgent;
|
|
48
49
|
exports.getLocationHref = getLocationHref.getLocationHref;
|
|
50
|
+
exports.createCachedRouteProvider = routing.createCachedRouteProvider;
|
|
51
|
+
exports.createUrlRouteProvider = routing.createUrlRouteProvider;
|
|
52
|
+
exports.getRouteProvider = routing.getRouteProvider;
|
|
53
|
+
exports.resolveCurrentRoute = routing.resolveCurrentRoute;
|
|
54
|
+
exports.resolveRoute = routing.resolveRoute;
|
|
55
|
+
exports.setRouteProvider = routing.setRouteProvider;
|
|
49
56
|
exports.userTimingIntegration = userTiming.userTimingIntegration;
|
|
50
57
|
exports.extractNetworkProtocol = utils.extractNetworkProtocol;
|
|
51
58
|
exports.trackClsAsSpan = spans.trackClsAsSpan;
|
package/build/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -7,6 +7,9 @@ const DEBOUNCE_DURATION = 1e3;
|
|
|
7
7
|
let debounceTimerID;
|
|
8
8
|
let lastCapturedEventType;
|
|
9
9
|
let lastCapturedEventTargetId;
|
|
10
|
+
function getCapture(options) {
|
|
11
|
+
return typeof options === "boolean" ? options : !!options?.capture;
|
|
12
|
+
}
|
|
10
13
|
function addClickKeypressInstrumentationHandler(handler) {
|
|
11
14
|
const type = "dom";
|
|
12
15
|
core.addHandler(type, handler);
|
|
@@ -28,16 +31,30 @@ function instrumentDOM() {
|
|
|
28
31
|
}
|
|
29
32
|
core.fill(proto, "addEventListener", function(originalAddEventListener) {
|
|
30
33
|
return function(type, listener, options) {
|
|
31
|
-
if (type === "click" || type == "keypress") {
|
|
34
|
+
if ((type === "click" || type == "keypress") && listener) {
|
|
32
35
|
try {
|
|
33
36
|
const handlers = this.__sentry_instrumentation_handlers__ = this.__sentry_instrumentation_handlers__ || {};
|
|
34
|
-
const handlerForType = handlers[type] = handlers[type] || {
|
|
37
|
+
const handlerForType = handlers[type] = handlers[type] || {
|
|
38
|
+
captureListeners: /* @__PURE__ */ new WeakSet(),
|
|
39
|
+
bubbleListeners: /* @__PURE__ */ new WeakSet(),
|
|
40
|
+
listenerCount: 0
|
|
41
|
+
};
|
|
42
|
+
const capture = getCapture(options);
|
|
35
43
|
if (!handlerForType.handler) {
|
|
36
44
|
const handler = makeDOMEventHandler(triggerDOMHandler);
|
|
37
45
|
handlerForType.handler = handler;
|
|
38
|
-
|
|
46
|
+
handlerForType.capture = capture;
|
|
47
|
+
originalAddEventListener.call(this, type, handler, handlerForType.capture);
|
|
48
|
+
}
|
|
49
|
+
const listeners = handlerForType[capture ? "captureListeners" : "bubbleListeners"];
|
|
50
|
+
if (!listeners.has(listener)) {
|
|
51
|
+
if (typeof options === "object" && (options?.once || options?.signal)) {
|
|
52
|
+
handlerForType.sticky = true;
|
|
53
|
+
} else {
|
|
54
|
+
listeners.add(listener);
|
|
55
|
+
handlerForType.listenerCount++;
|
|
56
|
+
}
|
|
39
57
|
}
|
|
40
|
-
handlerForType.refCount++;
|
|
41
58
|
} catch {
|
|
42
59
|
}
|
|
43
60
|
}
|
|
@@ -49,14 +66,13 @@ function instrumentDOM() {
|
|
|
49
66
|
"removeEventListener",
|
|
50
67
|
function(originalRemoveEventListener) {
|
|
51
68
|
return function(type, listener, options) {
|
|
52
|
-
if (type === "click" || type == "keypress") {
|
|
69
|
+
if ((type === "click" || type == "keypress") && listener) {
|
|
53
70
|
try {
|
|
54
71
|
const handlers = this.__sentry_instrumentation_handlers__ || {};
|
|
55
72
|
const handlerForType = handlers[type];
|
|
56
|
-
if (handlerForType) {
|
|
57
|
-
handlerForType.
|
|
58
|
-
|
|
59
|
-
originalRemoveEventListener.call(this, type, handlerForType.handler, options);
|
|
73
|
+
if (handlerForType?.[getCapture(options) ? "captureListeners" : "bubbleListeners"].delete(listener)) {
|
|
74
|
+
if (!--handlerForType.listenerCount && !handlerForType.sticky) {
|
|
75
|
+
originalRemoveEventListener.call(this, type, handlerForType.handler, handlerForType.capture);
|
|
60
76
|
handlerForType.handler = void 0;
|
|
61
77
|
delete handlers[type];
|
|
62
78
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom.js","sources":["../../../src/instrumentation/dom.ts"],"sourcesContent":["import { addHandler, addNonEnumerableProperty, fill, maybeInstrument, triggerHandlers, uuid4 } from '@sentry/core';\nimport type { HandlerDataDom } from '../types';\nimport { WINDOW } from '../types';\n\ntype SentryWrappedTarget = HTMLElement & { _sentryId?: string };\n\ntype AddEventListener = (\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n) => void;\ntype RemoveEventListener = (\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n) => void;\n\ntype InstrumentedElement = Element & {\n __sentry_instrumentation_handlers__?: {\n [key in 'click' | 'keypress']?: {\n handler?: unknown;\n /** The number of custom listeners attached to this element */\n refCount: number;\n };\n };\n};\n\nconst DEBOUNCE_DURATION = 1000;\n\nlet debounceTimerID: number | undefined;\nlet lastCapturedEventType: string | undefined;\nlet lastCapturedEventTargetId: string | undefined;\n\n/**\n * Add an instrumentation handler for when a click or a keypress happens.\n *\n * Use at your own risk, this might break without changelog notice, only used internally.\n * @hidden\n */\nexport function addClickKeypressInstrumentationHandler(handler: (data: HandlerDataDom) => void): void {\n const type = 'dom';\n addHandler(type, handler);\n maybeInstrument(type, instrumentDOM);\n}\n\n/** Exported for tests only. */\nexport function instrumentDOM(): void {\n if (!WINDOW.document) {\n return;\n }\n\n // Make it so that any click or keypress that is unhandled / bubbled up all the way to the document triggers our dom\n // handlers. (Normally we have only one, which captures a breadcrumb for each click or keypress.) Do this before\n // we instrument `addEventListener` so that we don't end up attaching this handler twice.\n const triggerDOMHandler = triggerHandlers.bind(null, 'dom');\n const globalDOMEventHandler = makeDOMEventHandler(triggerDOMHandler, true);\n WINDOW.document.addEventListener('click', globalDOMEventHandler, false);\n WINDOW.document.addEventListener('keypress', globalDOMEventHandler, false);\n\n // After hooking into click and keypress events bubbled up to `document`, we also hook into user-handled\n // clicks & keypresses, by adding an event listener of our own to any element to which they add a listener. That\n // way, whenever one of their handlers is triggered, ours will be, too. (This is needed because their handler\n // could potentially prevent the event from bubbling up to our global listeners. This way, our handler are still\n // guaranteed to fire at least once.)\n ['EventTarget', 'Node'].forEach((target: string) => {\n const globalObject = WINDOW as unknown as Record<string, { prototype?: object }>;\n const proto = globalObject[target]?.prototype;\n\n // eslint-disable-next-line no-prototype-builtins\n if (!proto?.hasOwnProperty?.('addEventListener')) {\n return;\n }\n\n fill(proto, 'addEventListener', function (originalAddEventListener: AddEventListener): AddEventListener {\n return function (this: InstrumentedElement, type, listener, options): AddEventListener {\n if (type === 'click' || type == 'keypress') {\n try {\n const handlers = (this.__sentry_instrumentation_handlers__ =\n this.__sentry_instrumentation_handlers__ || {});\n const handlerForType = (handlers[type] = handlers[type] || { refCount: 0 });\n\n if (!handlerForType.handler) {\n const handler = makeDOMEventHandler(triggerDOMHandler);\n handlerForType.handler = handler;\n originalAddEventListener.call(this, type, handler, options);\n }\n\n handlerForType.refCount++;\n } catch {\n // Accessing dom properties is always fragile.\n // Also allows us to skip `addEventListeners` calls with no proper `this` context.\n }\n }\n\n return originalAddEventListener.call(this, type, listener, options);\n };\n });\n\n fill(\n proto,\n 'removeEventListener',\n function (originalRemoveEventListener: RemoveEventListener): RemoveEventListener {\n return function (this: InstrumentedElement, type, listener, options): () => void {\n if (type === 'click' || type == 'keypress') {\n try {\n const handlers = this.__sentry_instrumentation_handlers__ || {};\n const handlerForType = handlers[type];\n\n if (handlerForType) {\n handlerForType.refCount--;\n // If there are no longer any custom handlers of the current type on this element, we can remove ours, too.\n if (handlerForType.refCount <= 0) {\n originalRemoveEventListener.call(this, type, handlerForType.handler, options);\n handlerForType.handler = undefined;\n delete handlers[type]; // eslint-disable-line @typescript-eslint/no-dynamic-delete\n }\n\n // If there are no longer any custom handlers of any type on this element, cleanup everything.\n if (Object.keys(handlers).length === 0) {\n delete this.__sentry_instrumentation_handlers__;\n }\n }\n } catch {\n // Accessing dom properties is always fragile.\n // Also allows us to skip `addEventListeners` calls with no proper `this` context.\n }\n }\n\n return originalRemoveEventListener.call(this, type, listener, options);\n };\n },\n );\n });\n}\n\n/**\n * Check whether the event is similar to the last captured one. For example, two click events on the same button.\n */\nfunction isSimilarToLastCapturedEvent(event: Event): boolean {\n // If both events have different type, then user definitely performed two separate actions. e.g. click + keypress.\n if (event.type !== lastCapturedEventType) {\n return false;\n }\n\n try {\n // If both events have the same type, it's still possible that actions were performed on different targets.\n // e.g. 2 clicks on different buttons.\n if (!event.target || (event.target as SentryWrappedTarget)._sentryId !== lastCapturedEventTargetId) {\n return false;\n }\n } catch {\n // just accessing `target` property can throw an exception in some rare circumstances\n // see: https://github.com/getsentry/sentry-javascript/issues/838\n }\n\n // If both events have the same type _and_ same `target` (an element which triggered an event, _not necessarily_\n // to which an event listener was attached), we treat them as the same action, as we want to capture\n // only one breadcrumb. e.g. multiple clicks on the same button, or typing inside a user input box.\n return true;\n}\n\n/**\n * Decide whether an event should be captured.\n * @param event event to be captured\n */\nfunction shouldSkipDOMEvent(eventType: string, target: SentryWrappedTarget | null): boolean {\n // We are only interested in filtering `keypress` events for now.\n if (eventType !== 'keypress') {\n return false;\n }\n\n if (!target?.tagName) {\n return true;\n }\n\n // Only consider keypress events on actual input elements. This will disregard keypresses targeting body\n // e.g.tabbing through elements, hotkeys, etc.\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Wraps addEventListener to capture UI breadcrumbs\n */\nfunction makeDOMEventHandler(\n handler: (data: HandlerDataDom) => void,\n globalListener: boolean = false,\n): (event: Event) => void {\n return (event: Event & { _sentryCaptured?: true }): void => {\n // It's possible this handler might trigger multiple times for the same\n // event (e.g. event propagation through node ancestors).\n // Ignore if we've already captured that event.\n if (!event || event['_sentryCaptured']) {\n return;\n }\n\n const target = getEventTarget(event);\n\n // We always want to skip _some_ events.\n if (shouldSkipDOMEvent(event.type, target)) {\n return;\n }\n\n // Mark event as \"seen\"\n addNonEnumerableProperty(event, '_sentryCaptured', true);\n\n if (target && !target._sentryId) {\n // Add UUID to event target so we can identify if\n addNonEnumerableProperty(target, '_sentryId', uuid4());\n }\n\n const name = event.type === 'keypress' ? 'input' : event.type;\n\n // If there is no last captured event, it means that we can safely capture the new event and store it for future comparisons.\n // If there is a last captured event, see if the new event is different enough to treat it as a unique one.\n // If that's the case, emit the previous event and store locally the newly-captured DOM event.\n if (!isSimilarToLastCapturedEvent(event)) {\n const handlerData: HandlerDataDom = { event, name, global: globalListener };\n handler(handlerData);\n lastCapturedEventType = event.type;\n lastCapturedEventTargetId = target ? target._sentryId : undefined;\n }\n\n // Start a new debounce timer that will prevent us from capturing multiple events that should be grouped together.\n clearTimeout(debounceTimerID);\n debounceTimerID = WINDOW.setTimeout(() => {\n lastCapturedEventTargetId = undefined;\n lastCapturedEventType = undefined;\n }, DEBOUNCE_DURATION);\n };\n}\n\nfunction getEventTarget(event: Event): SentryWrappedTarget | null {\n try {\n return event.target as SentryWrappedTarget | null;\n } catch {\n // just accessing `target` property can throw an exception in some rare circumstances\n // see: https://github.com/getsentry/sentry-javascript/issues/838\n return null;\n }\n}\n"],"names":["addHandler","maybeInstrument","WINDOW","triggerHandlers","fill","addNonEnumerableProperty","uuid4"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dom.js","sources":["../../../src/instrumentation/dom.ts"],"sourcesContent":["import { addHandler, addNonEnumerableProperty, fill, maybeInstrument, triggerHandlers, uuid4 } from '@sentry/core';\nimport type { HandlerDataDom } from '../types';\nimport { WINDOW } from '../types';\n\ntype SentryWrappedTarget = HTMLElement & { _sentryId?: string };\n\ntype AddEventListener = (\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n) => void;\ntype RemoveEventListener = (\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n) => void;\n\ntype InstrumentedElement = Element & {\n __sentry_instrumentation_handlers__?: {\n [key in 'click' | 'keypress']?: {\n handler?: unknown;\n capture?: boolean;\n // listeners added with `capture: true`, meaning the listener is invoked before other\n // listeners inside the element's hierarchy.\n captureListeners: WeakSet<EventListenerOrEventListenerObject>;\n // listeners added with `capture: false` (default), meaning the listener is invoked\n // after other listeners inside the element's hierarchy (i.e. the event bubbles up)\n bubbleListeners: WeakSet<EventListenerOrEventListenerObject>;\n // Total number of listeners in `captureListeners` and `bubbleListeners`. WeakSets have no `size`, but we use them\n // so listeners removed without going through our `removeEventListener` patch aren't retained by us.\n listenerCount: number;\n // Set once a `once` or `signal` listener was added. The browser removes those without going\n // through `removeEventListener`, so we can't tell when they're gone and must keep our handler attached.\n sticky?: boolean;\n };\n };\n};\n\nconst DEBOUNCE_DURATION = 1000;\n\nlet debounceTimerID: number | undefined;\nlet lastCapturedEventType: string | undefined;\nlet lastCapturedEventTargetId: string | undefined;\n\nfunction getCapture(options: boolean | EventListenerOptions | undefined): boolean {\n return typeof options === 'boolean' ? options : !!options?.capture;\n}\n\n/**\n * Add an instrumentation handler for when a click or a keypress happens.\n *\n * Use at your own risk, this might break without changelog notice, only used internally.\n * @hidden\n */\nexport function addClickKeypressInstrumentationHandler(handler: (data: HandlerDataDom) => void): void {\n const type = 'dom';\n addHandler(type, handler);\n maybeInstrument(type, instrumentDOM);\n}\n\n/** Exported for tests only. */\nexport function instrumentDOM(): void {\n if (!WINDOW.document) {\n return;\n }\n\n // Make it so that any click or keypress that is unhandled / bubbled up all the way to the document triggers our dom\n // handlers. (Normally we have only one, which captures a breadcrumb for each click or keypress.) Do this before\n // we instrument `addEventListener` so that we don't end up attaching this handler twice.\n const triggerDOMHandler = triggerHandlers.bind(null, 'dom');\n const globalDOMEventHandler = makeDOMEventHandler(triggerDOMHandler, true);\n WINDOW.document.addEventListener('click', globalDOMEventHandler, false);\n WINDOW.document.addEventListener('keypress', globalDOMEventHandler, false);\n\n // After hooking into click and keypress events bubbled up to `document`, we also hook into user-handled\n // clicks & keypresses, by adding an event listener of our own to any element to which they add a listener. That\n // way, whenever one of their handlers is triggered, ours will be, too. (This is needed because their handler\n // could potentially prevent the event from bubbling up to our global listeners. This way, our handler are still\n // guaranteed to fire at least once.)\n ['EventTarget', 'Node'].forEach((target: string) => {\n const globalObject = WINDOW as unknown as Record<string, { prototype?: object }>;\n const proto = globalObject[target]?.prototype;\n\n // eslint-disable-next-line no-prototype-builtins\n if (!proto?.hasOwnProperty?.('addEventListener')) {\n return;\n }\n\n fill(proto, 'addEventListener', function (originalAddEventListener: AddEventListener): AddEventListener {\n return function (this: InstrumentedElement, type, listener, options): AddEventListener {\n // The browser ignores `null` listeners, so there's nothing for our handler to accompany.\n if ((type === 'click' || type == 'keypress') && listener) {\n try {\n const handlers = (this.__sentry_instrumentation_handlers__ =\n this.__sentry_instrumentation_handlers__ || {});\n\n const handlerForType = (handlers[type] = handlers[type] || {\n captureListeners: new WeakSet(),\n bubbleListeners: new WeakSet(),\n listenerCount: 0,\n });\n\n const capture = getCapture(options);\n\n if (!handlerForType.handler) {\n const handler = makeDOMEventHandler(triggerDOMHandler);\n handlerForType.handler = handler;\n // Track the user-set `capture` option because it changes the identity of the registration of the\n // event listener callback function (addEL(fn, true) vs addEL(fn, false) are two different registrations).\n // Our listener needs to have the same capture setting, so that subsequent calls or removeEventListener\n // calls correspond to the correct handler function.\n handlerForType.capture = capture;\n originalAddEventListener.call(this, type, handler, handlerForType.capture);\n }\n\n const listeners = handlerForType[capture ? 'captureListeners' : 'bubbleListeners'];\n // Adding the same listener twice in the same phase is a no-op in the browser.\n if (!listeners.has(listener)) {\n if (typeof options === 'object' && (options?.once || options?.signal)) {\n // Not tracked to avoid retaining listeners the browser auto-removes.\n handlerForType.sticky = true;\n } else {\n listeners.add(listener);\n handlerForType.listenerCount++;\n }\n }\n } catch {\n // Accessing dom properties is always fragile.\n // Also allows us to skip `addEventListeners` calls with no proper `this` context.\n }\n }\n\n return originalAddEventListener.call(this, type, listener, options);\n };\n });\n\n fill(\n proto,\n 'removeEventListener',\n function (originalRemoveEventListener: RemoveEventListener): RemoveEventListener {\n return function (this: InstrumentedElement, type, listener, options): () => void {\n if ((type === 'click' || type == 'keypress') && listener) {\n try {\n const handlers = this.__sentry_instrumentation_handlers__ || {};\n const handlerForType = handlers[type];\n\n // Removing a listener that was never added is a no-op in the browser, so it mustn't count for ours either.\n if (handlerForType?.[getCapture(options) ? 'captureListeners' : 'bubbleListeners'].delete(listener)) {\n // If there are no longer any custom handlers of the current type on this element, we can remove ours, too.\n if (!--handlerForType.listenerCount && !handlerForType.sticky) {\n originalRemoveEventListener.call(this, type, handlerForType.handler, handlerForType.capture);\n handlerForType.handler = undefined;\n delete handlers[type]; // eslint-disable-line @typescript-eslint/no-dynamic-delete\n }\n\n // If there are no longer any custom handlers of any type on this element, cleanup everything.\n if (Object.keys(handlers).length === 0) {\n delete this.__sentry_instrumentation_handlers__;\n }\n }\n } catch {\n // Accessing dom properties is always fragile.\n // Also allows us to skip `addEventListeners` calls with no proper `this` context.\n }\n }\n\n return originalRemoveEventListener.call(this, type, listener, options);\n };\n },\n );\n });\n}\n\n/**\n * Check whether the event is similar to the last captured one. For example, two click events on the same button.\n */\nfunction isSimilarToLastCapturedEvent(event: Event): boolean {\n // If both events have different type, then user definitely performed two separate actions. e.g. click + keypress.\n if (event.type !== lastCapturedEventType) {\n return false;\n }\n\n try {\n // If both events have the same type, it's still possible that actions were performed on different targets.\n // e.g. 2 clicks on different buttons.\n if (!event.target || (event.target as SentryWrappedTarget)._sentryId !== lastCapturedEventTargetId) {\n return false;\n }\n } catch {\n // just accessing `target` property can throw an exception in some rare circumstances\n // see: https://github.com/getsentry/sentry-javascript/issues/838\n }\n\n // If both events have the same type _and_ same `target` (an element which triggered an event, _not necessarily_\n // to which an event listener was attached), we treat them as the same action, as we want to capture\n // only one breadcrumb. e.g. multiple clicks on the same button, or typing inside a user input box.\n return true;\n}\n\n/**\n * Decide whether an event should be captured.\n * @param event event to be captured\n */\nfunction shouldSkipDOMEvent(eventType: string, target: SentryWrappedTarget | null): boolean {\n // We are only interested in filtering `keypress` events for now.\n if (eventType !== 'keypress') {\n return false;\n }\n\n if (!target?.tagName) {\n return true;\n }\n\n // Only consider keypress events on actual input elements. This will disregard keypresses targeting body\n // e.g.tabbing through elements, hotkeys, etc.\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Wraps addEventListener to capture UI breadcrumbs\n */\nfunction makeDOMEventHandler(\n handler: (data: HandlerDataDom) => void,\n globalListener: boolean = false,\n): (event: Event) => void {\n return (event: Event & { _sentryCaptured?: true }): void => {\n // It's possible this handler might trigger multiple times for the same\n // event (e.g. event propagation through node ancestors).\n // Ignore if we've already captured that event.\n if (!event || event['_sentryCaptured']) {\n return;\n }\n\n const target = getEventTarget(event);\n\n // We always want to skip _some_ events.\n if (shouldSkipDOMEvent(event.type, target)) {\n return;\n }\n\n // Mark event as \"seen\"\n addNonEnumerableProperty(event, '_sentryCaptured', true);\n\n if (target && !target._sentryId) {\n // Add UUID to event target so we can identify if\n addNonEnumerableProperty(target, '_sentryId', uuid4());\n }\n\n const name = event.type === 'keypress' ? 'input' : event.type;\n\n // If there is no last captured event, it means that we can safely capture the new event and store it for future comparisons.\n // If there is a last captured event, see if the new event is different enough to treat it as a unique one.\n // If that's the case, emit the previous event and store locally the newly-captured DOM event.\n if (!isSimilarToLastCapturedEvent(event)) {\n const handlerData: HandlerDataDom = { event, name, global: globalListener };\n handler(handlerData);\n lastCapturedEventType = event.type;\n lastCapturedEventTargetId = target ? target._sentryId : undefined;\n }\n\n // Start a new debounce timer that will prevent us from capturing multiple events that should be grouped together.\n clearTimeout(debounceTimerID);\n debounceTimerID = WINDOW.setTimeout(() => {\n lastCapturedEventTargetId = undefined;\n lastCapturedEventType = undefined;\n }, DEBOUNCE_DURATION);\n };\n}\n\nfunction getEventTarget(event: Event): SentryWrappedTarget | null {\n try {\n return event.target as SentryWrappedTarget | null;\n } catch {\n // just accessing `target` property can throw an exception in some rare circumstances\n // see: https://github.com/getsentry/sentry-javascript/issues/838\n return null;\n }\n}\n"],"names":["addHandler","maybeInstrument","WINDOW","triggerHandlers","fill","addNonEnumerableProperty","uuid4"],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const core = require('@sentry/core');
|
|
4
|
+
const debugBuild = require('./debug-build.js');
|
|
5
|
+
const getLocationHref = require('./getLocationHref.js');
|
|
6
|
+
|
|
7
|
+
const CLIENT_ROUTE_PROVIDERS = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
function setRouteProvider(provider, client = core.getClient()) {
|
|
9
|
+
if (!client) {
|
|
10
|
+
debugBuild.DEBUG_BUILD && core.debug.warn("Cannot set a route provider without a client.");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (debugBuild.DEBUG_BUILD && getRouteProvider(client)) {
|
|
14
|
+
core.debug.warn(
|
|
15
|
+
"A route provider is already registered for this client and will be replaced. Routes only the previous provider knows about will no longer resolve."
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
CLIENT_ROUTE_PROVIDERS.set(client, provider);
|
|
19
|
+
}
|
|
20
|
+
function getRouteProvider(client = core.getClient()) {
|
|
21
|
+
return client && CLIENT_ROUTE_PROVIDERS.get(client);
|
|
22
|
+
}
|
|
23
|
+
function resolveRoute(url, client = core.getClient()) {
|
|
24
|
+
const provider = getRouteProvider(client);
|
|
25
|
+
if (!provider) {
|
|
26
|
+
return void 0;
|
|
27
|
+
}
|
|
28
|
+
const urlObject = typeof url === "string" ? parseLocation(url) : url;
|
|
29
|
+
if (!urlObject) {
|
|
30
|
+
return void 0;
|
|
31
|
+
}
|
|
32
|
+
return core.safeCallback(
|
|
33
|
+
debugBuild.DEBUG_BUILD ? "Route provider threw while resolving a route:" : "",
|
|
34
|
+
() => provider.resolveRoute(urlObject) || void 0,
|
|
35
|
+
() => void 0
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
function resolveCurrentRoute(client = core.getClient()) {
|
|
39
|
+
const provider = getRouteProvider(client);
|
|
40
|
+
return core.safeCallback(
|
|
41
|
+
debugBuild.DEBUG_BUILD ? "Route provider threw while resolving a route:" : "",
|
|
42
|
+
() => provider?.resolveCurrentRoute() || void 0,
|
|
43
|
+
() => void 0
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
function createUrlRouteProvider(resolveRouteFromUrl) {
|
|
47
|
+
return {
|
|
48
|
+
resolveRoute: resolveRouteFromUrl,
|
|
49
|
+
resolveCurrentRoute: () => {
|
|
50
|
+
const urlObject = parseLocation(getLocationHref.getLocationHref());
|
|
51
|
+
return urlObject && resolveRouteFromUrl(urlObject);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function createCachedRouteProvider(maxEntries = 50) {
|
|
56
|
+
const routeNames = new core.LRUMap(maxEntries);
|
|
57
|
+
return {
|
|
58
|
+
...createUrlRouteProvider((url) => routeNames.get(url.pathname)),
|
|
59
|
+
record(pathname, routeName) {
|
|
60
|
+
if (pathname && routeName) {
|
|
61
|
+
routeNames.set(pathname, routeName);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function parseLocation(url) {
|
|
67
|
+
return url ? core.parseStringToURLObject(url, getLocationHref.getLocationHref() || void 0) : void 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
exports.createCachedRouteProvider = createCachedRouteProvider;
|
|
71
|
+
exports.createUrlRouteProvider = createUrlRouteProvider;
|
|
72
|
+
exports.getRouteProvider = getRouteProvider;
|
|
73
|
+
exports.resolveCurrentRoute = resolveCurrentRoute;
|
|
74
|
+
exports.resolveRoute = resolveRoute;
|
|
75
|
+
exports.setRouteProvider = setRouteProvider;
|
|
76
|
+
//# sourceMappingURL=routing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing.js","sources":["../../src/routing.ts"],"sourcesContent":["import type { Client } from '@sentry/core';\nimport { debug, getClient, LRUMap, parseStringToURLObject, safeCallback } from '@sentry/core';\nimport { DEBUG_BUILD } from './debug-build';\nimport { getLocationHref } from './getLocationHref';\n\n/** The parts of a URL a route depends on. A `URL` satisfies it. */\ntype RouteUrl = Pick<URL, 'pathname' | 'search' | 'hash'>;\n\n/**\n * Resolves URLs to low-cardinality route names.\n *\n * Framework SDKs register one so that everything the SDK names after a route (span names, the scope's\n * transaction name, metric and span segment attributes) gets the parameterized route instead of the raw\n * URL, without each integration having to reach into the framework's router itself.\n */\nexport interface RouteProvider {\n /**\n * Resolves a URL path template for a specific URL, e.g. `/users/42` -> `/users/:id`.\n *\n * Must return a path template, never a route identifier. Routers that name routes independently of\n * their path (Vue Router's `route.name`, Ember's `posts.show`) have to return the matched path\n * instead: callers set `url.template` from this, and an identifier is not a template. An SDK that\n * wants to name its span after the identifier still can, on the span itself.\n *\n * Returns `undefined` when the URL matches no known route. Must answer for the URL it is given rather\n * than for wherever the router currently is, so that callers can resolve a URL they captured earlier\n * (a web vital reported after a soft navigation, for example).\n */\n resolveRoute(url: RouteUrl): string | undefined;\n\n /**\n * Resolves the route the app is currently on.\n *\n * Routers whose location lives in the address bar can delegate to `resolveRoute`, which is what\n * {@link createUrlRouteProvider} does. Routers that keep their own location (memory and hash routers)\n * have to answer from that location instead: for those, `location.href` is the unchanging shell URL\n * and would bucket every route together.\n */\n resolveCurrentRoute(): string | undefined;\n}\n\nconst CLIENT_ROUTE_PROVIDERS = new WeakMap<Client, RouteProvider>();\n\n/**\n * Registers the route provider for a client, replacing any previously registered one. `BrowserClient`\n * calls this with its `routeProvider` option.\n *\n * Prefer the `routeProvider` option where the provider is known at `init`: the pageload span is named\n * while `browserTracingIntegration` sets up, so a provider registered after `init` can only rename it\n * after the fact.\n *\n * A client holds one provider. An app running two routers (a framework migration, or a shell plus an\n * island) registers twice and the last one wins, so the first router's routes stop resolving.\n */\nexport function setRouteProvider(provider: RouteProvider, client: Client | undefined = getClient()): void {\n if (!client) {\n DEBUG_BUILD && debug.warn('Cannot set a route provider without a client.');\n return;\n }\n\n if (DEBUG_BUILD && getRouteProvider(client)) {\n debug.warn(\n 'A route provider is already registered for this client and will be replaced. Routes only the previous provider knows about will no longer resolve.',\n );\n }\n\n CLIENT_ROUTE_PROVIDERS.set(client, provider);\n}\n\n/**\n * Returns the route provider registered for a client.\n */\nexport function getRouteProvider(client: Client | undefined = getClient()): RouteProvider | undefined {\n return client && CLIENT_ROUTE_PROVIDERS.get(client);\n}\n\n/**\n * Resolves a URL to a low-cardinality route name, e.g. `/users/42` -> `/users/:id`.\n *\n * Returns `undefined` when no route provider is registered or the URL matches no route. Callers pick\n * their own fallback, because the right one differs: a span name falls back to a low-cardinality\n * constant, the scope's transaction name to the raw path.\n */\nexport function resolveRoute(url: string | URL, client: Client | undefined = getClient()): string | undefined {\n const provider = getRouteProvider(client);\n if (!provider) {\n return undefined;\n }\n\n const urlObject = typeof url === 'string' ? parseLocation(url) : url;\n if (!urlObject) {\n return undefined;\n }\n\n return safeCallback(\n DEBUG_BUILD ? 'Route provider threw while resolving a route:' : '',\n () => provider.resolveRoute(urlObject) || undefined,\n () => undefined,\n );\n}\n\n/**\n * Resolves the route the app is currently on.\n *\n * Returns `undefined` when no route provider is registered or the current location matches no route.\n */\nexport function resolveCurrentRoute(client: Client | undefined = getClient()): string | undefined {\n const provider = getRouteProvider(client);\n\n return safeCallback(\n DEBUG_BUILD ? 'Route provider threw while resolving a route:' : '',\n () => provider?.resolveCurrentRoute() || undefined,\n () => undefined,\n );\n}\n\n/**\n * Builds a {@link RouteProvider} for a router whose location is the browser's, which covers every\n * router except memory and hash routers.\n */\nexport function createUrlRouteProvider(resolveRouteFromUrl: (url: RouteUrl) => string | undefined): RouteProvider {\n return {\n resolveRoute: resolveRouteFromUrl,\n resolveCurrentRoute: () => {\n const urlObject = parseLocation(getLocationHref());\n\n return urlObject && resolveRouteFromUrl(urlObject);\n },\n };\n}\n\n/**\n * A {@link RouteProvider} that answers from routes it has been told about, rather than by matching.\n */\nexport interface CachedRouteProvider extends RouteProvider {\n /** Records the route name a router reported for a path. Ignores empty values. */\n record(pathname: string | undefined, routeName: string | null | undefined): void;\n}\n\n/**\n * Builds a route provider for a router with no usable matcher, which can only report the route it is\n * on as it gets there (SvelteKit's `page.route.id`, Solid Router's current matches).\n *\n * A URL the app has not visited resolves to `undefined`, which includes the first pageload until the\n * router reports. Backed by an LRU so a long-lived app visiting many URLs can't grow it without end,\n * and so routes that keep being resolved outlive ones passed through once.\n */\nexport function createCachedRouteProvider(maxEntries: number = 50): CachedRouteProvider {\n const routeNames = new LRUMap<string, string>(maxEntries);\n\n return {\n ...createUrlRouteProvider(url => routeNames.get(url.pathname)),\n record(pathname, routeName) {\n if (pathname && routeName) {\n routeNames.set(pathname, routeName);\n }\n },\n };\n}\n\n/**\n * Parses up front so providers never have to, and resolves relative locations (which memory routers\n * hand around) against the document.\n */\nfunction parseLocation(url: string): RouteUrl | undefined {\n // Without a document `getLocationHref()` is empty, which would otherwise parse as `/`.\n return url ? parseStringToURLObject(url, getLocationHref() || undefined) : undefined;\n}\n"],"names":["getClient","DEBUG_BUILD","debug","safeCallback","getLocationHref","LRUMap","parseStringToURLObject"],"mappings":";;;;;;AAyCA,MAAM,sBAAA,uBAA6B,OAAA,EAA+B;AAa3D,SAAS,gBAAA,CAAiB,QAAA,EAAyB,MAAA,GAA6BA,cAAA,EAAU,EAAS;AACxG,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAAC,sBAAA,IAAeC,UAAA,CAAM,KAAK,+CAA+C,CAAA;AACzE,IAAA;AAAA,EACF;AAEA,EAAA,IAAID,sBAAA,IAAe,gBAAA,CAAiB,MAAM,CAAA,EAAG;AAC3C,IAAAC,UAAA,CAAM,IAAA;AAAA,MACJ;AAAA,KACF;AAAA,EACF;AAEA,EAAA,sBAAA,CAAuB,GAAA,CAAI,QAAQ,QAAQ,CAAA;AAC7C;AAKO,SAAS,gBAAA,CAAiB,MAAA,GAA6BF,cAAA,EAAU,EAA8B;AACpG,EAAA,OAAO,MAAA,IAAU,sBAAA,CAAuB,GAAA,CAAI,MAAM,CAAA;AACpD;AASO,SAAS,YAAA,CAAa,GAAA,EAAmB,MAAA,GAA6BA,cAAA,EAAU,EAAuB;AAC5G,EAAA,MAAM,QAAA,GAAW,iBAAiB,MAAM,CAAA;AACxC,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,YAAY,OAAO,GAAA,KAAQ,QAAA,GAAW,aAAA,CAAc,GAAG,CAAA,GAAI,GAAA;AACjE,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,OAAOG,iBAAA;AAAA,IACLF,yBAAc,+CAAA,GAAkD,EAAA;AAAA,IAChE,MAAM,QAAA,CAAS,YAAA,CAAa,SAAS,CAAA,IAAK,MAAA;AAAA,IAC1C,MAAM;AAAA,GACR;AACF;AAOO,SAAS,mBAAA,CAAoB,MAAA,GAA6BD,cAAA,EAAU,EAAuB;AAChG,EAAA,MAAM,QAAA,GAAW,iBAAiB,MAAM,CAAA;AAExC,EAAA,OAAOG,iBAAA;AAAA,IACLF,yBAAc,+CAAA,GAAkD,EAAA;AAAA,IAChE,MAAM,QAAA,EAAU,mBAAA,EAAoB,IAAK,MAAA;AAAA,IACzC,MAAM;AAAA,GACR;AACF;AAMO,SAAS,uBAAuB,mBAAA,EAA2E;AAChH,EAAA,OAAO;AAAA,IACL,YAAA,EAAc,mBAAA;AAAA,IACd,qBAAqB,MAAM;AACzB,MAAA,MAAM,SAAA,GAAY,aAAA,CAAcG,+BAAA,EAAiB,CAAA;AAEjD,MAAA,OAAO,SAAA,IAAa,oBAAoB,SAAS,CAAA;AAAA,IACnD;AAAA,GACF;AACF;AAkBO,SAAS,yBAAA,CAA0B,aAAqB,EAAA,EAAyB;AACtF,EAAA,MAAM,UAAA,GAAa,IAAIC,WAAA,CAAuB,UAAU,CAAA;AAExD,EAAA,OAAO;AAAA,IACL,GAAG,sBAAA,CAAuB,CAAA,GAAA,KAAO,WAAW,GAAA,CAAI,GAAA,CAAI,QAAQ,CAAC,CAAA;AAAA,IAC7D,MAAA,CAAO,UAAU,SAAA,EAAW;AAC1B,MAAA,IAAI,YAAY,SAAA,EAAW;AACzB,QAAA,UAAA,CAAW,GAAA,CAAI,UAAU,SAAS,CAAA;AAAA,MACpC;AAAA,IACF;AAAA,GACF;AACF;AAMA,SAAS,cAAc,GAAA,EAAmC;AAExD,EAAA,OAAO,MAAMC,2BAAA,CAAuB,GAAA,EAAKF,+BAAA,EAAgB,IAAK,MAAS,CAAA,GAAI,MAAA;AAC7E;;;;;;;;;"}
|
|
@@ -25,7 +25,7 @@ function _emitWebVitalSpan(options) {
|
|
|
25
25
|
const attributes$1 = {
|
|
26
26
|
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: origin,
|
|
27
27
|
[core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
|
|
28
|
-
[
|
|
28
|
+
[attributes.SENTRY_EXCLUSIVE_TIME]: 0,
|
|
29
29
|
[`browser.web_vital.${metricName}.value`]: value,
|
|
30
30
|
// oxlint-disable-next-line typescript-eslint/no-deprecated
|
|
31
31
|
[attributes.SENTRY_TRANSACTION]: segmentName,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emitSpan.js","sources":["../../../src/web-vitals/emitSpan.ts"],"sourcesContent":["import type { Integration, Span, SpanAttributes } from '@sentry/core';\nimport {\n getClient,\n getCurrentScope,\n getRootSpan,\n
|
|
1
|
+
{"version":3,"file":"emitSpan.js","sources":["../../../src/web-vitals/emitSpan.ts"],"sourcesContent":["import type { Integration, Span, SpanAttributes } from '@sentry/core';\nimport {\n getClient,\n getCurrentScope,\n getRootSpan,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n spanToJSON,\n} from '@sentry/core';\nimport { startInactiveSpan } from '@sentry/core/browser';\nimport {\n SENTRY_EXCLUSIVE_TIME,\n BROWSER_NAVIGATION_ID,\n BROWSER_NAVIGATION_TYPE,\n SENTRY_REPLAY_ID,\n SENTRY_SEGMENT_NAME,\n SENTRY_TRANSACTION,\n USER_AGENT_ORIGINAL,\n} from '@sentry/conventions/attributes';\nimport { WINDOW } from '../types';\nimport type { MetricNavigationType } from '../instrumentation/performanceObserver';\n\n// Locally-defined interfaces to avoid leaking bare global type references into the\n// generated .d.ts. The `declare global` augmentations in web-vitals/types.ts make these\n// available during this package's compilation but are NOT carried to consumers.\n// This mirrors the pattern used for PerformanceEventTiming in instrument.ts.\nexport interface LayoutShift extends PerformanceEntry {\n value: number;\n sources: Array<{ node: Node | null }>;\n hadRecentInput: boolean;\n}\n\nexport interface LargestContentfulPaint extends PerformanceEntry {\n readonly renderTime: DOMHighResTimeStamp;\n readonly loadTime: DOMHighResTimeStamp;\n readonly size: number;\n readonly id: string;\n readonly url: string;\n readonly element: Element | null;\n}\n\ninterface WebVitalSpanOptions {\n name: string;\n op: string;\n origin: string;\n metricName: 'lcp' | 'cls' | 'inp';\n value: number;\n attributes?: SpanAttributes;\n parentSpan?: Span;\n startTime: number;\n endTime?: number;\n /** Set when the vital was reported for a soft navigation rather than the initial page load. */\n softNavigationId?: number;\n /** The navigation the vital was measured on, as reported by web-vitals. */\n navigationType?: MetricNavigationType;\n /**\n * When `true`, the span is sent on its own as a v2 streamed span instead of being folded into a\n * transaction. Used for INP when span streaming is disabled (it reports late, so it can't ride\n * the pageload transaction).\n *\n * TODO(standalone): remove once the static (transaction) trace lifecycle is dropped and INP always streams.\n */\n standalone?: boolean;\n}\n\n/**\n * Emits a web vital span. When `standalone` is set it is sent on its own as a v2 streamed span;\n * otherwise it flows through the span streaming pipeline as a child of `parentSpan`.\n */\nexport function _emitWebVitalSpan(options: WebVitalSpanOptions): void {\n const {\n name,\n op,\n origin,\n metricName,\n value,\n attributes: passedAttributes,\n parentSpan,\n startTime,\n endTime,\n standalone,\n softNavigationId,\n navigationType,\n } = options;\n\n // Taken off the segment span itself, so it can't diverge from it: a routing instrumentation may\n // rename that span (a pageload span is named `Pageload` until its route resolves), and the scope's\n // transaction name is deliberately not kept in sync with it. Only a standalone span, which is sent\n // without its segment span, has to fall back to the scope.\n const segmentSpan = parentSpan && getRootSpan(parentSpan);\n const segmentName = segmentSpan ? spanToJSON(segmentSpan).name : getCurrentScope().getScopeData().transactionName;\n\n const attributes: SpanAttributes = {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: origin,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,\n [SENTRY_EXCLUSIVE_TIME]: 0,\n [`browser.web_vital.${metricName}.value`]: value,\n // oxlint-disable-next-line typescript-eslint/no-deprecated\n [SENTRY_TRANSACTION]: segmentName,\n [SENTRY_SEGMENT_NAME]: segmentName,\n // Web vital score calculation relies on the user agent\n [USER_AGENT_ORIGINAL]: WINDOW.navigator?.userAgent,\n ...passedAttributes,\n };\n\n if (parentSpan && spanToJSON(parentSpan).attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] === 'pageload') {\n // for LCP and CLS, we collect the pageload span id as an attribute\n attributes['sentry.pageload.span_id'] = parentSpan.spanContext().spanId;\n }\n\n if (softNavigationId != null) {\n attributes[BROWSER_NAVIGATION_ID] = softNavigationId;\n }\n\n if (navigationType) {\n attributes[BROWSER_NAVIGATION_TYPE] = navigationType;\n }\n\n // A standalone span is sent as a plain v2 span without running the `processSpan` hooks (see\n // `captureStandaloneSpanWithStaticCallback`), so Replay can't attach the replay id itself. Set it\n // here, mirroring Replay's `processSpan`, so INP keeps its replay association like it did on v1.\n // TODO(standalone): remove once the static (transaction) trace lifecycle is dropped and INP always\n // streams, at which point Replay's `processSpan` runs and attaches the replay id.\n if (standalone) {\n Object.assign(attributes, getReplayAttributes());\n }\n\n const span = startInactiveSpan({\n name,\n attributes,\n startTime,\n parentSpan,\n // oxlint-disable-next-line typescript/no-deprecated -- intentional during the v1/v2 transition; see the TODO(standalone) above\n experimental: standalone ? { standalone: true } : undefined,\n });\n\n if (span) {\n span.end(endTime ?? startTime);\n }\n}\n\ninterface ReplayIntegration extends Integration {\n getReplayId: (onlyIfSampled?: boolean) => string | undefined;\n getRecordingMode: () => 'session' | 'buffer' | undefined;\n}\n\n// TODO(standalone): remove once the static (transaction) trace lifecycle is dropped; Replay's\n// `processSpan` then attaches the replay id to the streamed INP span instead.\nfunction getReplayAttributes(): SpanAttributes {\n const replay = getClient()?.getIntegrationByName<ReplayIntegration>('Replay');\n const replayId = replay?.getReplayId(true);\n if (!replayId) {\n return {};\n }\n\n return {\n [SENTRY_REPLAY_ID]: replayId,\n // Not the `SENTRY_REPLAY_IS_BUFFERING` convention: that one has no `sentry._internal.` prefix, and\n // the rest of the SDK sets the prefixed key (see `logs/internal.ts`, `metrics/internal.ts`).\n 'sentry._internal.replay_is_buffering': replay!.getRecordingMode() === 'buffer' ? true : undefined,\n };\n}\n"],"names":["getRootSpan","spanToJSON","getCurrentScope","attributes","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","SEMANTIC_ATTRIBUTE_SENTRY_OP","SENTRY_EXCLUSIVE_TIME","SENTRY_TRANSACTION","SENTRY_SEGMENT_NAME","USER_AGENT_ORIGINAL","WINDOW","BROWSER_NAVIGATION_ID","BROWSER_NAVIGATION_TYPE","startInactiveSpan","getClient","SENTRY_REPLAY_ID"],"mappings":";;;;;;;AAqEO,SAAS,kBAAkB,OAAA,EAAoC;AACpE,EAAA,MAAM;AAAA,IACJ,IAAA;AAAA,IACA,EAAA;AAAA,IACA,MAAA;AAAA,IACA,UAAA;AAAA,IACA,KAAA;AAAA,IACA,UAAA,EAAY,gBAAA;AAAA,IACZ,UAAA;AAAA,IACA,SAAA;AAAA,IACA,OAAA;AAAA,IACA,UAAA;AAAA,IACA,gBAAA;AAAA,IACA;AAAA,GACF,GAAI,OAAA;AAMJ,EAAA,MAAM,WAAA,GAAc,UAAA,IAAcA,gBAAA,CAAY,UAAU,CAAA;AACxD,EAAA,MAAM,WAAA,GAAc,cAAcC,eAAA,CAAW,WAAW,EAAE,IAAA,GAAOC,oBAAA,EAAgB,CAAE,YAAA,EAAa,CAAE,eAAA;AAElG,EAAA,MAAMC,YAAA,GAA6B;AAAA,IACjC,CAACC,qCAAgC,GAAG,MAAA;AAAA,IACpC,CAACC,iCAA4B,GAAG,EAAA;AAAA,IAChC,CAACC,gCAAqB,GAAG,CAAA;AAAA,IACzB,CAAC,CAAA,kBAAA,EAAqB,UAAU,CAAA,MAAA,CAAQ,GAAG,KAAA;AAAA;AAAA,IAE3C,CAACC,6BAAkB,GAAG,WAAA;AAAA,IACtB,CAACC,8BAAmB,GAAG,WAAA;AAAA;AAAA,IAEvB,CAACC,8BAAmB,GAAGC,YAAA,CAAO,SAAA,EAAW,SAAA;AAAA,IACzC,GAAG;AAAA,GACL;AAEA,EAAA,IAAI,cAAcT,eAAA,CAAW,UAAU,EAAE,UAAA,CAAWI,iCAA4B,MAAM,UAAA,EAAY;AAEhG,IAAAF,YAAA,CAAW,yBAAyB,CAAA,GAAI,UAAA,CAAW,WAAA,EAAY,CAAE,MAAA;AAAA,EACnE;AAEA,EAAA,IAAI,oBAAoB,IAAA,EAAM;AAC5B,IAAAA,YAAA,CAAWQ,gCAAqB,CAAA,GAAI,gBAAA;AAAA,EACtC;AAEA,EAAA,IAAI,cAAA,EAAgB;AAClB,IAAAR,YAAA,CAAWS,kCAAuB,CAAA,GAAI,cAAA;AAAA,EACxC;AAOA,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,MAAA,CAAO,MAAA,CAAOT,YAAA,EAAY,mBAAA,EAAqB,CAAA;AAAA,EACjD;AAEA,EAAA,MAAM,OAAOU,yBAAA,CAAkB;AAAA,IAC7B,IAAA;AAAA,gBACAV,YAAA;AAAA,IACA,SAAA;AAAA,IACA,UAAA;AAAA;AAAA,IAEA,YAAA,EAAc,UAAA,GAAa,EAAE,UAAA,EAAY,MAAK,GAAI;AAAA,GACnD,CAAA;AAED,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,IAAA,CAAK,GAAA,CAAI,WAAW,SAAS,CAAA;AAAA,EAC/B;AACF;AASA,SAAS,mBAAA,GAAsC;AAC7C,EAAA,MAAM,MAAA,GAASW,cAAA,EAAU,EAAG,oBAAA,CAAwC,QAAQ,CAAA;AAC5E,EAAA,MAAM,QAAA,GAAW,MAAA,EAAQ,WAAA,CAAY,IAAI,CAAA;AACzC,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,OAAO;AAAA,IACL,CAACC,2BAAgB,GAAG,QAAA;AAAA;AAAA;AAAA,IAGpB,sCAAA,EAAwC,MAAA,CAAQ,gBAAA,EAAiB,KAAM,WAAW,IAAA,GAAO;AAAA,GAC3F;AACF;;;;"}
|
|
@@ -243,7 +243,7 @@ function _sendInpSpan(inpValue, entry, standalone = false, attributedSpan, softN
|
|
|
243
243
|
const fallbackName = INTERACTION_TYPE_TO_SPAN_NAME_FALLBACK[interactionType];
|
|
244
244
|
const name = hasSpanStreaming ? componentName$1 || fallbackName : selector ?? "Interaction to next paint";
|
|
245
245
|
const attributes$1 = {
|
|
246
|
-
[
|
|
246
|
+
[attributes.SENTRY_EXCLUSIVE_TIME]: entry?.duration ?? inpValue
|
|
247
247
|
};
|
|
248
248
|
selector && selector !== inp.UNKNOWN_ELEMENT_NAME && (attributes$1[attributes.BROWSER_WEB_VITAL_INP_TARGET] = selector);
|
|
249
249
|
entryInteractionType && (attributes$1[attributes.BROWSER_WEB_VITAL_INP_INTERACTION_TYPE] = entryInteractionType);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spans.js","sources":["../../../src/web-vitals/spans.ts"],"sourcesContent":["/* eslint-disable max-lines */\nimport type { Client, Span, SpanAttributes } from '@sentry/core';\nimport {\n browserPerformanceTimeOrigin,\n debug,\n getActiveSpan,\n getClient,\n getRootSpan,\n hasSpanStreamingEnabled,\n SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n spanToJSON,\n timestampInSeconds,\n UI_INTERACTION_CLICK_SPAN_NAME_FALLBACK,\n UI_INTERACTION_DRAG_SPAN_NAME_FALLBACK,\n UI_INTERACTION_HOVER_SPAN_NAME_FALLBACK,\n UI_INTERACTION_PRESS_SPAN_NAME_FALLBACK,\n UI_WEBVITAL_CLS_SPAN_NAME_FALLBACK,\n UI_WEBVITAL_LCP_SPAN_NAME_FALLBACK,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\nimport { htmlTreeAsString } from '../htmlTreeAsString';\nimport { getComponentName } from '../component-name';\nimport type { InteractionType } from './inp';\nimport { getCachedInteractionContext, INP_ENTRY_MAP, MAX_PLAUSIBLE_INP_DURATION, UNKNOWN_ELEMENT_NAME } from './inp';\nimport type { InstrumentationHandlerCallback, MetricNavigationType } from '../instrumentation/performanceObserver';\nimport {\n addClsInstrumentationHandler,\n addInpInstrumentationHandler,\n addLcpInstrumentationHandler,\n} from '../instrumentation/performanceObserver';\nimport type { LargestContentfulPaint, LayoutShift } from './emitSpan';\nimport {\n BROWSER_NAVIGATION_TYPE,\n BROWSER_WEB_VITAL_INP_INTERACTION_TYPE,\n BROWSER_WEB_VITAL_INP_TARGET,\n UI_COMPONENT_NAME,\n} from '@sentry/conventions/attributes';\nimport { _emitWebVitalSpan } from './emitSpan';\nimport { isValidLcpMetric } from './lcp';\nimport { listenForWebVitalReportEvents } from './reportEvents';\nimport { getNavigationSpanForMetric } from './softNavs';\nimport { getBrowserPerformanceAPI, msToSec, supportsWebVital } from '../performance/utils';\nimport type { PerformanceEventTiming } from '../instrumentation/performanceObserver';\nimport {\n NAVIGATION,\n UI_INTERACTION_CLICK,\n UI_INTERACTION_DRAG,\n UI_INTERACTION_HOVER,\n UI_INTERACTION_PRESS,\n UI_WEBVITAL_CLS,\n UI_WEBVITAL_LCP,\n} from '@sentry/conventions/op';\n\nconst INTERACTION_TYPE_TO_SPAN_OP: Record<InteractionType, string> = {\n click: UI_INTERACTION_CLICK,\n hover: UI_INTERACTION_HOVER,\n drag: UI_INTERACTION_DRAG,\n press: UI_INTERACTION_PRESS,\n};\n\nconst INTERACTION_TYPE_TO_SPAN_NAME_FALLBACK: Record<InteractionType, string> = {\n click: UI_INTERACTION_CLICK_SPAN_NAME_FALLBACK,\n hover: UI_INTERACTION_HOVER_SPAN_NAME_FALLBACK,\n drag: UI_INTERACTION_DRAG_SPAN_NAME_FALLBACK,\n press: UI_INTERACTION_PRESS_SPAN_NAME_FALLBACK,\n};\n\ntype WebVitalMetric = Parameters<Parameters<typeof addLcpInstrumentationHandler>[0]>[0]['metric'];\ntype InpMetric = Parameters<InstrumentationHandlerCallback>[0]['metric'];\n\n/**\n * Reports a web vital once per navigation, rather than once per page load.\n *\n * web-vitals restarts the metric on every soft navigation and force-reports the previous one just\n * before it does (and again on pagehide), and re-reports every metric after a bfcache restore.\n * Since `reportAllChanges` is off in this mode, every value we're handed is already the final one\n * for its navigation, so there is nothing to accumulate: each report is a span.\n */\nfunction trackWebVitalPerNavigation<M extends WebVitalMetric>(\n client: Client,\n addInstrumentationHandler: (callback: (data: { metric: M }) => void) => unknown,\n send: (metric: M, parentSpan: Span | undefined, softNavigationId: number | undefined) => void,\n): void {\n let pageloadSpan: Span | undefined;\n client.on('afterStartPageLoadSpan', span => {\n pageloadSpan = span;\n });\n\n // Remembered when the restore happens rather than read back at report time: the restore\n // navigation span is an idle span, and CLS and INP are only finalized on pagehide, by which point\n // it has long ended and is no longer what is active.\n let bfcacheNavigationSpan: Span | undefined;\n client.on('spanStart', span => {\n // The op has to be checked too: the web vital spans emitted for a restore carry the same\n // `back-forward-cache` navigation type, so matching on that alone lets the first of them replace the\n // navigation span, and every later vital then hangs off a sibling vital instead.\n const attributes = spanToJSON(span).attributes;\n if (\n attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] === NAVIGATION &&\n attributes[BROWSER_NAVIGATION_TYPE] === 'back-forward-cache'\n ) {\n bfcacheNavigationSpan = span;\n }\n });\n\n addInstrumentationHandler(({ metric }) => {\n const navigationSpan = getNavigationSpanForMetric(metric);\n if (metric.navigationType === 'soft-navigation') {\n // Reporting an uncorrelated soft navigation vital would attribute it to the wrong route, so\n // it's dropped instead.\n if (navigationSpan) {\n send(metric, navigationSpan, metric.navigationId);\n } else {\n DEBUG_BUILD &&\n debug.log(`[SoftNav] Dropping ${metric.name} for uncorrelated soft navigation ${metric.navigationId}`);\n }\n return;\n }\n\n if (metric.navigationType === 'back-forward-cache') {\n // A restore reuses the frozen document, so the pageload span above belongs to the page view\n // from before the freeze. The navigation span started for the restore is the page view these\n // values were actually measured on.\n send(metric, bfcacheNavigationSpan, undefined);\n return;\n }\n\n send(metric, pageloadSpan, undefined);\n });\n}\n\n/**\n * Tracks LCP as a streamed span.\n */\nexport function trackLcpAsSpan(client: Client, perNavigation = false): void {\n if (!supportsWebVital('largest-contentful-paint')) {\n return;\n }\n\n if (perNavigation) {\n trackWebVitalPerNavigation(client, addLcpInstrumentationHandler, (metric, parentSpan, softNavigationId) => {\n const entry = metric.entries[metric.entries.length - 1] as LargestContentfulPaint | undefined;\n _sendLcpSpan(\n metric.value,\n entry,\n parentSpan,\n softNavigationId,\n metric.navigationType,\n metric.navigationStartTime,\n );\n });\n return;\n }\n\n let lcpValue = 0;\n let lcpEntry: LargestContentfulPaint | undefined;\n let lcpNavigationType: MetricNavigationType | undefined;\n\n const cleanupLcpHandler = addLcpInstrumentationHandler(({ metric }) => {\n // The navigation type describes the page, not the entry, so it is worth keeping even for a\n // report we otherwise discard.\n lcpNavigationType = metric.navigationType;\n\n const entry = metric.entries[metric.entries.length - 1] as LargestContentfulPaint | undefined;\n if (!entry || !isValidLcpMetric(metric.value)) {\n return;\n }\n lcpValue = metric.value;\n lcpEntry = entry;\n }, true);\n\n listenForWebVitalReportEvents(client, pageloadSpan => {\n _sendLcpSpan(lcpValue, lcpEntry, pageloadSpan, undefined, lcpNavigationType);\n cleanupLcpHandler();\n });\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendLcpSpan(\n lcpValue: number,\n entry: LargestContentfulPaint | undefined,\n pageloadSpan?: Span,\n softNavigationId?: number,\n navigationType?: MetricNavigationType,\n navigationStartTime?: number,\n): void {\n if (!isValidLcpMetric(lcpValue)) {\n return;\n }\n\n DEBUG_BUILD && debug.log(`Sending LCP span (${lcpValue})`);\n\n const performanceTimeOrigin = browserPerformanceTimeOrigin() || 0;\n // A soft navigation's LCP is measured from the triggering interaction, not the document time\n // origin. Starting the span there too keeps it inside the navigation span it is parented to and\n // keeps its duration equal to the value it reports.\n const startTime = msToSec(performanceTimeOrigin + (navigationStartTime || 0));\n // Without an entry there is no render time to end at, so the span lasts the value it reports,\n // like an entry-less INP does. Ending at the time origin instead would invert the span.\n const endTime = entry ? msToSec(performanceTimeOrigin + entry.startTime) : startTime + msToSec(lcpValue);\n const selector = entry ? htmlTreeAsString(entry.element) : undefined;\n const componentName = entry?.element ? getComponentName(entry.element) : null;\n const client = getClient();\n const hasSpanStreaming = !!client && hasSpanStreamingEnabled(client);\n const name = hasSpanStreaming\n ? componentName || UI_WEBVITAL_LCP_SPAN_NAME_FALLBACK\n : (selector ?? UI_WEBVITAL_LCP_SPAN_NAME_FALLBACK);\n\n const attributes: SpanAttributes = {};\n\n if (selector) {\n attributes['browser.web_vital.lcp.element'] = selector;\n }\n if (componentName) {\n attributes[UI_COMPONENT_NAME] = componentName;\n }\n entry?.id && (attributes['browser.web_vital.lcp.id'] = entry.id);\n entry?.url && (attributes['browser.web_vital.lcp.url'] = entry.url);\n entry?.loadTime != null && (attributes['browser.web_vital.lcp.load_time'] = entry.loadTime);\n entry?.renderTime != null && (attributes['browser.web_vital.lcp.render_time'] = entry.renderTime);\n entry?.size != null && (attributes['browser.web_vital.lcp.size'] = entry.size);\n\n _emitWebVitalSpan({\n name,\n op: UI_WEBVITAL_LCP,\n origin: 'auto.http.browser.lcp',\n metricName: 'lcp',\n value: lcpValue,\n attributes,\n parentSpan: pageloadSpan,\n startTime,\n endTime,\n softNavigationId,\n navigationType,\n });\n}\n\n/**\n * Tracks CLS as a streamed span.\n */\nexport function trackClsAsSpan(client: Client, perNavigation = false): void {\n if (!supportsWebVital('layout-shift')) {\n return;\n }\n\n if (perNavigation) {\n trackWebVitalPerNavigation(client, addClsInstrumentationHandler, (metric, parentSpan, softNavigationId) => {\n const entry = metric.entries[metric.entries.length - 1] as LayoutShift | undefined;\n _sendClsSpan(\n metric.value,\n entry,\n parentSpan,\n softNavigationId,\n metric.navigationType,\n metric.navigationStartTime,\n );\n });\n return;\n }\n\n let clsValue = 0;\n let clsEntry: LayoutShift | undefined;\n let clsNavigationType: MetricNavigationType | undefined;\n\n const cleanupClsHandler = addClsInstrumentationHandler(({ metric }) => {\n // A CLS of 0 is reported with no entries and still emits a span, so the navigation type has to\n // be captured before the entry check rather than alongside the value.\n clsNavigationType = metric.navigationType;\n\n const entry = metric.entries[metric.entries.length - 1] as LayoutShift | undefined;\n if (!entry) {\n return;\n }\n clsValue = metric.value;\n clsEntry = entry;\n }, true);\n\n listenForWebVitalReportEvents(client, pageloadSpan => {\n _sendClsSpan(clsValue, clsEntry, pageloadSpan, undefined, clsNavigationType);\n cleanupClsHandler();\n });\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendClsSpan(\n clsValue: number,\n entry: LayoutShift | undefined,\n pageloadSpan?: Span,\n softNavigationId?: number,\n navigationType?: MetricNavigationType,\n navigationStartTime?: number,\n): void {\n DEBUG_BUILD && debug.log(`Sending CLS span (${clsValue})`);\n\n const performanceTimeOrigin = browserPerformanceTimeOrigin();\n // A CLS of 0 has no shift to place the span at. It is reported when the navigation it was\n // measured on is already over - the next soft navigation, or pagehide - so the current time would\n // land it outside that navigation, on the route that follows it.\n const offset = entry?.startTime ?? navigationStartTime ?? 0;\n const startTime = performanceTimeOrigin ? msToSec(performanceTimeOrigin + offset) : timestampInSeconds();\n const firstSourceNode = entry?.sources[0]?.node;\n const selector = entry ? htmlTreeAsString(firstSourceNode) : undefined;\n const componentName = firstSourceNode ? getComponentName(firstSourceNode) : null;\n const client = getClient();\n const hasSpanStreaming = !!client && hasSpanStreamingEnabled(client);\n const name = hasSpanStreaming\n ? componentName || UI_WEBVITAL_CLS_SPAN_NAME_FALLBACK\n : (selector ?? UI_WEBVITAL_CLS_SPAN_NAME_FALLBACK);\n\n const attributes: SpanAttributes = {};\n\n if (componentName) {\n attributes[UI_COMPONENT_NAME] = componentName;\n }\n\n if (entry?.sources) {\n entry.sources.forEach((source, index) => {\n attributes[`browser.web_vital.cls.source.${index + 1}`] = htmlTreeAsString(source.node);\n });\n }\n\n _emitWebVitalSpan({\n name,\n op: UI_WEBVITAL_CLS,\n origin: 'auto.http.browser.cls',\n metricName: 'cls',\n value: clsValue,\n attributes,\n parentSpan: pageloadSpan,\n startTime,\n softNavigationId,\n navigationType,\n });\n}\n\n/**\n * Tracks INP and emits it as a web vital span, as a child of the interaction's root span.\n * Requires `registerInpInteractionListener()` to be called separately for cached element names and\n * root spans per interaction.\n */\nexport function trackInpAsSpan(client: Client, perNavigation = false): void {\n const performance = getBrowserPerformanceAPI();\n if (!performance || !browserPerformanceTimeOrigin()) {\n return;\n }\n\n // INP reports late (on pagehide, after the pageload span has ended). With span streaming enabled\n // it rides the streaming pipeline. With streaming disabled it would be dropped as a late span, so\n // it is emitted as its own standalone v2 span instead (see `_emitWebVitalSpan`), overriding the\n // static trace lifecycle for INP only.\n // TODO(standalone): once the static trace lifecycle is dropped, INP always streams; drop this flag.\n const standalone = !hasSpanStreamingEnabled(client);\n\n if (perNavigation) {\n // INP restarts per navigation and reports once that navigation is over, by which point the\n // navigation span has ended and the interaction cache no longer knows about it. The metric\n // says which navigation it belongs to, so INP is attributed exactly like LCP and CLS.\n trackWebVitalPerNavigation(client, addInpInstrumentationHandler, (metric, parentSpan, softNavigationId) => {\n if (isPlausibleInp(metric)) {\n _sendInpSpan(metric.value, findInpEntry(metric), standalone, parentSpan, softNavigationId, metric);\n }\n });\n return;\n }\n\n const onInp: InstrumentationHandlerCallback = ({ metric }) => {\n if (isPlausibleInp(metric)) {\n _sendInpSpan(metric.value, findInpEntry(metric), standalone, undefined, undefined, metric);\n }\n };\n\n addInpInstrumentationHandler(onInp);\n}\n\nfunction isPlausibleInp(metric: InpMetric): boolean {\n return metric.value != null && msToSec(metric.value) <= MAX_PLAUSIBLE_INP_DURATION;\n}\n\n/**\n * The entry an INP span is built from: the one whose duration the reported value came from.\n *\n * There isn't always one. When every interaction of a soft navigation stayed below the Event Timing\n * threshold, web-vitals reports a synthetic value with no entries at all - see\n * `_estimateP98LongestInteraction`. The span still gets reported in that case, just without the\n * element and interaction type an entry would have supplied.\n */\nfunction findInpEntry(metric: InpMetric): PerformanceEventTiming | undefined {\n return metric.entries.find(e => e.duration === metric.value && INP_ENTRY_MAP[e.name]);\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendInpSpan(\n inpValue: number,\n entry: PerformanceEventTiming | undefined,\n standalone = false,\n attributedSpan?: Span,\n softNavigationId?: number,\n metric?: InpMetric,\n): void {\n DEBUG_BUILD && debug.log(`Sending INP span (${inpValue})`);\n\n // A web vital span carries the metric, not a real interaction timing, so an INP without an entry\n // is still worth reporting. It just has no element or interaction type to describe, and is placed\n // at the start of the navigation it belongs to rather than at the interaction.\n const startTime = msToSec(\n (browserPerformanceTimeOrigin() as number) + (entry?.startTime ?? metric?.navigationStartTime ?? 0),\n );\n const duration = msToSec(inpValue);\n // An INP without an entry has no interaction type to report. It still has to land inside the\n // `ui.interaction.*` family, because falling outside it would hide exactly the fast navigations\n // that web-vitals synthesizes these values for (GoogleChrome/web-vitals#724), reintroducing the\n // reporting bias they were added to remove.\n const entryInteractionType = entry && INP_ENTRY_MAP[entry.name];\n const interactionType = entryInteractionType || 'click';\n\n const cachedContext = entry && getCachedInteractionContext(entry.interactionId);\n const activeSpan = getActiveSpan();\n const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;\n\n // With soft navigations the caller knows exactly which navigation the metric belongs to. Without\n // them we fall back to the span that was active when the interaction was observed.\n const spanToUse = attributedSpan || cachedContext?.span || rootSpan;\n const selector = cachedContext?.elementName || (entry ? htmlTreeAsString(entry.target) : undefined);\n const componentName = entry?.target ? getComponentName(entry.target) : null;\n const client = getClient();\n const hasSpanStreaming = !!client && hasSpanStreamingEnabled(client);\n const fallbackName = INTERACTION_TYPE_TO_SPAN_NAME_FALLBACK[interactionType];\n const name = hasSpanStreaming ? componentName || fallbackName : (selector ?? 'Interaction to next paint');\n\n const attributes: SpanAttributes = {\n [SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry?.duration ?? inpValue,\n };\n\n // The span's name and op always have a value, even for an INP without an entry, so they can't\n // say whether there was an interaction to describe. These attributes can: they are only set for\n // what was actually observed. The name no longer holds the selector either, now that it is the\n // component name or the op's fallback under span streaming.\n selector && selector !== UNKNOWN_ELEMENT_NAME && (attributes[BROWSER_WEB_VITAL_INP_TARGET] = selector);\n entryInteractionType && (attributes[BROWSER_WEB_VITAL_INP_INTERACTION_TYPE] = entryInteractionType);\n\n if (componentName) {\n attributes[UI_COMPONENT_NAME] = componentName;\n }\n\n _emitWebVitalSpan({\n name,\n op: INTERACTION_TYPE_TO_SPAN_OP[interactionType],\n origin: 'auto.http.browser.inp',\n metricName: 'inp',\n value: inpValue,\n attributes,\n startTime,\n endTime: startTime + duration,\n navigationType: metric?.navigationType,\n parentSpan: spanToUse,\n standalone,\n softNavigationId,\n });\n}\n"],"names":["UI_INTERACTION_CLICK","UI_INTERACTION_HOVER","UI_INTERACTION_DRAG","UI_INTERACTION_PRESS","UI_INTERACTION_CLICK_SPAN_NAME_FALLBACK","UI_INTERACTION_HOVER_SPAN_NAME_FALLBACK","UI_INTERACTION_DRAG_SPAN_NAME_FALLBACK","UI_INTERACTION_PRESS_SPAN_NAME_FALLBACK","attributes","spanToJSON","SEMANTIC_ATTRIBUTE_SENTRY_OP","NAVIGATION","BROWSER_NAVIGATION_TYPE","getNavigationSpanForMetric","DEBUG_BUILD","debug","supportsWebVital","addLcpInstrumentationHandler","isValidLcpMetric","listenForWebVitalReportEvents","browserPerformanceTimeOrigin","msToSec","htmlTreeAsString","componentName","getComponentName","getClient","hasSpanStreamingEnabled","UI_WEBVITAL_LCP_SPAN_NAME_FALLBACK","UI_COMPONENT_NAME","_emitWebVitalSpan","UI_WEBVITAL_LCP","addClsInstrumentationHandler","timestampInSeconds","UI_WEBVITAL_CLS_SPAN_NAME_FALLBACK","UI_WEBVITAL_CLS","getBrowserPerformanceAPI","addInpInstrumentationHandler","MAX_PLAUSIBLE_INP_DURATION","INP_ENTRY_MAP","getCachedInteractionContext","getActiveSpan","getRootSpan","SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME","UNKNOWN_ELEMENT_NAME","BROWSER_WEB_VITAL_INP_TARGET","BROWSER_WEB_VITAL_INP_INTERACTION_TYPE"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"spans.js","sources":["../../../src/web-vitals/spans.ts"],"sourcesContent":["/* eslint-disable max-lines */\nimport type { Client, Span, SpanAttributes } from '@sentry/core';\nimport {\n browserPerformanceTimeOrigin,\n debug,\n getActiveSpan,\n getClient,\n getRootSpan,\n hasSpanStreamingEnabled,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n spanToJSON,\n timestampInSeconds,\n UI_INTERACTION_CLICK_SPAN_NAME_FALLBACK,\n UI_INTERACTION_DRAG_SPAN_NAME_FALLBACK,\n UI_INTERACTION_HOVER_SPAN_NAME_FALLBACK,\n UI_INTERACTION_PRESS_SPAN_NAME_FALLBACK,\n UI_WEBVITAL_CLS_SPAN_NAME_FALLBACK,\n UI_WEBVITAL_LCP_SPAN_NAME_FALLBACK,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\nimport { htmlTreeAsString } from '../htmlTreeAsString';\nimport { getComponentName } from '../component-name';\nimport type { InteractionType } from './inp';\nimport { getCachedInteractionContext, INP_ENTRY_MAP, MAX_PLAUSIBLE_INP_DURATION, UNKNOWN_ELEMENT_NAME } from './inp';\nimport type { InstrumentationHandlerCallback, MetricNavigationType } from '../instrumentation/performanceObserver';\nimport {\n addClsInstrumentationHandler,\n addInpInstrumentationHandler,\n addLcpInstrumentationHandler,\n} from '../instrumentation/performanceObserver';\nimport type { LargestContentfulPaint, LayoutShift } from './emitSpan';\nimport {\n SENTRY_EXCLUSIVE_TIME,\n BROWSER_NAVIGATION_TYPE,\n BROWSER_WEB_VITAL_INP_INTERACTION_TYPE,\n BROWSER_WEB_VITAL_INP_TARGET,\n UI_COMPONENT_NAME,\n} from '@sentry/conventions/attributes';\nimport { _emitWebVitalSpan } from './emitSpan';\nimport { isValidLcpMetric } from './lcp';\nimport { listenForWebVitalReportEvents } from './reportEvents';\nimport { getNavigationSpanForMetric } from './softNavs';\nimport { getBrowserPerformanceAPI, msToSec, supportsWebVital } from '../performance/utils';\nimport type { PerformanceEventTiming } from '../instrumentation/performanceObserver';\nimport {\n NAVIGATION,\n UI_INTERACTION_CLICK,\n UI_INTERACTION_DRAG,\n UI_INTERACTION_HOVER,\n UI_INTERACTION_PRESS,\n UI_WEBVITAL_CLS,\n UI_WEBVITAL_LCP,\n} from '@sentry/conventions/op';\n\nconst INTERACTION_TYPE_TO_SPAN_OP: Record<InteractionType, string> = {\n click: UI_INTERACTION_CLICK,\n hover: UI_INTERACTION_HOVER,\n drag: UI_INTERACTION_DRAG,\n press: UI_INTERACTION_PRESS,\n};\n\nconst INTERACTION_TYPE_TO_SPAN_NAME_FALLBACK: Record<InteractionType, string> = {\n click: UI_INTERACTION_CLICK_SPAN_NAME_FALLBACK,\n hover: UI_INTERACTION_HOVER_SPAN_NAME_FALLBACK,\n drag: UI_INTERACTION_DRAG_SPAN_NAME_FALLBACK,\n press: UI_INTERACTION_PRESS_SPAN_NAME_FALLBACK,\n};\n\ntype WebVitalMetric = Parameters<Parameters<typeof addLcpInstrumentationHandler>[0]>[0]['metric'];\ntype InpMetric = Parameters<InstrumentationHandlerCallback>[0]['metric'];\n\n/**\n * Reports a web vital once per navigation, rather than once per page load.\n *\n * web-vitals restarts the metric on every soft navigation and force-reports the previous one just\n * before it does (and again on pagehide), and re-reports every metric after a bfcache restore.\n * Since `reportAllChanges` is off in this mode, every value we're handed is already the final one\n * for its navigation, so there is nothing to accumulate: each report is a span.\n */\nfunction trackWebVitalPerNavigation<M extends WebVitalMetric>(\n client: Client,\n addInstrumentationHandler: (callback: (data: { metric: M }) => void) => unknown,\n send: (metric: M, parentSpan: Span | undefined, softNavigationId: number | undefined) => void,\n): void {\n let pageloadSpan: Span | undefined;\n client.on('afterStartPageLoadSpan', span => {\n pageloadSpan = span;\n });\n\n // Remembered when the restore happens rather than read back at report time: the restore\n // navigation span is an idle span, and CLS and INP are only finalized on pagehide, by which point\n // it has long ended and is no longer what is active.\n let bfcacheNavigationSpan: Span | undefined;\n client.on('spanStart', span => {\n // The op has to be checked too: the web vital spans emitted for a restore carry the same\n // `back-forward-cache` navigation type, so matching on that alone lets the first of them replace the\n // navigation span, and every later vital then hangs off a sibling vital instead.\n const attributes = spanToJSON(span).attributes;\n if (\n attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] === NAVIGATION &&\n attributes[BROWSER_NAVIGATION_TYPE] === 'back-forward-cache'\n ) {\n bfcacheNavigationSpan = span;\n }\n });\n\n addInstrumentationHandler(({ metric }) => {\n const navigationSpan = getNavigationSpanForMetric(metric);\n if (metric.navigationType === 'soft-navigation') {\n // Reporting an uncorrelated soft navigation vital would attribute it to the wrong route, so\n // it's dropped instead.\n if (navigationSpan) {\n send(metric, navigationSpan, metric.navigationId);\n } else {\n DEBUG_BUILD &&\n debug.log(`[SoftNav] Dropping ${metric.name} for uncorrelated soft navigation ${metric.navigationId}`);\n }\n return;\n }\n\n if (metric.navigationType === 'back-forward-cache') {\n // A restore reuses the frozen document, so the pageload span above belongs to the page view\n // from before the freeze. The navigation span started for the restore is the page view these\n // values were actually measured on.\n send(metric, bfcacheNavigationSpan, undefined);\n return;\n }\n\n send(metric, pageloadSpan, undefined);\n });\n}\n\n/**\n * Tracks LCP as a streamed span.\n */\nexport function trackLcpAsSpan(client: Client, perNavigation = false): void {\n if (!supportsWebVital('largest-contentful-paint')) {\n return;\n }\n\n if (perNavigation) {\n trackWebVitalPerNavigation(client, addLcpInstrumentationHandler, (metric, parentSpan, softNavigationId) => {\n const entry = metric.entries[metric.entries.length - 1] as LargestContentfulPaint | undefined;\n _sendLcpSpan(\n metric.value,\n entry,\n parentSpan,\n softNavigationId,\n metric.navigationType,\n metric.navigationStartTime,\n );\n });\n return;\n }\n\n let lcpValue = 0;\n let lcpEntry: LargestContentfulPaint | undefined;\n let lcpNavigationType: MetricNavigationType | undefined;\n\n const cleanupLcpHandler = addLcpInstrumentationHandler(({ metric }) => {\n // The navigation type describes the page, not the entry, so it is worth keeping even for a\n // report we otherwise discard.\n lcpNavigationType = metric.navigationType;\n\n const entry = metric.entries[metric.entries.length - 1] as LargestContentfulPaint | undefined;\n if (!entry || !isValidLcpMetric(metric.value)) {\n return;\n }\n lcpValue = metric.value;\n lcpEntry = entry;\n }, true);\n\n listenForWebVitalReportEvents(client, pageloadSpan => {\n _sendLcpSpan(lcpValue, lcpEntry, pageloadSpan, undefined, lcpNavigationType);\n cleanupLcpHandler();\n });\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendLcpSpan(\n lcpValue: number,\n entry: LargestContentfulPaint | undefined,\n pageloadSpan?: Span,\n softNavigationId?: number,\n navigationType?: MetricNavigationType,\n navigationStartTime?: number,\n): void {\n if (!isValidLcpMetric(lcpValue)) {\n return;\n }\n\n DEBUG_BUILD && debug.log(`Sending LCP span (${lcpValue})`);\n\n const performanceTimeOrigin = browserPerformanceTimeOrigin() || 0;\n // A soft navigation's LCP is measured from the triggering interaction, not the document time\n // origin. Starting the span there too keeps it inside the navigation span it is parented to and\n // keeps its duration equal to the value it reports.\n const startTime = msToSec(performanceTimeOrigin + (navigationStartTime || 0));\n // Without an entry there is no render time to end at, so the span lasts the value it reports,\n // like an entry-less INP does. Ending at the time origin instead would invert the span.\n const endTime = entry ? msToSec(performanceTimeOrigin + entry.startTime) : startTime + msToSec(lcpValue);\n const selector = entry ? htmlTreeAsString(entry.element) : undefined;\n const componentName = entry?.element ? getComponentName(entry.element) : null;\n const client = getClient();\n const hasSpanStreaming = !!client && hasSpanStreamingEnabled(client);\n const name = hasSpanStreaming\n ? componentName || UI_WEBVITAL_LCP_SPAN_NAME_FALLBACK\n : (selector ?? UI_WEBVITAL_LCP_SPAN_NAME_FALLBACK);\n\n const attributes: SpanAttributes = {};\n\n if (selector) {\n attributes['browser.web_vital.lcp.element'] = selector;\n }\n if (componentName) {\n attributes[UI_COMPONENT_NAME] = componentName;\n }\n entry?.id && (attributes['browser.web_vital.lcp.id'] = entry.id);\n entry?.url && (attributes['browser.web_vital.lcp.url'] = entry.url);\n entry?.loadTime != null && (attributes['browser.web_vital.lcp.load_time'] = entry.loadTime);\n entry?.renderTime != null && (attributes['browser.web_vital.lcp.render_time'] = entry.renderTime);\n entry?.size != null && (attributes['browser.web_vital.lcp.size'] = entry.size);\n\n _emitWebVitalSpan({\n name,\n op: UI_WEBVITAL_LCP,\n origin: 'auto.http.browser.lcp',\n metricName: 'lcp',\n value: lcpValue,\n attributes,\n parentSpan: pageloadSpan,\n startTime,\n endTime,\n softNavigationId,\n navigationType,\n });\n}\n\n/**\n * Tracks CLS as a streamed span.\n */\nexport function trackClsAsSpan(client: Client, perNavigation = false): void {\n if (!supportsWebVital('layout-shift')) {\n return;\n }\n\n if (perNavigation) {\n trackWebVitalPerNavigation(client, addClsInstrumentationHandler, (metric, parentSpan, softNavigationId) => {\n const entry = metric.entries[metric.entries.length - 1] as LayoutShift | undefined;\n _sendClsSpan(\n metric.value,\n entry,\n parentSpan,\n softNavigationId,\n metric.navigationType,\n metric.navigationStartTime,\n );\n });\n return;\n }\n\n let clsValue = 0;\n let clsEntry: LayoutShift | undefined;\n let clsNavigationType: MetricNavigationType | undefined;\n\n const cleanupClsHandler = addClsInstrumentationHandler(({ metric }) => {\n // A CLS of 0 is reported with no entries and still emits a span, so the navigation type has to\n // be captured before the entry check rather than alongside the value.\n clsNavigationType = metric.navigationType;\n\n const entry = metric.entries[metric.entries.length - 1] as LayoutShift | undefined;\n if (!entry) {\n return;\n }\n clsValue = metric.value;\n clsEntry = entry;\n }, true);\n\n listenForWebVitalReportEvents(client, pageloadSpan => {\n _sendClsSpan(clsValue, clsEntry, pageloadSpan, undefined, clsNavigationType);\n cleanupClsHandler();\n });\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendClsSpan(\n clsValue: number,\n entry: LayoutShift | undefined,\n pageloadSpan?: Span,\n softNavigationId?: number,\n navigationType?: MetricNavigationType,\n navigationStartTime?: number,\n): void {\n DEBUG_BUILD && debug.log(`Sending CLS span (${clsValue})`);\n\n const performanceTimeOrigin = browserPerformanceTimeOrigin();\n // A CLS of 0 has no shift to place the span at. It is reported when the navigation it was\n // measured on is already over - the next soft navigation, or pagehide - so the current time would\n // land it outside that navigation, on the route that follows it.\n const offset = entry?.startTime ?? navigationStartTime ?? 0;\n const startTime = performanceTimeOrigin ? msToSec(performanceTimeOrigin + offset) : timestampInSeconds();\n const firstSourceNode = entry?.sources[0]?.node;\n const selector = entry ? htmlTreeAsString(firstSourceNode) : undefined;\n const componentName = firstSourceNode ? getComponentName(firstSourceNode) : null;\n const client = getClient();\n const hasSpanStreaming = !!client && hasSpanStreamingEnabled(client);\n const name = hasSpanStreaming\n ? componentName || UI_WEBVITAL_CLS_SPAN_NAME_FALLBACK\n : (selector ?? UI_WEBVITAL_CLS_SPAN_NAME_FALLBACK);\n\n const attributes: SpanAttributes = {};\n\n if (componentName) {\n attributes[UI_COMPONENT_NAME] = componentName;\n }\n\n if (entry?.sources) {\n entry.sources.forEach((source, index) => {\n attributes[`browser.web_vital.cls.source.${index + 1}`] = htmlTreeAsString(source.node);\n });\n }\n\n _emitWebVitalSpan({\n name,\n op: UI_WEBVITAL_CLS,\n origin: 'auto.http.browser.cls',\n metricName: 'cls',\n value: clsValue,\n attributes,\n parentSpan: pageloadSpan,\n startTime,\n softNavigationId,\n navigationType,\n });\n}\n\n/**\n * Tracks INP and emits it as a web vital span, as a child of the interaction's root span.\n * Requires `registerInpInteractionListener()` to be called separately for cached element names and\n * root spans per interaction.\n */\nexport function trackInpAsSpan(client: Client, perNavigation = false): void {\n const performance = getBrowserPerformanceAPI();\n if (!performance || !browserPerformanceTimeOrigin()) {\n return;\n }\n\n // INP reports late (on pagehide, after the pageload span has ended). With span streaming enabled\n // it rides the streaming pipeline. With streaming disabled it would be dropped as a late span, so\n // it is emitted as its own standalone v2 span instead (see `_emitWebVitalSpan`), overriding the\n // static trace lifecycle for INP only.\n // TODO(standalone): once the static trace lifecycle is dropped, INP always streams; drop this flag.\n const standalone = !hasSpanStreamingEnabled(client);\n\n if (perNavigation) {\n // INP restarts per navigation and reports once that navigation is over, by which point the\n // navigation span has ended and the interaction cache no longer knows about it. The metric\n // says which navigation it belongs to, so INP is attributed exactly like LCP and CLS.\n trackWebVitalPerNavigation(client, addInpInstrumentationHandler, (metric, parentSpan, softNavigationId) => {\n if (isPlausibleInp(metric)) {\n _sendInpSpan(metric.value, findInpEntry(metric), standalone, parentSpan, softNavigationId, metric);\n }\n });\n return;\n }\n\n const onInp: InstrumentationHandlerCallback = ({ metric }) => {\n if (isPlausibleInp(metric)) {\n _sendInpSpan(metric.value, findInpEntry(metric), standalone, undefined, undefined, metric);\n }\n };\n\n addInpInstrumentationHandler(onInp);\n}\n\nfunction isPlausibleInp(metric: InpMetric): boolean {\n return metric.value != null && msToSec(metric.value) <= MAX_PLAUSIBLE_INP_DURATION;\n}\n\n/**\n * The entry an INP span is built from: the one whose duration the reported value came from.\n *\n * There isn't always one. When every interaction of a soft navigation stayed below the Event Timing\n * threshold, web-vitals reports a synthetic value with no entries at all - see\n * `_estimateP98LongestInteraction`. The span still gets reported in that case, just without the\n * element and interaction type an entry would have supplied.\n */\nfunction findInpEntry(metric: InpMetric): PerformanceEventTiming | undefined {\n return metric.entries.find(e => e.duration === metric.value && INP_ENTRY_MAP[e.name]);\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendInpSpan(\n inpValue: number,\n entry: PerformanceEventTiming | undefined,\n standalone = false,\n attributedSpan?: Span,\n softNavigationId?: number,\n metric?: InpMetric,\n): void {\n DEBUG_BUILD && debug.log(`Sending INP span (${inpValue})`);\n\n // A web vital span carries the metric, not a real interaction timing, so an INP without an entry\n // is still worth reporting. It just has no element or interaction type to describe, and is placed\n // at the start of the navigation it belongs to rather than at the interaction.\n const startTime = msToSec(\n (browserPerformanceTimeOrigin() as number) + (entry?.startTime ?? metric?.navigationStartTime ?? 0),\n );\n const duration = msToSec(inpValue);\n // An INP without an entry has no interaction type to report. It still has to land inside the\n // `ui.interaction.*` family, because falling outside it would hide exactly the fast navigations\n // that web-vitals synthesizes these values for (GoogleChrome/web-vitals#724), reintroducing the\n // reporting bias they were added to remove.\n const entryInteractionType = entry && INP_ENTRY_MAP[entry.name];\n const interactionType = entryInteractionType || 'click';\n\n const cachedContext = entry && getCachedInteractionContext(entry.interactionId);\n const activeSpan = getActiveSpan();\n const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;\n\n // With soft navigations the caller knows exactly which navigation the metric belongs to. Without\n // them we fall back to the span that was active when the interaction was observed.\n const spanToUse = attributedSpan || cachedContext?.span || rootSpan;\n const selector = cachedContext?.elementName || (entry ? htmlTreeAsString(entry.target) : undefined);\n const componentName = entry?.target ? getComponentName(entry.target) : null;\n const client = getClient();\n const hasSpanStreaming = !!client && hasSpanStreamingEnabled(client);\n const fallbackName = INTERACTION_TYPE_TO_SPAN_NAME_FALLBACK[interactionType];\n const name = hasSpanStreaming ? componentName || fallbackName : (selector ?? 'Interaction to next paint');\n\n const attributes: SpanAttributes = {\n [SENTRY_EXCLUSIVE_TIME]: entry?.duration ?? inpValue,\n };\n\n // The span's name and op always have a value, even for an INP without an entry, so they can't\n // say whether there was an interaction to describe. These attributes can: they are only set for\n // what was actually observed. The name no longer holds the selector either, now that it is the\n // component name or the op's fallback under span streaming.\n selector && selector !== UNKNOWN_ELEMENT_NAME && (attributes[BROWSER_WEB_VITAL_INP_TARGET] = selector);\n entryInteractionType && (attributes[BROWSER_WEB_VITAL_INP_INTERACTION_TYPE] = entryInteractionType);\n\n if (componentName) {\n attributes[UI_COMPONENT_NAME] = componentName;\n }\n\n _emitWebVitalSpan({\n name,\n op: INTERACTION_TYPE_TO_SPAN_OP[interactionType],\n origin: 'auto.http.browser.inp',\n metricName: 'inp',\n value: inpValue,\n attributes,\n startTime,\n endTime: startTime + duration,\n navigationType: metric?.navigationType,\n parentSpan: spanToUse,\n standalone,\n softNavigationId,\n });\n}\n"],"names":["UI_INTERACTION_CLICK","UI_INTERACTION_HOVER","UI_INTERACTION_DRAG","UI_INTERACTION_PRESS","UI_INTERACTION_CLICK_SPAN_NAME_FALLBACK","UI_INTERACTION_HOVER_SPAN_NAME_FALLBACK","UI_INTERACTION_DRAG_SPAN_NAME_FALLBACK","UI_INTERACTION_PRESS_SPAN_NAME_FALLBACK","attributes","spanToJSON","SEMANTIC_ATTRIBUTE_SENTRY_OP","NAVIGATION","BROWSER_NAVIGATION_TYPE","getNavigationSpanForMetric","DEBUG_BUILD","debug","supportsWebVital","addLcpInstrumentationHandler","isValidLcpMetric","listenForWebVitalReportEvents","browserPerformanceTimeOrigin","msToSec","htmlTreeAsString","componentName","getComponentName","getClient","hasSpanStreamingEnabled","UI_WEBVITAL_LCP_SPAN_NAME_FALLBACK","UI_COMPONENT_NAME","_emitWebVitalSpan","UI_WEBVITAL_LCP","addClsInstrumentationHandler","timestampInSeconds","UI_WEBVITAL_CLS_SPAN_NAME_FALLBACK","UI_WEBVITAL_CLS","getBrowserPerformanceAPI","addInpInstrumentationHandler","MAX_PLAUSIBLE_INP_DURATION","INP_ENTRY_MAP","getCachedInteractionContext","getActiveSpan","getRootSpan","SENTRY_EXCLUSIVE_TIME","UNKNOWN_ELEMENT_NAME","BROWSER_WEB_VITAL_INP_TARGET","BROWSER_WEB_VITAL_INP_INTERACTION_TYPE"],"mappings":"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"}
|
package/build/esm/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { elementTimingIntegration, startTrackingElementTiming } from './performa
|
|
|
6
6
|
export { interactionsIntegration } from './performance/interactions.js';
|
|
7
7
|
export { isBotUserAgent } from './isBotUserAgent.js';
|
|
8
8
|
export { getLocationHref } from './getLocationHref.js';
|
|
9
|
+
export { createCachedRouteProvider, createUrlRouteProvider, getRouteProvider, resolveCurrentRoute, resolveRoute, setRouteProvider } from './routing.js';
|
|
9
10
|
export { userTimingIntegration } from './performance/userTiming.js';
|
|
10
11
|
export { extractNetworkProtocol } from './performance/utils.js';
|
|
11
12
|
export { trackClsAsSpan, trackInpAsSpan, trackLcpAsSpan } from './web-vitals/spans.js';
|
package/build/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -5,6 +5,9 @@ const DEBOUNCE_DURATION = 1e3;
|
|
|
5
5
|
let debounceTimerID;
|
|
6
6
|
let lastCapturedEventType;
|
|
7
7
|
let lastCapturedEventTargetId;
|
|
8
|
+
function getCapture(options) {
|
|
9
|
+
return typeof options === "boolean" ? options : !!options?.capture;
|
|
10
|
+
}
|
|
8
11
|
function addClickKeypressInstrumentationHandler(handler) {
|
|
9
12
|
const type = "dom";
|
|
10
13
|
addHandler(type, handler);
|
|
@@ -26,16 +29,30 @@ function instrumentDOM() {
|
|
|
26
29
|
}
|
|
27
30
|
fill(proto, "addEventListener", function(originalAddEventListener) {
|
|
28
31
|
return function(type, listener, options) {
|
|
29
|
-
if (type === "click" || type == "keypress") {
|
|
32
|
+
if ((type === "click" || type == "keypress") && listener) {
|
|
30
33
|
try {
|
|
31
34
|
const handlers = this.__sentry_instrumentation_handlers__ = this.__sentry_instrumentation_handlers__ || {};
|
|
32
|
-
const handlerForType = handlers[type] = handlers[type] || {
|
|
35
|
+
const handlerForType = handlers[type] = handlers[type] || {
|
|
36
|
+
captureListeners: /* @__PURE__ */ new WeakSet(),
|
|
37
|
+
bubbleListeners: /* @__PURE__ */ new WeakSet(),
|
|
38
|
+
listenerCount: 0
|
|
39
|
+
};
|
|
40
|
+
const capture = getCapture(options);
|
|
33
41
|
if (!handlerForType.handler) {
|
|
34
42
|
const handler = makeDOMEventHandler(triggerDOMHandler);
|
|
35
43
|
handlerForType.handler = handler;
|
|
36
|
-
|
|
44
|
+
handlerForType.capture = capture;
|
|
45
|
+
originalAddEventListener.call(this, type, handler, handlerForType.capture);
|
|
46
|
+
}
|
|
47
|
+
const listeners = handlerForType[capture ? "captureListeners" : "bubbleListeners"];
|
|
48
|
+
if (!listeners.has(listener)) {
|
|
49
|
+
if (typeof options === "object" && (options?.once || options?.signal)) {
|
|
50
|
+
handlerForType.sticky = true;
|
|
51
|
+
} else {
|
|
52
|
+
listeners.add(listener);
|
|
53
|
+
handlerForType.listenerCount++;
|
|
54
|
+
}
|
|
37
55
|
}
|
|
38
|
-
handlerForType.refCount++;
|
|
39
56
|
} catch {
|
|
40
57
|
}
|
|
41
58
|
}
|
|
@@ -47,14 +64,13 @@ function instrumentDOM() {
|
|
|
47
64
|
"removeEventListener",
|
|
48
65
|
function(originalRemoveEventListener) {
|
|
49
66
|
return function(type, listener, options) {
|
|
50
|
-
if (type === "click" || type == "keypress") {
|
|
67
|
+
if ((type === "click" || type == "keypress") && listener) {
|
|
51
68
|
try {
|
|
52
69
|
const handlers = this.__sentry_instrumentation_handlers__ || {};
|
|
53
70
|
const handlerForType = handlers[type];
|
|
54
|
-
if (handlerForType) {
|
|
55
|
-
handlerForType.
|
|
56
|
-
|
|
57
|
-
originalRemoveEventListener.call(this, type, handlerForType.handler, options);
|
|
71
|
+
if (handlerForType?.[getCapture(options) ? "captureListeners" : "bubbleListeners"].delete(listener)) {
|
|
72
|
+
if (!--handlerForType.listenerCount && !handlerForType.sticky) {
|
|
73
|
+
originalRemoveEventListener.call(this, type, handlerForType.handler, handlerForType.capture);
|
|
58
74
|
handlerForType.handler = void 0;
|
|
59
75
|
delete handlers[type];
|
|
60
76
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom.js","sources":["../../../src/instrumentation/dom.ts"],"sourcesContent":["import { addHandler, addNonEnumerableProperty, fill, maybeInstrument, triggerHandlers, uuid4 } from '@sentry/core';\nimport type { HandlerDataDom } from '../types';\nimport { WINDOW } from '../types';\n\ntype SentryWrappedTarget = HTMLElement & { _sentryId?: string };\n\ntype AddEventListener = (\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n) => void;\ntype RemoveEventListener = (\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n) => void;\n\ntype InstrumentedElement = Element & {\n __sentry_instrumentation_handlers__?: {\n [key in 'click' | 'keypress']?: {\n handler?: unknown;\n /** The number of custom listeners attached to this element */\n refCount: number;\n };\n };\n};\n\nconst DEBOUNCE_DURATION = 1000;\n\nlet debounceTimerID: number | undefined;\nlet lastCapturedEventType: string | undefined;\nlet lastCapturedEventTargetId: string | undefined;\n\n/**\n * Add an instrumentation handler for when a click or a keypress happens.\n *\n * Use at your own risk, this might break without changelog notice, only used internally.\n * @hidden\n */\nexport function addClickKeypressInstrumentationHandler(handler: (data: HandlerDataDom) => void): void {\n const type = 'dom';\n addHandler(type, handler);\n maybeInstrument(type, instrumentDOM);\n}\n\n/** Exported for tests only. */\nexport function instrumentDOM(): void {\n if (!WINDOW.document) {\n return;\n }\n\n // Make it so that any click or keypress that is unhandled / bubbled up all the way to the document triggers our dom\n // handlers. (Normally we have only one, which captures a breadcrumb for each click or keypress.) Do this before\n // we instrument `addEventListener` so that we don't end up attaching this handler twice.\n const triggerDOMHandler = triggerHandlers.bind(null, 'dom');\n const globalDOMEventHandler = makeDOMEventHandler(triggerDOMHandler, true);\n WINDOW.document.addEventListener('click', globalDOMEventHandler, false);\n WINDOW.document.addEventListener('keypress', globalDOMEventHandler, false);\n\n // After hooking into click and keypress events bubbled up to `document`, we also hook into user-handled\n // clicks & keypresses, by adding an event listener of our own to any element to which they add a listener. That\n // way, whenever one of their handlers is triggered, ours will be, too. (This is needed because their handler\n // could potentially prevent the event from bubbling up to our global listeners. This way, our handler are still\n // guaranteed to fire at least once.)\n ['EventTarget', 'Node'].forEach((target: string) => {\n const globalObject = WINDOW as unknown as Record<string, { prototype?: object }>;\n const proto = globalObject[target]?.prototype;\n\n // eslint-disable-next-line no-prototype-builtins\n if (!proto?.hasOwnProperty?.('addEventListener')) {\n return;\n }\n\n fill(proto, 'addEventListener', function (originalAddEventListener: AddEventListener): AddEventListener {\n return function (this: InstrumentedElement, type, listener, options): AddEventListener {\n if (type === 'click' || type == 'keypress') {\n try {\n const handlers = (this.__sentry_instrumentation_handlers__ =\n this.__sentry_instrumentation_handlers__ || {});\n const handlerForType = (handlers[type] = handlers[type] || { refCount: 0 });\n\n if (!handlerForType.handler) {\n const handler = makeDOMEventHandler(triggerDOMHandler);\n handlerForType.handler = handler;\n originalAddEventListener.call(this, type, handler, options);\n }\n\n handlerForType.refCount++;\n } catch {\n // Accessing dom properties is always fragile.\n // Also allows us to skip `addEventListeners` calls with no proper `this` context.\n }\n }\n\n return originalAddEventListener.call(this, type, listener, options);\n };\n });\n\n fill(\n proto,\n 'removeEventListener',\n function (originalRemoveEventListener: RemoveEventListener): RemoveEventListener {\n return function (this: InstrumentedElement, type, listener, options): () => void {\n if (type === 'click' || type == 'keypress') {\n try {\n const handlers = this.__sentry_instrumentation_handlers__ || {};\n const handlerForType = handlers[type];\n\n if (handlerForType) {\n handlerForType.refCount--;\n // If there are no longer any custom handlers of the current type on this element, we can remove ours, too.\n if (handlerForType.refCount <= 0) {\n originalRemoveEventListener.call(this, type, handlerForType.handler, options);\n handlerForType.handler = undefined;\n delete handlers[type]; // eslint-disable-line @typescript-eslint/no-dynamic-delete\n }\n\n // If there are no longer any custom handlers of any type on this element, cleanup everything.\n if (Object.keys(handlers).length === 0) {\n delete this.__sentry_instrumentation_handlers__;\n }\n }\n } catch {\n // Accessing dom properties is always fragile.\n // Also allows us to skip `addEventListeners` calls with no proper `this` context.\n }\n }\n\n return originalRemoveEventListener.call(this, type, listener, options);\n };\n },\n );\n });\n}\n\n/**\n * Check whether the event is similar to the last captured one. For example, two click events on the same button.\n */\nfunction isSimilarToLastCapturedEvent(event: Event): boolean {\n // If both events have different type, then user definitely performed two separate actions. e.g. click + keypress.\n if (event.type !== lastCapturedEventType) {\n return false;\n }\n\n try {\n // If both events have the same type, it's still possible that actions were performed on different targets.\n // e.g. 2 clicks on different buttons.\n if (!event.target || (event.target as SentryWrappedTarget)._sentryId !== lastCapturedEventTargetId) {\n return false;\n }\n } catch {\n // just accessing `target` property can throw an exception in some rare circumstances\n // see: https://github.com/getsentry/sentry-javascript/issues/838\n }\n\n // If both events have the same type _and_ same `target` (an element which triggered an event, _not necessarily_\n // to which an event listener was attached), we treat them as the same action, as we want to capture\n // only one breadcrumb. e.g. multiple clicks on the same button, or typing inside a user input box.\n return true;\n}\n\n/**\n * Decide whether an event should be captured.\n * @param event event to be captured\n */\nfunction shouldSkipDOMEvent(eventType: string, target: SentryWrappedTarget | null): boolean {\n // We are only interested in filtering `keypress` events for now.\n if (eventType !== 'keypress') {\n return false;\n }\n\n if (!target?.tagName) {\n return true;\n }\n\n // Only consider keypress events on actual input elements. This will disregard keypresses targeting body\n // e.g.tabbing through elements, hotkeys, etc.\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Wraps addEventListener to capture UI breadcrumbs\n */\nfunction makeDOMEventHandler(\n handler: (data: HandlerDataDom) => void,\n globalListener: boolean = false,\n): (event: Event) => void {\n return (event: Event & { _sentryCaptured?: true }): void => {\n // It's possible this handler might trigger multiple times for the same\n // event (e.g. event propagation through node ancestors).\n // Ignore if we've already captured that event.\n if (!event || event['_sentryCaptured']) {\n return;\n }\n\n const target = getEventTarget(event);\n\n // We always want to skip _some_ events.\n if (shouldSkipDOMEvent(event.type, target)) {\n return;\n }\n\n // Mark event as \"seen\"\n addNonEnumerableProperty(event, '_sentryCaptured', true);\n\n if (target && !target._sentryId) {\n // Add UUID to event target so we can identify if\n addNonEnumerableProperty(target, '_sentryId', uuid4());\n }\n\n const name = event.type === 'keypress' ? 'input' : event.type;\n\n // If there is no last captured event, it means that we can safely capture the new event and store it for future comparisons.\n // If there is a last captured event, see if the new event is different enough to treat it as a unique one.\n // If that's the case, emit the previous event and store locally the newly-captured DOM event.\n if (!isSimilarToLastCapturedEvent(event)) {\n const handlerData: HandlerDataDom = { event, name, global: globalListener };\n handler(handlerData);\n lastCapturedEventType = event.type;\n lastCapturedEventTargetId = target ? target._sentryId : undefined;\n }\n\n // Start a new debounce timer that will prevent us from capturing multiple events that should be grouped together.\n clearTimeout(debounceTimerID);\n debounceTimerID = WINDOW.setTimeout(() => {\n lastCapturedEventTargetId = undefined;\n lastCapturedEventType = undefined;\n }, DEBOUNCE_DURATION);\n };\n}\n\nfunction getEventTarget(event: Event): SentryWrappedTarget | null {\n try {\n return event.target as SentryWrappedTarget | null;\n } catch {\n // just accessing `target` property can throw an exception in some rare circumstances\n // see: https://github.com/getsentry/sentry-javascript/issues/838\n return null;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dom.js","sources":["../../../src/instrumentation/dom.ts"],"sourcesContent":["import { addHandler, addNonEnumerableProperty, fill, maybeInstrument, triggerHandlers, uuid4 } from '@sentry/core';\nimport type { HandlerDataDom } from '../types';\nimport { WINDOW } from '../types';\n\ntype SentryWrappedTarget = HTMLElement & { _sentryId?: string };\n\ntype AddEventListener = (\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n) => void;\ntype RemoveEventListener = (\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n) => void;\n\ntype InstrumentedElement = Element & {\n __sentry_instrumentation_handlers__?: {\n [key in 'click' | 'keypress']?: {\n handler?: unknown;\n capture?: boolean;\n // listeners added with `capture: true`, meaning the listener is invoked before other\n // listeners inside the element's hierarchy.\n captureListeners: WeakSet<EventListenerOrEventListenerObject>;\n // listeners added with `capture: false` (default), meaning the listener is invoked\n // after other listeners inside the element's hierarchy (i.e. the event bubbles up)\n bubbleListeners: WeakSet<EventListenerOrEventListenerObject>;\n // Total number of listeners in `captureListeners` and `bubbleListeners`. WeakSets have no `size`, but we use them\n // so listeners removed without going through our `removeEventListener` patch aren't retained by us.\n listenerCount: number;\n // Set once a `once` or `signal` listener was added. The browser removes those without going\n // through `removeEventListener`, so we can't tell when they're gone and must keep our handler attached.\n sticky?: boolean;\n };\n };\n};\n\nconst DEBOUNCE_DURATION = 1000;\n\nlet debounceTimerID: number | undefined;\nlet lastCapturedEventType: string | undefined;\nlet lastCapturedEventTargetId: string | undefined;\n\nfunction getCapture(options: boolean | EventListenerOptions | undefined): boolean {\n return typeof options === 'boolean' ? options : !!options?.capture;\n}\n\n/**\n * Add an instrumentation handler for when a click or a keypress happens.\n *\n * Use at your own risk, this might break without changelog notice, only used internally.\n * @hidden\n */\nexport function addClickKeypressInstrumentationHandler(handler: (data: HandlerDataDom) => void): void {\n const type = 'dom';\n addHandler(type, handler);\n maybeInstrument(type, instrumentDOM);\n}\n\n/** Exported for tests only. */\nexport function instrumentDOM(): void {\n if (!WINDOW.document) {\n return;\n }\n\n // Make it so that any click or keypress that is unhandled / bubbled up all the way to the document triggers our dom\n // handlers. (Normally we have only one, which captures a breadcrumb for each click or keypress.) Do this before\n // we instrument `addEventListener` so that we don't end up attaching this handler twice.\n const triggerDOMHandler = triggerHandlers.bind(null, 'dom');\n const globalDOMEventHandler = makeDOMEventHandler(triggerDOMHandler, true);\n WINDOW.document.addEventListener('click', globalDOMEventHandler, false);\n WINDOW.document.addEventListener('keypress', globalDOMEventHandler, false);\n\n // After hooking into click and keypress events bubbled up to `document`, we also hook into user-handled\n // clicks & keypresses, by adding an event listener of our own to any element to which they add a listener. That\n // way, whenever one of their handlers is triggered, ours will be, too. (This is needed because their handler\n // could potentially prevent the event from bubbling up to our global listeners. This way, our handler are still\n // guaranteed to fire at least once.)\n ['EventTarget', 'Node'].forEach((target: string) => {\n const globalObject = WINDOW as unknown as Record<string, { prototype?: object }>;\n const proto = globalObject[target]?.prototype;\n\n // eslint-disable-next-line no-prototype-builtins\n if (!proto?.hasOwnProperty?.('addEventListener')) {\n return;\n }\n\n fill(proto, 'addEventListener', function (originalAddEventListener: AddEventListener): AddEventListener {\n return function (this: InstrumentedElement, type, listener, options): AddEventListener {\n // The browser ignores `null` listeners, so there's nothing for our handler to accompany.\n if ((type === 'click' || type == 'keypress') && listener) {\n try {\n const handlers = (this.__sentry_instrumentation_handlers__ =\n this.__sentry_instrumentation_handlers__ || {});\n\n const handlerForType = (handlers[type] = handlers[type] || {\n captureListeners: new WeakSet(),\n bubbleListeners: new WeakSet(),\n listenerCount: 0,\n });\n\n const capture = getCapture(options);\n\n if (!handlerForType.handler) {\n const handler = makeDOMEventHandler(triggerDOMHandler);\n handlerForType.handler = handler;\n // Track the user-set `capture` option because it changes the identity of the registration of the\n // event listener callback function (addEL(fn, true) vs addEL(fn, false) are two different registrations).\n // Our listener needs to have the same capture setting, so that subsequent calls or removeEventListener\n // calls correspond to the correct handler function.\n handlerForType.capture = capture;\n originalAddEventListener.call(this, type, handler, handlerForType.capture);\n }\n\n const listeners = handlerForType[capture ? 'captureListeners' : 'bubbleListeners'];\n // Adding the same listener twice in the same phase is a no-op in the browser.\n if (!listeners.has(listener)) {\n if (typeof options === 'object' && (options?.once || options?.signal)) {\n // Not tracked to avoid retaining listeners the browser auto-removes.\n handlerForType.sticky = true;\n } else {\n listeners.add(listener);\n handlerForType.listenerCount++;\n }\n }\n } catch {\n // Accessing dom properties is always fragile.\n // Also allows us to skip `addEventListeners` calls with no proper `this` context.\n }\n }\n\n return originalAddEventListener.call(this, type, listener, options);\n };\n });\n\n fill(\n proto,\n 'removeEventListener',\n function (originalRemoveEventListener: RemoveEventListener): RemoveEventListener {\n return function (this: InstrumentedElement, type, listener, options): () => void {\n if ((type === 'click' || type == 'keypress') && listener) {\n try {\n const handlers = this.__sentry_instrumentation_handlers__ || {};\n const handlerForType = handlers[type];\n\n // Removing a listener that was never added is a no-op in the browser, so it mustn't count for ours either.\n if (handlerForType?.[getCapture(options) ? 'captureListeners' : 'bubbleListeners'].delete(listener)) {\n // If there are no longer any custom handlers of the current type on this element, we can remove ours, too.\n if (!--handlerForType.listenerCount && !handlerForType.sticky) {\n originalRemoveEventListener.call(this, type, handlerForType.handler, handlerForType.capture);\n handlerForType.handler = undefined;\n delete handlers[type]; // eslint-disable-line @typescript-eslint/no-dynamic-delete\n }\n\n // If there are no longer any custom handlers of any type on this element, cleanup everything.\n if (Object.keys(handlers).length === 0) {\n delete this.__sentry_instrumentation_handlers__;\n }\n }\n } catch {\n // Accessing dom properties is always fragile.\n // Also allows us to skip `addEventListeners` calls with no proper `this` context.\n }\n }\n\n return originalRemoveEventListener.call(this, type, listener, options);\n };\n },\n );\n });\n}\n\n/**\n * Check whether the event is similar to the last captured one. For example, two click events on the same button.\n */\nfunction isSimilarToLastCapturedEvent(event: Event): boolean {\n // If both events have different type, then user definitely performed two separate actions. e.g. click + keypress.\n if (event.type !== lastCapturedEventType) {\n return false;\n }\n\n try {\n // If both events have the same type, it's still possible that actions were performed on different targets.\n // e.g. 2 clicks on different buttons.\n if (!event.target || (event.target as SentryWrappedTarget)._sentryId !== lastCapturedEventTargetId) {\n return false;\n }\n } catch {\n // just accessing `target` property can throw an exception in some rare circumstances\n // see: https://github.com/getsentry/sentry-javascript/issues/838\n }\n\n // If both events have the same type _and_ same `target` (an element which triggered an event, _not necessarily_\n // to which an event listener was attached), we treat them as the same action, as we want to capture\n // only one breadcrumb. e.g. multiple clicks on the same button, or typing inside a user input box.\n return true;\n}\n\n/**\n * Decide whether an event should be captured.\n * @param event event to be captured\n */\nfunction shouldSkipDOMEvent(eventType: string, target: SentryWrappedTarget | null): boolean {\n // We are only interested in filtering `keypress` events for now.\n if (eventType !== 'keypress') {\n return false;\n }\n\n if (!target?.tagName) {\n return true;\n }\n\n // Only consider keypress events on actual input elements. This will disregard keypresses targeting body\n // e.g.tabbing through elements, hotkeys, etc.\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Wraps addEventListener to capture UI breadcrumbs\n */\nfunction makeDOMEventHandler(\n handler: (data: HandlerDataDom) => void,\n globalListener: boolean = false,\n): (event: Event) => void {\n return (event: Event & { _sentryCaptured?: true }): void => {\n // It's possible this handler might trigger multiple times for the same\n // event (e.g. event propagation through node ancestors).\n // Ignore if we've already captured that event.\n if (!event || event['_sentryCaptured']) {\n return;\n }\n\n const target = getEventTarget(event);\n\n // We always want to skip _some_ events.\n if (shouldSkipDOMEvent(event.type, target)) {\n return;\n }\n\n // Mark event as \"seen\"\n addNonEnumerableProperty(event, '_sentryCaptured', true);\n\n if (target && !target._sentryId) {\n // Add UUID to event target so we can identify if\n addNonEnumerableProperty(target, '_sentryId', uuid4());\n }\n\n const name = event.type === 'keypress' ? 'input' : event.type;\n\n // If there is no last captured event, it means that we can safely capture the new event and store it for future comparisons.\n // If there is a last captured event, see if the new event is different enough to treat it as a unique one.\n // If that's the case, emit the previous event and store locally the newly-captured DOM event.\n if (!isSimilarToLastCapturedEvent(event)) {\n const handlerData: HandlerDataDom = { event, name, global: globalListener };\n handler(handlerData);\n lastCapturedEventType = event.type;\n lastCapturedEventTargetId = target ? target._sentryId : undefined;\n }\n\n // Start a new debounce timer that will prevent us from capturing multiple events that should be grouped together.\n clearTimeout(debounceTimerID);\n debounceTimerID = WINDOW.setTimeout(() => {\n lastCapturedEventTargetId = undefined;\n lastCapturedEventType = undefined;\n }, DEBOUNCE_DURATION);\n };\n}\n\nfunction getEventTarget(event: Event): SentryWrappedTarget | null {\n try {\n return event.target as SentryWrappedTarget | null;\n } catch {\n // just accessing `target` property can throw an exception in some rare circumstances\n // see: https://github.com/getsentry/sentry-javascript/issues/838\n return null;\n }\n}\n"],"names":[],"mappings":"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"}
|
package/build/esm/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"module","version":"11.
|
|
1
|
+
{"type":"module","version":"11.3.0","sideEffects":false}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { LRUMap, getClient, safeCallback, debug, parseStringToURLObject } from '@sentry/core';
|
|
2
|
+
import { DEBUG_BUILD } from './debug-build.js';
|
|
3
|
+
import { getLocationHref } from './getLocationHref.js';
|
|
4
|
+
|
|
5
|
+
const CLIENT_ROUTE_PROVIDERS = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
function setRouteProvider(provider, client = getClient()) {
|
|
7
|
+
if (!client) {
|
|
8
|
+
DEBUG_BUILD && debug.warn("Cannot set a route provider without a client.");
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (DEBUG_BUILD && getRouteProvider(client)) {
|
|
12
|
+
debug.warn(
|
|
13
|
+
"A route provider is already registered for this client and will be replaced. Routes only the previous provider knows about will no longer resolve."
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
CLIENT_ROUTE_PROVIDERS.set(client, provider);
|
|
17
|
+
}
|
|
18
|
+
function getRouteProvider(client = getClient()) {
|
|
19
|
+
return client && CLIENT_ROUTE_PROVIDERS.get(client);
|
|
20
|
+
}
|
|
21
|
+
function resolveRoute(url, client = getClient()) {
|
|
22
|
+
const provider = getRouteProvider(client);
|
|
23
|
+
if (!provider) {
|
|
24
|
+
return void 0;
|
|
25
|
+
}
|
|
26
|
+
const urlObject = typeof url === "string" ? parseLocation(url) : url;
|
|
27
|
+
if (!urlObject) {
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
return safeCallback(
|
|
31
|
+
DEBUG_BUILD ? "Route provider threw while resolving a route:" : "",
|
|
32
|
+
() => provider.resolveRoute(urlObject) || void 0,
|
|
33
|
+
() => void 0
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
function resolveCurrentRoute(client = getClient()) {
|
|
37
|
+
const provider = getRouteProvider(client);
|
|
38
|
+
return safeCallback(
|
|
39
|
+
DEBUG_BUILD ? "Route provider threw while resolving a route:" : "",
|
|
40
|
+
() => provider?.resolveCurrentRoute() || void 0,
|
|
41
|
+
() => void 0
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
function createUrlRouteProvider(resolveRouteFromUrl) {
|
|
45
|
+
return {
|
|
46
|
+
resolveRoute: resolveRouteFromUrl,
|
|
47
|
+
resolveCurrentRoute: () => {
|
|
48
|
+
const urlObject = parseLocation(getLocationHref());
|
|
49
|
+
return urlObject && resolveRouteFromUrl(urlObject);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function createCachedRouteProvider(maxEntries = 50) {
|
|
54
|
+
const routeNames = new LRUMap(maxEntries);
|
|
55
|
+
return {
|
|
56
|
+
...createUrlRouteProvider((url) => routeNames.get(url.pathname)),
|
|
57
|
+
record(pathname, routeName) {
|
|
58
|
+
if (pathname && routeName) {
|
|
59
|
+
routeNames.set(pathname, routeName);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function parseLocation(url) {
|
|
65
|
+
return url ? parseStringToURLObject(url, getLocationHref() || void 0) : void 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { createCachedRouteProvider, createUrlRouteProvider, getRouteProvider, resolveCurrentRoute, resolveRoute, setRouteProvider };
|
|
69
|
+
//# sourceMappingURL=routing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing.js","sources":["../../src/routing.ts"],"sourcesContent":["import type { Client } from '@sentry/core';\nimport { debug, getClient, LRUMap, parseStringToURLObject, safeCallback } from '@sentry/core';\nimport { DEBUG_BUILD } from './debug-build';\nimport { getLocationHref } from './getLocationHref';\n\n/** The parts of a URL a route depends on. A `URL` satisfies it. */\ntype RouteUrl = Pick<URL, 'pathname' | 'search' | 'hash'>;\n\n/**\n * Resolves URLs to low-cardinality route names.\n *\n * Framework SDKs register one so that everything the SDK names after a route (span names, the scope's\n * transaction name, metric and span segment attributes) gets the parameterized route instead of the raw\n * URL, without each integration having to reach into the framework's router itself.\n */\nexport interface RouteProvider {\n /**\n * Resolves a URL path template for a specific URL, e.g. `/users/42` -> `/users/:id`.\n *\n * Must return a path template, never a route identifier. Routers that name routes independently of\n * their path (Vue Router's `route.name`, Ember's `posts.show`) have to return the matched path\n * instead: callers set `url.template` from this, and an identifier is not a template. An SDK that\n * wants to name its span after the identifier still can, on the span itself.\n *\n * Returns `undefined` when the URL matches no known route. Must answer for the URL it is given rather\n * than for wherever the router currently is, so that callers can resolve a URL they captured earlier\n * (a web vital reported after a soft navigation, for example).\n */\n resolveRoute(url: RouteUrl): string | undefined;\n\n /**\n * Resolves the route the app is currently on.\n *\n * Routers whose location lives in the address bar can delegate to `resolveRoute`, which is what\n * {@link createUrlRouteProvider} does. Routers that keep their own location (memory and hash routers)\n * have to answer from that location instead: for those, `location.href` is the unchanging shell URL\n * and would bucket every route together.\n */\n resolveCurrentRoute(): string | undefined;\n}\n\nconst CLIENT_ROUTE_PROVIDERS = new WeakMap<Client, RouteProvider>();\n\n/**\n * Registers the route provider for a client, replacing any previously registered one. `BrowserClient`\n * calls this with its `routeProvider` option.\n *\n * Prefer the `routeProvider` option where the provider is known at `init`: the pageload span is named\n * while `browserTracingIntegration` sets up, so a provider registered after `init` can only rename it\n * after the fact.\n *\n * A client holds one provider. An app running two routers (a framework migration, or a shell plus an\n * island) registers twice and the last one wins, so the first router's routes stop resolving.\n */\nexport function setRouteProvider(provider: RouteProvider, client: Client | undefined = getClient()): void {\n if (!client) {\n DEBUG_BUILD && debug.warn('Cannot set a route provider without a client.');\n return;\n }\n\n if (DEBUG_BUILD && getRouteProvider(client)) {\n debug.warn(\n 'A route provider is already registered for this client and will be replaced. Routes only the previous provider knows about will no longer resolve.',\n );\n }\n\n CLIENT_ROUTE_PROVIDERS.set(client, provider);\n}\n\n/**\n * Returns the route provider registered for a client.\n */\nexport function getRouteProvider(client: Client | undefined = getClient()): RouteProvider | undefined {\n return client && CLIENT_ROUTE_PROVIDERS.get(client);\n}\n\n/**\n * Resolves a URL to a low-cardinality route name, e.g. `/users/42` -> `/users/:id`.\n *\n * Returns `undefined` when no route provider is registered or the URL matches no route. Callers pick\n * their own fallback, because the right one differs: a span name falls back to a low-cardinality\n * constant, the scope's transaction name to the raw path.\n */\nexport function resolveRoute(url: string | URL, client: Client | undefined = getClient()): string | undefined {\n const provider = getRouteProvider(client);\n if (!provider) {\n return undefined;\n }\n\n const urlObject = typeof url === 'string' ? parseLocation(url) : url;\n if (!urlObject) {\n return undefined;\n }\n\n return safeCallback(\n DEBUG_BUILD ? 'Route provider threw while resolving a route:' : '',\n () => provider.resolveRoute(urlObject) || undefined,\n () => undefined,\n );\n}\n\n/**\n * Resolves the route the app is currently on.\n *\n * Returns `undefined` when no route provider is registered or the current location matches no route.\n */\nexport function resolveCurrentRoute(client: Client | undefined = getClient()): string | undefined {\n const provider = getRouteProvider(client);\n\n return safeCallback(\n DEBUG_BUILD ? 'Route provider threw while resolving a route:' : '',\n () => provider?.resolveCurrentRoute() || undefined,\n () => undefined,\n );\n}\n\n/**\n * Builds a {@link RouteProvider} for a router whose location is the browser's, which covers every\n * router except memory and hash routers.\n */\nexport function createUrlRouteProvider(resolveRouteFromUrl: (url: RouteUrl) => string | undefined): RouteProvider {\n return {\n resolveRoute: resolveRouteFromUrl,\n resolveCurrentRoute: () => {\n const urlObject = parseLocation(getLocationHref());\n\n return urlObject && resolveRouteFromUrl(urlObject);\n },\n };\n}\n\n/**\n * A {@link RouteProvider} that answers from routes it has been told about, rather than by matching.\n */\nexport interface CachedRouteProvider extends RouteProvider {\n /** Records the route name a router reported for a path. Ignores empty values. */\n record(pathname: string | undefined, routeName: string | null | undefined): void;\n}\n\n/**\n * Builds a route provider for a router with no usable matcher, which can only report the route it is\n * on as it gets there (SvelteKit's `page.route.id`, Solid Router's current matches).\n *\n * A URL the app has not visited resolves to `undefined`, which includes the first pageload until the\n * router reports. Backed by an LRU so a long-lived app visiting many URLs can't grow it without end,\n * and so routes that keep being resolved outlive ones passed through once.\n */\nexport function createCachedRouteProvider(maxEntries: number = 50): CachedRouteProvider {\n const routeNames = new LRUMap<string, string>(maxEntries);\n\n return {\n ...createUrlRouteProvider(url => routeNames.get(url.pathname)),\n record(pathname, routeName) {\n if (pathname && routeName) {\n routeNames.set(pathname, routeName);\n }\n },\n };\n}\n\n/**\n * Parses up front so providers never have to, and resolves relative locations (which memory routers\n * hand around) against the document.\n */\nfunction parseLocation(url: string): RouteUrl | undefined {\n // Without a document `getLocationHref()` is empty, which would otherwise parse as `/`.\n return url ? parseStringToURLObject(url, getLocationHref() || undefined) : undefined;\n}\n"],"names":[],"mappings":";;;;AAyCA,MAAM,sBAAA,uBAA6B,OAAA,EAA+B;AAa3D,SAAS,gBAAA,CAAiB,QAAA,EAAyB,MAAA,GAA6B,SAAA,EAAU,EAAS;AACxG,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,WAAA,IAAe,KAAA,CAAM,KAAK,+CAA+C,CAAA;AACzE,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,WAAA,IAAe,gBAAA,CAAiB,MAAM,CAAA,EAAG;AAC3C,IAAA,KAAA,CAAM,IAAA;AAAA,MACJ;AAAA,KACF;AAAA,EACF;AAEA,EAAA,sBAAA,CAAuB,GAAA,CAAI,QAAQ,QAAQ,CAAA;AAC7C;AAKO,SAAS,gBAAA,CAAiB,MAAA,GAA6B,SAAA,EAAU,EAA8B;AACpG,EAAA,OAAO,MAAA,IAAU,sBAAA,CAAuB,GAAA,CAAI,MAAM,CAAA;AACpD;AASO,SAAS,YAAA,CAAa,GAAA,EAAmB,MAAA,GAA6B,SAAA,EAAU,EAAuB;AAC5G,EAAA,MAAM,QAAA,GAAW,iBAAiB,MAAM,CAAA;AACxC,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,YAAY,OAAO,GAAA,KAAQ,QAAA,GAAW,aAAA,CAAc,GAAG,CAAA,GAAI,GAAA;AACjE,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,OAAO,YAAA;AAAA,IACL,cAAc,+CAAA,GAAkD,EAAA;AAAA,IAChE,MAAM,QAAA,CAAS,YAAA,CAAa,SAAS,CAAA,IAAK,MAAA;AAAA,IAC1C,MAAM;AAAA,GACR;AACF;AAOO,SAAS,mBAAA,CAAoB,MAAA,GAA6B,SAAA,EAAU,EAAuB;AAChG,EAAA,MAAM,QAAA,GAAW,iBAAiB,MAAM,CAAA;AAExC,EAAA,OAAO,YAAA;AAAA,IACL,cAAc,+CAAA,GAAkD,EAAA;AAAA,IAChE,MAAM,QAAA,EAAU,mBAAA,EAAoB,IAAK,MAAA;AAAA,IACzC,MAAM;AAAA,GACR;AACF;AAMO,SAAS,uBAAuB,mBAAA,EAA2E;AAChH,EAAA,OAAO;AAAA,IACL,YAAA,EAAc,mBAAA;AAAA,IACd,qBAAqB,MAAM;AACzB,MAAA,MAAM,SAAA,GAAY,aAAA,CAAc,eAAA,EAAiB,CAAA;AAEjD,MAAA,OAAO,SAAA,IAAa,oBAAoB,SAAS,CAAA;AAAA,IACnD;AAAA,GACF;AACF;AAkBO,SAAS,yBAAA,CAA0B,aAAqB,EAAA,EAAyB;AACtF,EAAA,MAAM,UAAA,GAAa,IAAI,MAAA,CAAuB,UAAU,CAAA;AAExD,EAAA,OAAO;AAAA,IACL,GAAG,sBAAA,CAAuB,CAAA,GAAA,KAAO,WAAW,GAAA,CAAI,GAAA,CAAI,QAAQ,CAAC,CAAA;AAAA,IAC7D,MAAA,CAAO,UAAU,SAAA,EAAW;AAC1B,MAAA,IAAI,YAAY,SAAA,EAAW;AACzB,QAAA,UAAA,CAAW,GAAA,CAAI,UAAU,SAAS,CAAA;AAAA,MACpC;AAAA,IACF;AAAA,GACF;AACF;AAMA,SAAS,cAAc,GAAA,EAAmC;AAExD,EAAA,OAAO,MAAM,sBAAA,CAAuB,GAAA,EAAK,eAAA,EAAgB,IAAK,MAAS,CAAA,GAAI,MAAA;AAC7E;;;;"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getRootSpan, spanToJSON, getCurrentScope, SEMANTIC_ATTRIBUTE_SENTRY_OP,
|
|
1
|
+
import { getRootSpan, spanToJSON, getCurrentScope, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getClient } from '@sentry/core';
|
|
2
2
|
import { startInactiveSpan } from '@sentry/core/browser';
|
|
3
|
-
import { USER_AGENT_ORIGINAL, BROWSER_NAVIGATION_ID, BROWSER_NAVIGATION_TYPE, SENTRY_SEGMENT_NAME, SENTRY_TRANSACTION, SENTRY_REPLAY_ID } from '@sentry/conventions/attributes';
|
|
3
|
+
import { USER_AGENT_ORIGINAL, BROWSER_NAVIGATION_ID, BROWSER_NAVIGATION_TYPE, SENTRY_SEGMENT_NAME, SENTRY_TRANSACTION, SENTRY_EXCLUSIVE_TIME, SENTRY_REPLAY_ID } from '@sentry/conventions/attributes';
|
|
4
4
|
import { WINDOW } from '../types.js';
|
|
5
5
|
|
|
6
6
|
function _emitWebVitalSpan(options) {
|
|
@@ -23,7 +23,7 @@ function _emitWebVitalSpan(options) {
|
|
|
23
23
|
const attributes = {
|
|
24
24
|
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: origin,
|
|
25
25
|
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
|
|
26
|
-
[
|
|
26
|
+
[SENTRY_EXCLUSIVE_TIME]: 0,
|
|
27
27
|
[`browser.web_vital.${metricName}.value`]: value,
|
|
28
28
|
// oxlint-disable-next-line typescript-eslint/no-deprecated
|
|
29
29
|
[SENTRY_TRANSACTION]: segmentName,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emitSpan.js","sources":["../../../src/web-vitals/emitSpan.ts"],"sourcesContent":["import type { Integration, Span, SpanAttributes } from '@sentry/core';\nimport {\n getClient,\n getCurrentScope,\n getRootSpan,\n
|
|
1
|
+
{"version":3,"file":"emitSpan.js","sources":["../../../src/web-vitals/emitSpan.ts"],"sourcesContent":["import type { Integration, Span, SpanAttributes } from '@sentry/core';\nimport {\n getClient,\n getCurrentScope,\n getRootSpan,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n spanToJSON,\n} from '@sentry/core';\nimport { startInactiveSpan } from '@sentry/core/browser';\nimport {\n SENTRY_EXCLUSIVE_TIME,\n BROWSER_NAVIGATION_ID,\n BROWSER_NAVIGATION_TYPE,\n SENTRY_REPLAY_ID,\n SENTRY_SEGMENT_NAME,\n SENTRY_TRANSACTION,\n USER_AGENT_ORIGINAL,\n} from '@sentry/conventions/attributes';\nimport { WINDOW } from '../types';\nimport type { MetricNavigationType } from '../instrumentation/performanceObserver';\n\n// Locally-defined interfaces to avoid leaking bare global type references into the\n// generated .d.ts. The `declare global` augmentations in web-vitals/types.ts make these\n// available during this package's compilation but are NOT carried to consumers.\n// This mirrors the pattern used for PerformanceEventTiming in instrument.ts.\nexport interface LayoutShift extends PerformanceEntry {\n value: number;\n sources: Array<{ node: Node | null }>;\n hadRecentInput: boolean;\n}\n\nexport interface LargestContentfulPaint extends PerformanceEntry {\n readonly renderTime: DOMHighResTimeStamp;\n readonly loadTime: DOMHighResTimeStamp;\n readonly size: number;\n readonly id: string;\n readonly url: string;\n readonly element: Element | null;\n}\n\ninterface WebVitalSpanOptions {\n name: string;\n op: string;\n origin: string;\n metricName: 'lcp' | 'cls' | 'inp';\n value: number;\n attributes?: SpanAttributes;\n parentSpan?: Span;\n startTime: number;\n endTime?: number;\n /** Set when the vital was reported for a soft navigation rather than the initial page load. */\n softNavigationId?: number;\n /** The navigation the vital was measured on, as reported by web-vitals. */\n navigationType?: MetricNavigationType;\n /**\n * When `true`, the span is sent on its own as a v2 streamed span instead of being folded into a\n * transaction. Used for INP when span streaming is disabled (it reports late, so it can't ride\n * the pageload transaction).\n *\n * TODO(standalone): remove once the static (transaction) trace lifecycle is dropped and INP always streams.\n */\n standalone?: boolean;\n}\n\n/**\n * Emits a web vital span. When `standalone` is set it is sent on its own as a v2 streamed span;\n * otherwise it flows through the span streaming pipeline as a child of `parentSpan`.\n */\nexport function _emitWebVitalSpan(options: WebVitalSpanOptions): void {\n const {\n name,\n op,\n origin,\n metricName,\n value,\n attributes: passedAttributes,\n parentSpan,\n startTime,\n endTime,\n standalone,\n softNavigationId,\n navigationType,\n } = options;\n\n // Taken off the segment span itself, so it can't diverge from it: a routing instrumentation may\n // rename that span (a pageload span is named `Pageload` until its route resolves), and the scope's\n // transaction name is deliberately not kept in sync with it. Only a standalone span, which is sent\n // without its segment span, has to fall back to the scope.\n const segmentSpan = parentSpan && getRootSpan(parentSpan);\n const segmentName = segmentSpan ? spanToJSON(segmentSpan).name : getCurrentScope().getScopeData().transactionName;\n\n const attributes: SpanAttributes = {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: origin,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,\n [SENTRY_EXCLUSIVE_TIME]: 0,\n [`browser.web_vital.${metricName}.value`]: value,\n // oxlint-disable-next-line typescript-eslint/no-deprecated\n [SENTRY_TRANSACTION]: segmentName,\n [SENTRY_SEGMENT_NAME]: segmentName,\n // Web vital score calculation relies on the user agent\n [USER_AGENT_ORIGINAL]: WINDOW.navigator?.userAgent,\n ...passedAttributes,\n };\n\n if (parentSpan && spanToJSON(parentSpan).attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] === 'pageload') {\n // for LCP and CLS, we collect the pageload span id as an attribute\n attributes['sentry.pageload.span_id'] = parentSpan.spanContext().spanId;\n }\n\n if (softNavigationId != null) {\n attributes[BROWSER_NAVIGATION_ID] = softNavigationId;\n }\n\n if (navigationType) {\n attributes[BROWSER_NAVIGATION_TYPE] = navigationType;\n }\n\n // A standalone span is sent as a plain v2 span without running the `processSpan` hooks (see\n // `captureStandaloneSpanWithStaticCallback`), so Replay can't attach the replay id itself. Set it\n // here, mirroring Replay's `processSpan`, so INP keeps its replay association like it did on v1.\n // TODO(standalone): remove once the static (transaction) trace lifecycle is dropped and INP always\n // streams, at which point Replay's `processSpan` runs and attaches the replay id.\n if (standalone) {\n Object.assign(attributes, getReplayAttributes());\n }\n\n const span = startInactiveSpan({\n name,\n attributes,\n startTime,\n parentSpan,\n // oxlint-disable-next-line typescript/no-deprecated -- intentional during the v1/v2 transition; see the TODO(standalone) above\n experimental: standalone ? { standalone: true } : undefined,\n });\n\n if (span) {\n span.end(endTime ?? startTime);\n }\n}\n\ninterface ReplayIntegration extends Integration {\n getReplayId: (onlyIfSampled?: boolean) => string | undefined;\n getRecordingMode: () => 'session' | 'buffer' | undefined;\n}\n\n// TODO(standalone): remove once the static (transaction) trace lifecycle is dropped; Replay's\n// `processSpan` then attaches the replay id to the streamed INP span instead.\nfunction getReplayAttributes(): SpanAttributes {\n const replay = getClient()?.getIntegrationByName<ReplayIntegration>('Replay');\n const replayId = replay?.getReplayId(true);\n if (!replayId) {\n return {};\n }\n\n return {\n [SENTRY_REPLAY_ID]: replayId,\n // Not the `SENTRY_REPLAY_IS_BUFFERING` convention: that one has no `sentry._internal.` prefix, and\n // the rest of the SDK sets the prefixed key (see `logs/internal.ts`, `metrics/internal.ts`).\n 'sentry._internal.replay_is_buffering': replay!.getRecordingMode() === 'buffer' ? true : undefined,\n };\n}\n"],"names":[],"mappings":";;;;;AAqEO,SAAS,kBAAkB,OAAA,EAAoC;AACpE,EAAA,MAAM;AAAA,IACJ,IAAA;AAAA,IACA,EAAA;AAAA,IACA,MAAA;AAAA,IACA,UAAA;AAAA,IACA,KAAA;AAAA,IACA,UAAA,EAAY,gBAAA;AAAA,IACZ,UAAA;AAAA,IACA,SAAA;AAAA,IACA,OAAA;AAAA,IACA,UAAA;AAAA,IACA,gBAAA;AAAA,IACA;AAAA,GACF,GAAI,OAAA;AAMJ,EAAA,MAAM,WAAA,GAAc,UAAA,IAAc,WAAA,CAAY,UAAU,CAAA;AACxD,EAAA,MAAM,WAAA,GAAc,cAAc,UAAA,CAAW,WAAW,EAAE,IAAA,GAAO,eAAA,EAAgB,CAAE,YAAA,EAAa,CAAE,eAAA;AAElG,EAAA,MAAM,UAAA,GAA6B;AAAA,IACjC,CAAC,gCAAgC,GAAG,MAAA;AAAA,IACpC,CAAC,4BAA4B,GAAG,EAAA;AAAA,IAChC,CAAC,qBAAqB,GAAG,CAAA;AAAA,IACzB,CAAC,CAAA,kBAAA,EAAqB,UAAU,CAAA,MAAA,CAAQ,GAAG,KAAA;AAAA;AAAA,IAE3C,CAAC,kBAAkB,GAAG,WAAA;AAAA,IACtB,CAAC,mBAAmB,GAAG,WAAA;AAAA;AAAA,IAEvB,CAAC,mBAAmB,GAAG,MAAA,CAAO,SAAA,EAAW,SAAA;AAAA,IACzC,GAAG;AAAA,GACL;AAEA,EAAA,IAAI,cAAc,UAAA,CAAW,UAAU,EAAE,UAAA,CAAW,4BAA4B,MAAM,UAAA,EAAY;AAEhG,IAAA,UAAA,CAAW,yBAAyB,CAAA,GAAI,UAAA,CAAW,WAAA,EAAY,CAAE,MAAA;AAAA,EACnE;AAEA,EAAA,IAAI,oBAAoB,IAAA,EAAM;AAC5B,IAAA,UAAA,CAAW,qBAAqB,CAAA,GAAI,gBAAA;AAAA,EACtC;AAEA,EAAA,IAAI,cAAA,EAAgB;AAClB,IAAA,UAAA,CAAW,uBAAuB,CAAA,GAAI,cAAA;AAAA,EACxC;AAOA,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,mBAAA,EAAqB,CAAA;AAAA,EACjD;AAEA,EAAA,MAAM,OAAO,iBAAA,CAAkB;AAAA,IAC7B,IAAA;AAAA,IACA,UAAA;AAAA,IACA,SAAA;AAAA,IACA,UAAA;AAAA;AAAA,IAEA,YAAA,EAAc,UAAA,GAAa,EAAE,UAAA,EAAY,MAAK,GAAI;AAAA,GACnD,CAAA;AAED,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,IAAA,CAAK,GAAA,CAAI,WAAW,SAAS,CAAA;AAAA,EAC/B;AACF;AASA,SAAS,mBAAA,GAAsC;AAC7C,EAAA,MAAM,MAAA,GAAS,SAAA,EAAU,EAAG,oBAAA,CAAwC,QAAQ,CAAA;AAC5E,EAAA,MAAM,QAAA,GAAW,MAAA,EAAQ,WAAA,CAAY,IAAI,CAAA;AACzC,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,OAAO;AAAA,IACL,CAAC,gBAAgB,GAAG,QAAA;AAAA;AAAA;AAAA,IAGpB,sCAAA,EAAwC,MAAA,CAAQ,gBAAA,EAAiB,KAAM,WAAW,IAAA,GAAO;AAAA,GAC3F;AACF;;;;"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { browserPerformanceTimeOrigin, hasSpanStreamingEnabled, spanToJSON, SEMANTIC_ATTRIBUTE_SENTRY_OP, debug, getClient, timestampInSeconds, getActiveSpan, getRootSpan,
|
|
1
|
+
import { browserPerformanceTimeOrigin, hasSpanStreamingEnabled, spanToJSON, SEMANTIC_ATTRIBUTE_SENTRY_OP, debug, getClient, timestampInSeconds, getActiveSpan, getRootSpan, UI_WEBVITAL_CLS_SPAN_NAME_FALLBACK, UI_INTERACTION_PRESS_SPAN_NAME_FALLBACK, UI_INTERACTION_DRAG_SPAN_NAME_FALLBACK, UI_INTERACTION_HOVER_SPAN_NAME_FALLBACK, UI_INTERACTION_CLICK_SPAN_NAME_FALLBACK, UI_WEBVITAL_LCP_SPAN_NAME_FALLBACK } from '@sentry/core';
|
|
2
2
|
import { DEBUG_BUILD } from '../debug-build.js';
|
|
3
3
|
import { htmlTreeAsString } from '../htmlTreeAsString.js';
|
|
4
4
|
import { getComponentName } from '../component-name.js';
|
|
5
5
|
import { MAX_PLAUSIBLE_INP_DURATION, INP_ENTRY_MAP, getCachedInteractionContext, UNKNOWN_ELEMENT_NAME } from './inp.js';
|
|
6
6
|
import { addClsInstrumentationHandler, addInpInstrumentationHandler, addLcpInstrumentationHandler } from '../instrumentation/performanceObserver.js';
|
|
7
|
-
import { BROWSER_NAVIGATION_TYPE, UI_COMPONENT_NAME, BROWSER_WEB_VITAL_INP_TARGET, BROWSER_WEB_VITAL_INP_INTERACTION_TYPE } from '@sentry/conventions/attributes';
|
|
7
|
+
import { BROWSER_NAVIGATION_TYPE, UI_COMPONENT_NAME, SENTRY_EXCLUSIVE_TIME, BROWSER_WEB_VITAL_INP_TARGET, BROWSER_WEB_VITAL_INP_INTERACTION_TYPE } from '@sentry/conventions/attributes';
|
|
8
8
|
import { _emitWebVitalSpan } from './emitSpan.js';
|
|
9
9
|
import { isValidLcpMetric } from './lcp.js';
|
|
10
10
|
import { listenForWebVitalReportEvents } from './reportEvents.js';
|
|
@@ -241,7 +241,7 @@ function _sendInpSpan(inpValue, entry, standalone = false, attributedSpan, softN
|
|
|
241
241
|
const fallbackName = INTERACTION_TYPE_TO_SPAN_NAME_FALLBACK[interactionType];
|
|
242
242
|
const name = hasSpanStreaming ? componentName || fallbackName : selector ?? "Interaction to next paint";
|
|
243
243
|
const attributes = {
|
|
244
|
-
[
|
|
244
|
+
[SENTRY_EXCLUSIVE_TIME]: entry?.duration ?? inpValue
|
|
245
245
|
};
|
|
246
246
|
selector && selector !== UNKNOWN_ELEMENT_NAME && (attributes[BROWSER_WEB_VITAL_INP_TARGET] = selector);
|
|
247
247
|
entryInteractionType && (attributes[BROWSER_WEB_VITAL_INP_INTERACTION_TYPE] = entryInteractionType);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spans.js","sources":["../../../src/web-vitals/spans.ts"],"sourcesContent":["/* eslint-disable max-lines */\nimport type { Client, Span, SpanAttributes } from '@sentry/core';\nimport {\n browserPerformanceTimeOrigin,\n debug,\n getActiveSpan,\n getClient,\n getRootSpan,\n hasSpanStreamingEnabled,\n SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n spanToJSON,\n timestampInSeconds,\n UI_INTERACTION_CLICK_SPAN_NAME_FALLBACK,\n UI_INTERACTION_DRAG_SPAN_NAME_FALLBACK,\n UI_INTERACTION_HOVER_SPAN_NAME_FALLBACK,\n UI_INTERACTION_PRESS_SPAN_NAME_FALLBACK,\n UI_WEBVITAL_CLS_SPAN_NAME_FALLBACK,\n UI_WEBVITAL_LCP_SPAN_NAME_FALLBACK,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\nimport { htmlTreeAsString } from '../htmlTreeAsString';\nimport { getComponentName } from '../component-name';\nimport type { InteractionType } from './inp';\nimport { getCachedInteractionContext, INP_ENTRY_MAP, MAX_PLAUSIBLE_INP_DURATION, UNKNOWN_ELEMENT_NAME } from './inp';\nimport type { InstrumentationHandlerCallback, MetricNavigationType } from '../instrumentation/performanceObserver';\nimport {\n addClsInstrumentationHandler,\n addInpInstrumentationHandler,\n addLcpInstrumentationHandler,\n} from '../instrumentation/performanceObserver';\nimport type { LargestContentfulPaint, LayoutShift } from './emitSpan';\nimport {\n BROWSER_NAVIGATION_TYPE,\n BROWSER_WEB_VITAL_INP_INTERACTION_TYPE,\n BROWSER_WEB_VITAL_INP_TARGET,\n UI_COMPONENT_NAME,\n} from '@sentry/conventions/attributes';\nimport { _emitWebVitalSpan } from './emitSpan';\nimport { isValidLcpMetric } from './lcp';\nimport { listenForWebVitalReportEvents } from './reportEvents';\nimport { getNavigationSpanForMetric } from './softNavs';\nimport { getBrowserPerformanceAPI, msToSec, supportsWebVital } from '../performance/utils';\nimport type { PerformanceEventTiming } from '../instrumentation/performanceObserver';\nimport {\n NAVIGATION,\n UI_INTERACTION_CLICK,\n UI_INTERACTION_DRAG,\n UI_INTERACTION_HOVER,\n UI_INTERACTION_PRESS,\n UI_WEBVITAL_CLS,\n UI_WEBVITAL_LCP,\n} from '@sentry/conventions/op';\n\nconst INTERACTION_TYPE_TO_SPAN_OP: Record<InteractionType, string> = {\n click: UI_INTERACTION_CLICK,\n hover: UI_INTERACTION_HOVER,\n drag: UI_INTERACTION_DRAG,\n press: UI_INTERACTION_PRESS,\n};\n\nconst INTERACTION_TYPE_TO_SPAN_NAME_FALLBACK: Record<InteractionType, string> = {\n click: UI_INTERACTION_CLICK_SPAN_NAME_FALLBACK,\n hover: UI_INTERACTION_HOVER_SPAN_NAME_FALLBACK,\n drag: UI_INTERACTION_DRAG_SPAN_NAME_FALLBACK,\n press: UI_INTERACTION_PRESS_SPAN_NAME_FALLBACK,\n};\n\ntype WebVitalMetric = Parameters<Parameters<typeof addLcpInstrumentationHandler>[0]>[0]['metric'];\ntype InpMetric = Parameters<InstrumentationHandlerCallback>[0]['metric'];\n\n/**\n * Reports a web vital once per navigation, rather than once per page load.\n *\n * web-vitals restarts the metric on every soft navigation and force-reports the previous one just\n * before it does (and again on pagehide), and re-reports every metric after a bfcache restore.\n * Since `reportAllChanges` is off in this mode, every value we're handed is already the final one\n * for its navigation, so there is nothing to accumulate: each report is a span.\n */\nfunction trackWebVitalPerNavigation<M extends WebVitalMetric>(\n client: Client,\n addInstrumentationHandler: (callback: (data: { metric: M }) => void) => unknown,\n send: (metric: M, parentSpan: Span | undefined, softNavigationId: number | undefined) => void,\n): void {\n let pageloadSpan: Span | undefined;\n client.on('afterStartPageLoadSpan', span => {\n pageloadSpan = span;\n });\n\n // Remembered when the restore happens rather than read back at report time: the restore\n // navigation span is an idle span, and CLS and INP are only finalized on pagehide, by which point\n // it has long ended and is no longer what is active.\n let bfcacheNavigationSpan: Span | undefined;\n client.on('spanStart', span => {\n // The op has to be checked too: the web vital spans emitted for a restore carry the same\n // `back-forward-cache` navigation type, so matching on that alone lets the first of them replace the\n // navigation span, and every later vital then hangs off a sibling vital instead.\n const attributes = spanToJSON(span).attributes;\n if (\n attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] === NAVIGATION &&\n attributes[BROWSER_NAVIGATION_TYPE] === 'back-forward-cache'\n ) {\n bfcacheNavigationSpan = span;\n }\n });\n\n addInstrumentationHandler(({ metric }) => {\n const navigationSpan = getNavigationSpanForMetric(metric);\n if (metric.navigationType === 'soft-navigation') {\n // Reporting an uncorrelated soft navigation vital would attribute it to the wrong route, so\n // it's dropped instead.\n if (navigationSpan) {\n send(metric, navigationSpan, metric.navigationId);\n } else {\n DEBUG_BUILD &&\n debug.log(`[SoftNav] Dropping ${metric.name} for uncorrelated soft navigation ${metric.navigationId}`);\n }\n return;\n }\n\n if (metric.navigationType === 'back-forward-cache') {\n // A restore reuses the frozen document, so the pageload span above belongs to the page view\n // from before the freeze. The navigation span started for the restore is the page view these\n // values were actually measured on.\n send(metric, bfcacheNavigationSpan, undefined);\n return;\n }\n\n send(metric, pageloadSpan, undefined);\n });\n}\n\n/**\n * Tracks LCP as a streamed span.\n */\nexport function trackLcpAsSpan(client: Client, perNavigation = false): void {\n if (!supportsWebVital('largest-contentful-paint')) {\n return;\n }\n\n if (perNavigation) {\n trackWebVitalPerNavigation(client, addLcpInstrumentationHandler, (metric, parentSpan, softNavigationId) => {\n const entry = metric.entries[metric.entries.length - 1] as LargestContentfulPaint | undefined;\n _sendLcpSpan(\n metric.value,\n entry,\n parentSpan,\n softNavigationId,\n metric.navigationType,\n metric.navigationStartTime,\n );\n });\n return;\n }\n\n let lcpValue = 0;\n let lcpEntry: LargestContentfulPaint | undefined;\n let lcpNavigationType: MetricNavigationType | undefined;\n\n const cleanupLcpHandler = addLcpInstrumentationHandler(({ metric }) => {\n // The navigation type describes the page, not the entry, so it is worth keeping even for a\n // report we otherwise discard.\n lcpNavigationType = metric.navigationType;\n\n const entry = metric.entries[metric.entries.length - 1] as LargestContentfulPaint | undefined;\n if (!entry || !isValidLcpMetric(metric.value)) {\n return;\n }\n lcpValue = metric.value;\n lcpEntry = entry;\n }, true);\n\n listenForWebVitalReportEvents(client, pageloadSpan => {\n _sendLcpSpan(lcpValue, lcpEntry, pageloadSpan, undefined, lcpNavigationType);\n cleanupLcpHandler();\n });\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendLcpSpan(\n lcpValue: number,\n entry: LargestContentfulPaint | undefined,\n pageloadSpan?: Span,\n softNavigationId?: number,\n navigationType?: MetricNavigationType,\n navigationStartTime?: number,\n): void {\n if (!isValidLcpMetric(lcpValue)) {\n return;\n }\n\n DEBUG_BUILD && debug.log(`Sending LCP span (${lcpValue})`);\n\n const performanceTimeOrigin = browserPerformanceTimeOrigin() || 0;\n // A soft navigation's LCP is measured from the triggering interaction, not the document time\n // origin. Starting the span there too keeps it inside the navigation span it is parented to and\n // keeps its duration equal to the value it reports.\n const startTime = msToSec(performanceTimeOrigin + (navigationStartTime || 0));\n // Without an entry there is no render time to end at, so the span lasts the value it reports,\n // like an entry-less INP does. Ending at the time origin instead would invert the span.\n const endTime = entry ? msToSec(performanceTimeOrigin + entry.startTime) : startTime + msToSec(lcpValue);\n const selector = entry ? htmlTreeAsString(entry.element) : undefined;\n const componentName = entry?.element ? getComponentName(entry.element) : null;\n const client = getClient();\n const hasSpanStreaming = !!client && hasSpanStreamingEnabled(client);\n const name = hasSpanStreaming\n ? componentName || UI_WEBVITAL_LCP_SPAN_NAME_FALLBACK\n : (selector ?? UI_WEBVITAL_LCP_SPAN_NAME_FALLBACK);\n\n const attributes: SpanAttributes = {};\n\n if (selector) {\n attributes['browser.web_vital.lcp.element'] = selector;\n }\n if (componentName) {\n attributes[UI_COMPONENT_NAME] = componentName;\n }\n entry?.id && (attributes['browser.web_vital.lcp.id'] = entry.id);\n entry?.url && (attributes['browser.web_vital.lcp.url'] = entry.url);\n entry?.loadTime != null && (attributes['browser.web_vital.lcp.load_time'] = entry.loadTime);\n entry?.renderTime != null && (attributes['browser.web_vital.lcp.render_time'] = entry.renderTime);\n entry?.size != null && (attributes['browser.web_vital.lcp.size'] = entry.size);\n\n _emitWebVitalSpan({\n name,\n op: UI_WEBVITAL_LCP,\n origin: 'auto.http.browser.lcp',\n metricName: 'lcp',\n value: lcpValue,\n attributes,\n parentSpan: pageloadSpan,\n startTime,\n endTime,\n softNavigationId,\n navigationType,\n });\n}\n\n/**\n * Tracks CLS as a streamed span.\n */\nexport function trackClsAsSpan(client: Client, perNavigation = false): void {\n if (!supportsWebVital('layout-shift')) {\n return;\n }\n\n if (perNavigation) {\n trackWebVitalPerNavigation(client, addClsInstrumentationHandler, (metric, parentSpan, softNavigationId) => {\n const entry = metric.entries[metric.entries.length - 1] as LayoutShift | undefined;\n _sendClsSpan(\n metric.value,\n entry,\n parentSpan,\n softNavigationId,\n metric.navigationType,\n metric.navigationStartTime,\n );\n });\n return;\n }\n\n let clsValue = 0;\n let clsEntry: LayoutShift | undefined;\n let clsNavigationType: MetricNavigationType | undefined;\n\n const cleanupClsHandler = addClsInstrumentationHandler(({ metric }) => {\n // A CLS of 0 is reported with no entries and still emits a span, so the navigation type has to\n // be captured before the entry check rather than alongside the value.\n clsNavigationType = metric.navigationType;\n\n const entry = metric.entries[metric.entries.length - 1] as LayoutShift | undefined;\n if (!entry) {\n return;\n }\n clsValue = metric.value;\n clsEntry = entry;\n }, true);\n\n listenForWebVitalReportEvents(client, pageloadSpan => {\n _sendClsSpan(clsValue, clsEntry, pageloadSpan, undefined, clsNavigationType);\n cleanupClsHandler();\n });\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendClsSpan(\n clsValue: number,\n entry: LayoutShift | undefined,\n pageloadSpan?: Span,\n softNavigationId?: number,\n navigationType?: MetricNavigationType,\n navigationStartTime?: number,\n): void {\n DEBUG_BUILD && debug.log(`Sending CLS span (${clsValue})`);\n\n const performanceTimeOrigin = browserPerformanceTimeOrigin();\n // A CLS of 0 has no shift to place the span at. It is reported when the navigation it was\n // measured on is already over - the next soft navigation, or pagehide - so the current time would\n // land it outside that navigation, on the route that follows it.\n const offset = entry?.startTime ?? navigationStartTime ?? 0;\n const startTime = performanceTimeOrigin ? msToSec(performanceTimeOrigin + offset) : timestampInSeconds();\n const firstSourceNode = entry?.sources[0]?.node;\n const selector = entry ? htmlTreeAsString(firstSourceNode) : undefined;\n const componentName = firstSourceNode ? getComponentName(firstSourceNode) : null;\n const client = getClient();\n const hasSpanStreaming = !!client && hasSpanStreamingEnabled(client);\n const name = hasSpanStreaming\n ? componentName || UI_WEBVITAL_CLS_SPAN_NAME_FALLBACK\n : (selector ?? UI_WEBVITAL_CLS_SPAN_NAME_FALLBACK);\n\n const attributes: SpanAttributes = {};\n\n if (componentName) {\n attributes[UI_COMPONENT_NAME] = componentName;\n }\n\n if (entry?.sources) {\n entry.sources.forEach((source, index) => {\n attributes[`browser.web_vital.cls.source.${index + 1}`] = htmlTreeAsString(source.node);\n });\n }\n\n _emitWebVitalSpan({\n name,\n op: UI_WEBVITAL_CLS,\n origin: 'auto.http.browser.cls',\n metricName: 'cls',\n value: clsValue,\n attributes,\n parentSpan: pageloadSpan,\n startTime,\n softNavigationId,\n navigationType,\n });\n}\n\n/**\n * Tracks INP and emits it as a web vital span, as a child of the interaction's root span.\n * Requires `registerInpInteractionListener()` to be called separately for cached element names and\n * root spans per interaction.\n */\nexport function trackInpAsSpan(client: Client, perNavigation = false): void {\n const performance = getBrowserPerformanceAPI();\n if (!performance || !browserPerformanceTimeOrigin()) {\n return;\n }\n\n // INP reports late (on pagehide, after the pageload span has ended). With span streaming enabled\n // it rides the streaming pipeline. With streaming disabled it would be dropped as a late span, so\n // it is emitted as its own standalone v2 span instead (see `_emitWebVitalSpan`), overriding the\n // static trace lifecycle for INP only.\n // TODO(standalone): once the static trace lifecycle is dropped, INP always streams; drop this flag.\n const standalone = !hasSpanStreamingEnabled(client);\n\n if (perNavigation) {\n // INP restarts per navigation and reports once that navigation is over, by which point the\n // navigation span has ended and the interaction cache no longer knows about it. The metric\n // says which navigation it belongs to, so INP is attributed exactly like LCP and CLS.\n trackWebVitalPerNavigation(client, addInpInstrumentationHandler, (metric, parentSpan, softNavigationId) => {\n if (isPlausibleInp(metric)) {\n _sendInpSpan(metric.value, findInpEntry(metric), standalone, parentSpan, softNavigationId, metric);\n }\n });\n return;\n }\n\n const onInp: InstrumentationHandlerCallback = ({ metric }) => {\n if (isPlausibleInp(metric)) {\n _sendInpSpan(metric.value, findInpEntry(metric), standalone, undefined, undefined, metric);\n }\n };\n\n addInpInstrumentationHandler(onInp);\n}\n\nfunction isPlausibleInp(metric: InpMetric): boolean {\n return metric.value != null && msToSec(metric.value) <= MAX_PLAUSIBLE_INP_DURATION;\n}\n\n/**\n * The entry an INP span is built from: the one whose duration the reported value came from.\n *\n * There isn't always one. When every interaction of a soft navigation stayed below the Event Timing\n * threshold, web-vitals reports a synthetic value with no entries at all - see\n * `_estimateP98LongestInteraction`. The span still gets reported in that case, just without the\n * element and interaction type an entry would have supplied.\n */\nfunction findInpEntry(metric: InpMetric): PerformanceEventTiming | undefined {\n return metric.entries.find(e => e.duration === metric.value && INP_ENTRY_MAP[e.name]);\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendInpSpan(\n inpValue: number,\n entry: PerformanceEventTiming | undefined,\n standalone = false,\n attributedSpan?: Span,\n softNavigationId?: number,\n metric?: InpMetric,\n): void {\n DEBUG_BUILD && debug.log(`Sending INP span (${inpValue})`);\n\n // A web vital span carries the metric, not a real interaction timing, so an INP without an entry\n // is still worth reporting. It just has no element or interaction type to describe, and is placed\n // at the start of the navigation it belongs to rather than at the interaction.\n const startTime = msToSec(\n (browserPerformanceTimeOrigin() as number) + (entry?.startTime ?? metric?.navigationStartTime ?? 0),\n );\n const duration = msToSec(inpValue);\n // An INP without an entry has no interaction type to report. It still has to land inside the\n // `ui.interaction.*` family, because falling outside it would hide exactly the fast navigations\n // that web-vitals synthesizes these values for (GoogleChrome/web-vitals#724), reintroducing the\n // reporting bias they were added to remove.\n const entryInteractionType = entry && INP_ENTRY_MAP[entry.name];\n const interactionType = entryInteractionType || 'click';\n\n const cachedContext = entry && getCachedInteractionContext(entry.interactionId);\n const activeSpan = getActiveSpan();\n const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;\n\n // With soft navigations the caller knows exactly which navigation the metric belongs to. Without\n // them we fall back to the span that was active when the interaction was observed.\n const spanToUse = attributedSpan || cachedContext?.span || rootSpan;\n const selector = cachedContext?.elementName || (entry ? htmlTreeAsString(entry.target) : undefined);\n const componentName = entry?.target ? getComponentName(entry.target) : null;\n const client = getClient();\n const hasSpanStreaming = !!client && hasSpanStreamingEnabled(client);\n const fallbackName = INTERACTION_TYPE_TO_SPAN_NAME_FALLBACK[interactionType];\n const name = hasSpanStreaming ? componentName || fallbackName : (selector ?? 'Interaction to next paint');\n\n const attributes: SpanAttributes = {\n [SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry?.duration ?? inpValue,\n };\n\n // The span's name and op always have a value, even for an INP without an entry, so they can't\n // say whether there was an interaction to describe. These attributes can: they are only set for\n // what was actually observed. The name no longer holds the selector either, now that it is the\n // component name or the op's fallback under span streaming.\n selector && selector !== UNKNOWN_ELEMENT_NAME && (attributes[BROWSER_WEB_VITAL_INP_TARGET] = selector);\n entryInteractionType && (attributes[BROWSER_WEB_VITAL_INP_INTERACTION_TYPE] = entryInteractionType);\n\n if (componentName) {\n attributes[UI_COMPONENT_NAME] = componentName;\n }\n\n _emitWebVitalSpan({\n name,\n op: INTERACTION_TYPE_TO_SPAN_OP[interactionType],\n origin: 'auto.http.browser.inp',\n metricName: 'inp',\n value: inpValue,\n attributes,\n startTime,\n endTime: startTime + duration,\n navigationType: metric?.navigationType,\n parentSpan: spanToUse,\n standalone,\n softNavigationId,\n });\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"spans.js","sources":["../../../src/web-vitals/spans.ts"],"sourcesContent":["/* eslint-disable max-lines */\nimport type { Client, Span, SpanAttributes } from '@sentry/core';\nimport {\n browserPerformanceTimeOrigin,\n debug,\n getActiveSpan,\n getClient,\n getRootSpan,\n hasSpanStreamingEnabled,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n spanToJSON,\n timestampInSeconds,\n UI_INTERACTION_CLICK_SPAN_NAME_FALLBACK,\n UI_INTERACTION_DRAG_SPAN_NAME_FALLBACK,\n UI_INTERACTION_HOVER_SPAN_NAME_FALLBACK,\n UI_INTERACTION_PRESS_SPAN_NAME_FALLBACK,\n UI_WEBVITAL_CLS_SPAN_NAME_FALLBACK,\n UI_WEBVITAL_LCP_SPAN_NAME_FALLBACK,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\nimport { htmlTreeAsString } from '../htmlTreeAsString';\nimport { getComponentName } from '../component-name';\nimport type { InteractionType } from './inp';\nimport { getCachedInteractionContext, INP_ENTRY_MAP, MAX_PLAUSIBLE_INP_DURATION, UNKNOWN_ELEMENT_NAME } from './inp';\nimport type { InstrumentationHandlerCallback, MetricNavigationType } from '../instrumentation/performanceObserver';\nimport {\n addClsInstrumentationHandler,\n addInpInstrumentationHandler,\n addLcpInstrumentationHandler,\n} from '../instrumentation/performanceObserver';\nimport type { LargestContentfulPaint, LayoutShift } from './emitSpan';\nimport {\n SENTRY_EXCLUSIVE_TIME,\n BROWSER_NAVIGATION_TYPE,\n BROWSER_WEB_VITAL_INP_INTERACTION_TYPE,\n BROWSER_WEB_VITAL_INP_TARGET,\n UI_COMPONENT_NAME,\n} from '@sentry/conventions/attributes';\nimport { _emitWebVitalSpan } from './emitSpan';\nimport { isValidLcpMetric } from './lcp';\nimport { listenForWebVitalReportEvents } from './reportEvents';\nimport { getNavigationSpanForMetric } from './softNavs';\nimport { getBrowserPerformanceAPI, msToSec, supportsWebVital } from '../performance/utils';\nimport type { PerformanceEventTiming } from '../instrumentation/performanceObserver';\nimport {\n NAVIGATION,\n UI_INTERACTION_CLICK,\n UI_INTERACTION_DRAG,\n UI_INTERACTION_HOVER,\n UI_INTERACTION_PRESS,\n UI_WEBVITAL_CLS,\n UI_WEBVITAL_LCP,\n} from '@sentry/conventions/op';\n\nconst INTERACTION_TYPE_TO_SPAN_OP: Record<InteractionType, string> = {\n click: UI_INTERACTION_CLICK,\n hover: UI_INTERACTION_HOVER,\n drag: UI_INTERACTION_DRAG,\n press: UI_INTERACTION_PRESS,\n};\n\nconst INTERACTION_TYPE_TO_SPAN_NAME_FALLBACK: Record<InteractionType, string> = {\n click: UI_INTERACTION_CLICK_SPAN_NAME_FALLBACK,\n hover: UI_INTERACTION_HOVER_SPAN_NAME_FALLBACK,\n drag: UI_INTERACTION_DRAG_SPAN_NAME_FALLBACK,\n press: UI_INTERACTION_PRESS_SPAN_NAME_FALLBACK,\n};\n\ntype WebVitalMetric = Parameters<Parameters<typeof addLcpInstrumentationHandler>[0]>[0]['metric'];\ntype InpMetric = Parameters<InstrumentationHandlerCallback>[0]['metric'];\n\n/**\n * Reports a web vital once per navigation, rather than once per page load.\n *\n * web-vitals restarts the metric on every soft navigation and force-reports the previous one just\n * before it does (and again on pagehide), and re-reports every metric after a bfcache restore.\n * Since `reportAllChanges` is off in this mode, every value we're handed is already the final one\n * for its navigation, so there is nothing to accumulate: each report is a span.\n */\nfunction trackWebVitalPerNavigation<M extends WebVitalMetric>(\n client: Client,\n addInstrumentationHandler: (callback: (data: { metric: M }) => void) => unknown,\n send: (metric: M, parentSpan: Span | undefined, softNavigationId: number | undefined) => void,\n): void {\n let pageloadSpan: Span | undefined;\n client.on('afterStartPageLoadSpan', span => {\n pageloadSpan = span;\n });\n\n // Remembered when the restore happens rather than read back at report time: the restore\n // navigation span is an idle span, and CLS and INP are only finalized on pagehide, by which point\n // it has long ended and is no longer what is active.\n let bfcacheNavigationSpan: Span | undefined;\n client.on('spanStart', span => {\n // The op has to be checked too: the web vital spans emitted for a restore carry the same\n // `back-forward-cache` navigation type, so matching on that alone lets the first of them replace the\n // navigation span, and every later vital then hangs off a sibling vital instead.\n const attributes = spanToJSON(span).attributes;\n if (\n attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] === NAVIGATION &&\n attributes[BROWSER_NAVIGATION_TYPE] === 'back-forward-cache'\n ) {\n bfcacheNavigationSpan = span;\n }\n });\n\n addInstrumentationHandler(({ metric }) => {\n const navigationSpan = getNavigationSpanForMetric(metric);\n if (metric.navigationType === 'soft-navigation') {\n // Reporting an uncorrelated soft navigation vital would attribute it to the wrong route, so\n // it's dropped instead.\n if (navigationSpan) {\n send(metric, navigationSpan, metric.navigationId);\n } else {\n DEBUG_BUILD &&\n debug.log(`[SoftNav] Dropping ${metric.name} for uncorrelated soft navigation ${metric.navigationId}`);\n }\n return;\n }\n\n if (metric.navigationType === 'back-forward-cache') {\n // A restore reuses the frozen document, so the pageload span above belongs to the page view\n // from before the freeze. The navigation span started for the restore is the page view these\n // values were actually measured on.\n send(metric, bfcacheNavigationSpan, undefined);\n return;\n }\n\n send(metric, pageloadSpan, undefined);\n });\n}\n\n/**\n * Tracks LCP as a streamed span.\n */\nexport function trackLcpAsSpan(client: Client, perNavigation = false): void {\n if (!supportsWebVital('largest-contentful-paint')) {\n return;\n }\n\n if (perNavigation) {\n trackWebVitalPerNavigation(client, addLcpInstrumentationHandler, (metric, parentSpan, softNavigationId) => {\n const entry = metric.entries[metric.entries.length - 1] as LargestContentfulPaint | undefined;\n _sendLcpSpan(\n metric.value,\n entry,\n parentSpan,\n softNavigationId,\n metric.navigationType,\n metric.navigationStartTime,\n );\n });\n return;\n }\n\n let lcpValue = 0;\n let lcpEntry: LargestContentfulPaint | undefined;\n let lcpNavigationType: MetricNavigationType | undefined;\n\n const cleanupLcpHandler = addLcpInstrumentationHandler(({ metric }) => {\n // The navigation type describes the page, not the entry, so it is worth keeping even for a\n // report we otherwise discard.\n lcpNavigationType = metric.navigationType;\n\n const entry = metric.entries[metric.entries.length - 1] as LargestContentfulPaint | undefined;\n if (!entry || !isValidLcpMetric(metric.value)) {\n return;\n }\n lcpValue = metric.value;\n lcpEntry = entry;\n }, true);\n\n listenForWebVitalReportEvents(client, pageloadSpan => {\n _sendLcpSpan(lcpValue, lcpEntry, pageloadSpan, undefined, lcpNavigationType);\n cleanupLcpHandler();\n });\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendLcpSpan(\n lcpValue: number,\n entry: LargestContentfulPaint | undefined,\n pageloadSpan?: Span,\n softNavigationId?: number,\n navigationType?: MetricNavigationType,\n navigationStartTime?: number,\n): void {\n if (!isValidLcpMetric(lcpValue)) {\n return;\n }\n\n DEBUG_BUILD && debug.log(`Sending LCP span (${lcpValue})`);\n\n const performanceTimeOrigin = browserPerformanceTimeOrigin() || 0;\n // A soft navigation's LCP is measured from the triggering interaction, not the document time\n // origin. Starting the span there too keeps it inside the navigation span it is parented to and\n // keeps its duration equal to the value it reports.\n const startTime = msToSec(performanceTimeOrigin + (navigationStartTime || 0));\n // Without an entry there is no render time to end at, so the span lasts the value it reports,\n // like an entry-less INP does. Ending at the time origin instead would invert the span.\n const endTime = entry ? msToSec(performanceTimeOrigin + entry.startTime) : startTime + msToSec(lcpValue);\n const selector = entry ? htmlTreeAsString(entry.element) : undefined;\n const componentName = entry?.element ? getComponentName(entry.element) : null;\n const client = getClient();\n const hasSpanStreaming = !!client && hasSpanStreamingEnabled(client);\n const name = hasSpanStreaming\n ? componentName || UI_WEBVITAL_LCP_SPAN_NAME_FALLBACK\n : (selector ?? UI_WEBVITAL_LCP_SPAN_NAME_FALLBACK);\n\n const attributes: SpanAttributes = {};\n\n if (selector) {\n attributes['browser.web_vital.lcp.element'] = selector;\n }\n if (componentName) {\n attributes[UI_COMPONENT_NAME] = componentName;\n }\n entry?.id && (attributes['browser.web_vital.lcp.id'] = entry.id);\n entry?.url && (attributes['browser.web_vital.lcp.url'] = entry.url);\n entry?.loadTime != null && (attributes['browser.web_vital.lcp.load_time'] = entry.loadTime);\n entry?.renderTime != null && (attributes['browser.web_vital.lcp.render_time'] = entry.renderTime);\n entry?.size != null && (attributes['browser.web_vital.lcp.size'] = entry.size);\n\n _emitWebVitalSpan({\n name,\n op: UI_WEBVITAL_LCP,\n origin: 'auto.http.browser.lcp',\n metricName: 'lcp',\n value: lcpValue,\n attributes,\n parentSpan: pageloadSpan,\n startTime,\n endTime,\n softNavigationId,\n navigationType,\n });\n}\n\n/**\n * Tracks CLS as a streamed span.\n */\nexport function trackClsAsSpan(client: Client, perNavigation = false): void {\n if (!supportsWebVital('layout-shift')) {\n return;\n }\n\n if (perNavigation) {\n trackWebVitalPerNavigation(client, addClsInstrumentationHandler, (metric, parentSpan, softNavigationId) => {\n const entry = metric.entries[metric.entries.length - 1] as LayoutShift | undefined;\n _sendClsSpan(\n metric.value,\n entry,\n parentSpan,\n softNavigationId,\n metric.navigationType,\n metric.navigationStartTime,\n );\n });\n return;\n }\n\n let clsValue = 0;\n let clsEntry: LayoutShift | undefined;\n let clsNavigationType: MetricNavigationType | undefined;\n\n const cleanupClsHandler = addClsInstrumentationHandler(({ metric }) => {\n // A CLS of 0 is reported with no entries and still emits a span, so the navigation type has to\n // be captured before the entry check rather than alongside the value.\n clsNavigationType = metric.navigationType;\n\n const entry = metric.entries[metric.entries.length - 1] as LayoutShift | undefined;\n if (!entry) {\n return;\n }\n clsValue = metric.value;\n clsEntry = entry;\n }, true);\n\n listenForWebVitalReportEvents(client, pageloadSpan => {\n _sendClsSpan(clsValue, clsEntry, pageloadSpan, undefined, clsNavigationType);\n cleanupClsHandler();\n });\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendClsSpan(\n clsValue: number,\n entry: LayoutShift | undefined,\n pageloadSpan?: Span,\n softNavigationId?: number,\n navigationType?: MetricNavigationType,\n navigationStartTime?: number,\n): void {\n DEBUG_BUILD && debug.log(`Sending CLS span (${clsValue})`);\n\n const performanceTimeOrigin = browserPerformanceTimeOrigin();\n // A CLS of 0 has no shift to place the span at. It is reported when the navigation it was\n // measured on is already over - the next soft navigation, or pagehide - so the current time would\n // land it outside that navigation, on the route that follows it.\n const offset = entry?.startTime ?? navigationStartTime ?? 0;\n const startTime = performanceTimeOrigin ? msToSec(performanceTimeOrigin + offset) : timestampInSeconds();\n const firstSourceNode = entry?.sources[0]?.node;\n const selector = entry ? htmlTreeAsString(firstSourceNode) : undefined;\n const componentName = firstSourceNode ? getComponentName(firstSourceNode) : null;\n const client = getClient();\n const hasSpanStreaming = !!client && hasSpanStreamingEnabled(client);\n const name = hasSpanStreaming\n ? componentName || UI_WEBVITAL_CLS_SPAN_NAME_FALLBACK\n : (selector ?? UI_WEBVITAL_CLS_SPAN_NAME_FALLBACK);\n\n const attributes: SpanAttributes = {};\n\n if (componentName) {\n attributes[UI_COMPONENT_NAME] = componentName;\n }\n\n if (entry?.sources) {\n entry.sources.forEach((source, index) => {\n attributes[`browser.web_vital.cls.source.${index + 1}`] = htmlTreeAsString(source.node);\n });\n }\n\n _emitWebVitalSpan({\n name,\n op: UI_WEBVITAL_CLS,\n origin: 'auto.http.browser.cls',\n metricName: 'cls',\n value: clsValue,\n attributes,\n parentSpan: pageloadSpan,\n startTime,\n softNavigationId,\n navigationType,\n });\n}\n\n/**\n * Tracks INP and emits it as a web vital span, as a child of the interaction's root span.\n * Requires `registerInpInteractionListener()` to be called separately for cached element names and\n * root spans per interaction.\n */\nexport function trackInpAsSpan(client: Client, perNavigation = false): void {\n const performance = getBrowserPerformanceAPI();\n if (!performance || !browserPerformanceTimeOrigin()) {\n return;\n }\n\n // INP reports late (on pagehide, after the pageload span has ended). With span streaming enabled\n // it rides the streaming pipeline. With streaming disabled it would be dropped as a late span, so\n // it is emitted as its own standalone v2 span instead (see `_emitWebVitalSpan`), overriding the\n // static trace lifecycle for INP only.\n // TODO(standalone): once the static trace lifecycle is dropped, INP always streams; drop this flag.\n const standalone = !hasSpanStreamingEnabled(client);\n\n if (perNavigation) {\n // INP restarts per navigation and reports once that navigation is over, by which point the\n // navigation span has ended and the interaction cache no longer knows about it. The metric\n // says which navigation it belongs to, so INP is attributed exactly like LCP and CLS.\n trackWebVitalPerNavigation(client, addInpInstrumentationHandler, (metric, parentSpan, softNavigationId) => {\n if (isPlausibleInp(metric)) {\n _sendInpSpan(metric.value, findInpEntry(metric), standalone, parentSpan, softNavigationId, metric);\n }\n });\n return;\n }\n\n const onInp: InstrumentationHandlerCallback = ({ metric }) => {\n if (isPlausibleInp(metric)) {\n _sendInpSpan(metric.value, findInpEntry(metric), standalone, undefined, undefined, metric);\n }\n };\n\n addInpInstrumentationHandler(onInp);\n}\n\nfunction isPlausibleInp(metric: InpMetric): boolean {\n return metric.value != null && msToSec(metric.value) <= MAX_PLAUSIBLE_INP_DURATION;\n}\n\n/**\n * The entry an INP span is built from: the one whose duration the reported value came from.\n *\n * There isn't always one. When every interaction of a soft navigation stayed below the Event Timing\n * threshold, web-vitals reports a synthetic value with no entries at all - see\n * `_estimateP98LongestInteraction`. The span still gets reported in that case, just without the\n * element and interaction type an entry would have supplied.\n */\nfunction findInpEntry(metric: InpMetric): PerformanceEventTiming | undefined {\n return metric.entries.find(e => e.duration === metric.value && INP_ENTRY_MAP[e.name]);\n}\n\n/**\n * Exported only for testing.\n */\nexport function _sendInpSpan(\n inpValue: number,\n entry: PerformanceEventTiming | undefined,\n standalone = false,\n attributedSpan?: Span,\n softNavigationId?: number,\n metric?: InpMetric,\n): void {\n DEBUG_BUILD && debug.log(`Sending INP span (${inpValue})`);\n\n // A web vital span carries the metric, not a real interaction timing, so an INP without an entry\n // is still worth reporting. It just has no element or interaction type to describe, and is placed\n // at the start of the navigation it belongs to rather than at the interaction.\n const startTime = msToSec(\n (browserPerformanceTimeOrigin() as number) + (entry?.startTime ?? metric?.navigationStartTime ?? 0),\n );\n const duration = msToSec(inpValue);\n // An INP without an entry has no interaction type to report. It still has to land inside the\n // `ui.interaction.*` family, because falling outside it would hide exactly the fast navigations\n // that web-vitals synthesizes these values for (GoogleChrome/web-vitals#724), reintroducing the\n // reporting bias they were added to remove.\n const entryInteractionType = entry && INP_ENTRY_MAP[entry.name];\n const interactionType = entryInteractionType || 'click';\n\n const cachedContext = entry && getCachedInteractionContext(entry.interactionId);\n const activeSpan = getActiveSpan();\n const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;\n\n // With soft navigations the caller knows exactly which navigation the metric belongs to. Without\n // them we fall back to the span that was active when the interaction was observed.\n const spanToUse = attributedSpan || cachedContext?.span || rootSpan;\n const selector = cachedContext?.elementName || (entry ? htmlTreeAsString(entry.target) : undefined);\n const componentName = entry?.target ? getComponentName(entry.target) : null;\n const client = getClient();\n const hasSpanStreaming = !!client && hasSpanStreamingEnabled(client);\n const fallbackName = INTERACTION_TYPE_TO_SPAN_NAME_FALLBACK[interactionType];\n const name = hasSpanStreaming ? componentName || fallbackName : (selector ?? 'Interaction to next paint');\n\n const attributes: SpanAttributes = {\n [SENTRY_EXCLUSIVE_TIME]: entry?.duration ?? inpValue,\n };\n\n // The span's name and op always have a value, even for an INP without an entry, so they can't\n // say whether there was an interaction to describe. These attributes can: they are only set for\n // what was actually observed. The name no longer holds the selector either, now that it is the\n // component name or the op's fallback under span streaming.\n selector && selector !== UNKNOWN_ELEMENT_NAME && (attributes[BROWSER_WEB_VITAL_INP_TARGET] = selector);\n entryInteractionType && (attributes[BROWSER_WEB_VITAL_INP_INTERACTION_TYPE] = entryInteractionType);\n\n if (componentName) {\n attributes[UI_COMPONENT_NAME] = componentName;\n }\n\n _emitWebVitalSpan({\n name,\n op: INTERACTION_TYPE_TO_SPAN_OP[interactionType],\n origin: 'auto.http.browser.inp',\n metricName: 'inp',\n value: inpValue,\n attributes,\n startTime,\n endTime: startTime + duration,\n navigationType: metric?.navigationType,\n parentSpan: spanToUse,\n standalone,\n softNavigationId,\n });\n}\n"],"names":[],"mappings":"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"}
|
package/build/types/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export { elementTimingIntegration, startTrackingElementTiming } from './performa
|
|
|
6
6
|
export { interactionsIntegration } from './performance/interactions';
|
|
7
7
|
export { isBotUserAgent } from './isBotUserAgent';
|
|
8
8
|
export { getLocationHref } from './getLocationHref';
|
|
9
|
+
export { createCachedRouteProvider, createUrlRouteProvider, getRouteProvider, resolveCurrentRoute, resolveRoute, setRouteProvider, } from './routing';
|
|
10
|
+
export type { CachedRouteProvider, RouteProvider } from './routing';
|
|
9
11
|
export { userTimingIntegration } from './performance/userTiming';
|
|
10
12
|
export { extractNetworkProtocol } from './performance/utils';
|
|
11
13
|
export { trackClsAsSpan, trackInpAsSpan, trackLcpAsSpan } from './web-vitals/spans';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oCAAoC,EACpC,4BAA4B,EAC5B,6BAA6B,EAC7B,4BAA4B,EAC5B,4BAA4B,EAC5B,4BAA4B,EAC5B,sBAAsB,EACtB,6BAA6B,GAC9B,MAAM,uCAAuC,CAAC;AAE/C,OAAO,EAAE,8BAA8B,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhG,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,gCAAgC,EAAE,MAAM,uBAAuB,CAAC;AAExH,OAAO,EACL,kBAAkB,EAElB,sBAAsB,EACtB,8BAA8B,GAC/B,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AAEnG,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAErE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpF,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,sCAAsC,EAAE,MAAM,uBAAuB,CAAC;AAE/E,OAAO,EAAE,gCAAgC,EAAE,MAAM,2BAA2B,CAAC;AAE7E,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAElH,OAAO,EAAE,4BAA4B,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE1F,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAEnH,OAAO,EAAE,8BAA8B,EAAE,MAAM,8BAA8B,CAAC;AAE9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEjC,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,YAAY,EACV,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,2BAA2B,EAC3B,aAAa,EACb,OAAO,GACR,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oCAAoC,EACpC,4BAA4B,EAC5B,6BAA6B,EAC7B,4BAA4B,EAC5B,4BAA4B,EAC5B,4BAA4B,EAC5B,sBAAsB,EACtB,6BAA6B,GAC9B,MAAM,uCAAuC,CAAC;AAE/C,OAAO,EAAE,8BAA8B,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhG,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,gCAAgC,EAAE,MAAM,uBAAuB,CAAC;AAExH,OAAO,EACL,kBAAkB,EAElB,sBAAsB,EACtB,8BAA8B,GAC/B,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AAEnG,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAErE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EACL,yBAAyB,EACzB,sBAAsB,EACtB,gBAAgB,EAChB,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAEpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpF,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,sCAAsC,EAAE,MAAM,uBAAuB,CAAC;AAE/E,OAAO,EAAE,gCAAgC,EAAE,MAAM,2BAA2B,CAAC;AAE7E,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAElH,OAAO,EAAE,4BAA4B,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE1F,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAEnH,OAAO,EAAE,8BAA8B,EAAE,MAAM,8BAA8B,CAAC;AAE9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEjC,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,YAAY,EACV,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,2BAA2B,EAC3B,aAAa,EACb,OAAO,GACR,MAAM,SAAS,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../../src/instrumentation/dom.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../../src/instrumentation/dom.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AA+C/C;;;;;GAKG;AACH,wBAAgB,sCAAsC,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI,CAIpG;AAED,+BAA+B;AAC/B,wBAAgB,aAAa,IAAI,IAAI,CA8GpC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { Client } from '@sentry/core';
|
|
2
|
+
/** The parts of a URL a route depends on. A `URL` satisfies it. */
|
|
3
|
+
type RouteUrl = Pick<URL, 'pathname' | 'search' | 'hash'>;
|
|
4
|
+
/**
|
|
5
|
+
* Resolves URLs to low-cardinality route names.
|
|
6
|
+
*
|
|
7
|
+
* Framework SDKs register one so that everything the SDK names after a route (span names, the scope's
|
|
8
|
+
* transaction name, metric and span segment attributes) gets the parameterized route instead of the raw
|
|
9
|
+
* URL, without each integration having to reach into the framework's router itself.
|
|
10
|
+
*/
|
|
11
|
+
export interface RouteProvider {
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a URL path template for a specific URL, e.g. `/users/42` -> `/users/:id`.
|
|
14
|
+
*
|
|
15
|
+
* Must return a path template, never a route identifier. Routers that name routes independently of
|
|
16
|
+
* their path (Vue Router's `route.name`, Ember's `posts.show`) have to return the matched path
|
|
17
|
+
* instead: callers set `url.template` from this, and an identifier is not a template. An SDK that
|
|
18
|
+
* wants to name its span after the identifier still can, on the span itself.
|
|
19
|
+
*
|
|
20
|
+
* Returns `undefined` when the URL matches no known route. Must answer for the URL it is given rather
|
|
21
|
+
* than for wherever the router currently is, so that callers can resolve a URL they captured earlier
|
|
22
|
+
* (a web vital reported after a soft navigation, for example).
|
|
23
|
+
*/
|
|
24
|
+
resolveRoute(url: RouteUrl): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Resolves the route the app is currently on.
|
|
27
|
+
*
|
|
28
|
+
* Routers whose location lives in the address bar can delegate to `resolveRoute`, which is what
|
|
29
|
+
* {@link createUrlRouteProvider} does. Routers that keep their own location (memory and hash routers)
|
|
30
|
+
* have to answer from that location instead: for those, `location.href` is the unchanging shell URL
|
|
31
|
+
* and would bucket every route together.
|
|
32
|
+
*/
|
|
33
|
+
resolveCurrentRoute(): string | undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Registers the route provider for a client, replacing any previously registered one. `BrowserClient`
|
|
37
|
+
* calls this with its `routeProvider` option.
|
|
38
|
+
*
|
|
39
|
+
* Prefer the `routeProvider` option where the provider is known at `init`: the pageload span is named
|
|
40
|
+
* while `browserTracingIntegration` sets up, so a provider registered after `init` can only rename it
|
|
41
|
+
* after the fact.
|
|
42
|
+
*
|
|
43
|
+
* A client holds one provider. An app running two routers (a framework migration, or a shell plus an
|
|
44
|
+
* island) registers twice and the last one wins, so the first router's routes stop resolving.
|
|
45
|
+
*/
|
|
46
|
+
export declare function setRouteProvider(provider: RouteProvider, client?: Client | undefined): void;
|
|
47
|
+
/**
|
|
48
|
+
* Returns the route provider registered for a client.
|
|
49
|
+
*/
|
|
50
|
+
export declare function getRouteProvider(client?: Client | undefined): RouteProvider | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Resolves a URL to a low-cardinality route name, e.g. `/users/42` -> `/users/:id`.
|
|
53
|
+
*
|
|
54
|
+
* Returns `undefined` when no route provider is registered or the URL matches no route. Callers pick
|
|
55
|
+
* their own fallback, because the right one differs: a span name falls back to a low-cardinality
|
|
56
|
+
* constant, the scope's transaction name to the raw path.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveRoute(url: string | URL, client?: Client | undefined): string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Resolves the route the app is currently on.
|
|
61
|
+
*
|
|
62
|
+
* Returns `undefined` when no route provider is registered or the current location matches no route.
|
|
63
|
+
*/
|
|
64
|
+
export declare function resolveCurrentRoute(client?: Client | undefined): string | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Builds a {@link RouteProvider} for a router whose location is the browser's, which covers every
|
|
67
|
+
* router except memory and hash routers.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createUrlRouteProvider(resolveRouteFromUrl: (url: RouteUrl) => string | undefined): RouteProvider;
|
|
70
|
+
/**
|
|
71
|
+
* A {@link RouteProvider} that answers from routes it has been told about, rather than by matching.
|
|
72
|
+
*/
|
|
73
|
+
export interface CachedRouteProvider extends RouteProvider {
|
|
74
|
+
/** Records the route name a router reported for a path. Ignores empty values. */
|
|
75
|
+
record(pathname: string | undefined, routeName: string | null | undefined): void;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Builds a route provider for a router with no usable matcher, which can only report the route it is
|
|
79
|
+
* on as it gets there (SvelteKit's `page.route.id`, Solid Router's current matches).
|
|
80
|
+
*
|
|
81
|
+
* A URL the app has not visited resolves to `undefined`, which includes the first pageload until the
|
|
82
|
+
* router reports. Backed by an LRU so a long-lived app visiting many URLs can't grow it without end,
|
|
83
|
+
* and so routes that keep being resolved outlive ones passed through once.
|
|
84
|
+
*/
|
|
85
|
+
export declare function createCachedRouteProvider(maxEntries?: number): CachedRouteProvider;
|
|
86
|
+
export {};
|
|
87
|
+
//# sourceMappingURL=routing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/routing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAK3C,mEAAmE;AACnE,KAAK,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC;AAE1D;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;IAEhD;;;;;;;OAOG;IACH,mBAAmB,IAAI,MAAM,GAAG,SAAS,CAAC;CAC3C;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,EAAE,MAAM,GAAE,MAAM,GAAG,SAAuB,GAAG,IAAI,CAaxG;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,GAAE,MAAM,GAAG,SAAuB,GAAG,aAAa,GAAG,SAAS,CAEpG;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,MAAM,GAAE,MAAM,GAAG,SAAuB,GAAG,MAAM,GAAG,SAAS,CAgB5G;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,GAAE,MAAM,GAAG,SAAuB,GAAG,MAAM,GAAG,SAAS,CAQhG;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,mBAAmB,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,MAAM,GAAG,SAAS,GAAG,aAAa,CAShH;AAED;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACxD,iFAAiF;IACjF,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAAC;CAClF;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,GAAE,MAAW,GAAG,mBAAmB,CAWtF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spans.d.ts","sourceRoot":"","sources":["../../../src/web-vitals/spans.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAkB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"spans.d.ts","sourceRoot":"","sources":["../../../src/web-vitals/spans.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAkB,MAAM,cAAc,CAAC;AAuBjE,OAAO,KAAK,EAAE,8BAA8B,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAMnH,OAAO,KAAK,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAatE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AA0BrF,KAAK,SAAS,GAAG,UAAU,CAAC,8BAA8B,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AA+DzE;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,UAAQ,GAAG,IAAI,CAyC1E;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,sBAAsB,GAAG,SAAS,EACzC,YAAY,CAAC,EAAE,IAAI,EACnB,gBAAgB,CAAC,EAAE,MAAM,EACzB,cAAc,CAAC,EAAE,oBAAoB,EACrC,mBAAmB,CAAC,EAAE,MAAM,GAC3B,IAAI,CAkDN;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,UAAQ,GAAG,IAAI,CAyC1E;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,WAAW,GAAG,SAAS,EAC9B,YAAY,CAAC,EAAE,IAAI,EACnB,gBAAgB,CAAC,EAAE,MAAM,EACzB,cAAc,CAAC,EAAE,oBAAoB,EACrC,mBAAmB,CAAC,EAAE,MAAM,GAC3B,IAAI,CA0CN;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,UAAQ,GAAG,IAAI,CAgC1E;AAkBD;;GAEG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,sBAAsB,GAAG,SAAS,EACzC,UAAU,UAAQ,EAClB,cAAc,CAAC,EAAE,IAAI,EACrB,gBAAgB,CAAC,EAAE,MAAM,EACzB,MAAM,CAAC,EAAE,SAAS,GACjB,IAAI,CA4DN"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/browser-utils",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.3.0",
|
|
4
4
|
"description": "Browser Utilities for all Sentry JavaScript SDKs",
|
|
5
5
|
"repository": "git://github.com/getsentry/sentry-javascript.git",
|
|
6
6
|
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/browser-utils",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@sentry/core": "11.
|
|
37
|
-
"@sentry/conventions": "^0.
|
|
36
|
+
"@sentry/core": "11.3.0",
|
|
37
|
+
"@sentry/conventions": "^0.25.0",
|
|
38
38
|
"web-vitals": "^6.1.1"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|