@sentry/browser-utils 11.1.0 → 11.2.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/instrumentation/dom.js +25 -9
- package/build/cjs/instrumentation/dom.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/types/instrumentation/dom.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -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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"}
|
|
@@ -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.2.0","sideEffects":false}
|
|
@@ -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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/browser-utils",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.2.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,7 +33,7 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@sentry/core": "11.
|
|
36
|
+
"@sentry/core": "11.2.0",
|
|
37
37
|
"@sentry/conventions": "^0.24.0",
|
|
38
38
|
"web-vitals": "^6.1.1"
|
|
39
39
|
},
|