diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/template.html b/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/template.html index 5f23d569fcc2..bd12f84b090a 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/template.html +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/template.html @@ -13,7 +13,6 @@ function draw() { const canvas = document.getElementById("canvas"); if (canvas.getContext) { - console.log('has canvas') const ctx = canvas.getContext("2d"); ctx.fillRect(25, 25, 100, 100); diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/records/template.html b/dev-packages/browser-integration-tests/suites/replay/canvas/records/template.html index 5f23d569fcc2..bd12f84b090a 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/records/template.html +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/records/template.html @@ -13,7 +13,6 @@ function draw() { const canvas = document.getElementById("canvas"); if (canvas.getContext) { - console.log('has canvas') const ctx = canvas.getContext("2d"); ctx.fillRect(25, 25, 100, 100); diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json index 2f0ffd4e8eba..066076c00969 100644 --- a/packages/replay-canvas/package.json +++ b/packages/replay-canvas/package.json @@ -69,7 +69,7 @@ "homepage": "https://docs.sentry.io/platforms/javascript/session-replay/", "devDependencies": { "@babel/core": "^7.17.5", - "@sentry-internal/rrweb": "2.15.0" + "@sentry-internal/rrweb": "2.20.0" }, "dependencies": { "@sentry-internal/replay": "8.9.2", diff --git a/packages/replay-internal/package.json b/packages/replay-internal/package.json index 42877089266e..9ff53f7dd16b 100644 --- a/packages/replay-internal/package.json +++ b/packages/replay-internal/package.json @@ -72,8 +72,8 @@ "devDependencies": { "@babel/core": "^7.17.5", "@sentry-internal/replay-worker": "8.9.2", - "@sentry-internal/rrweb": "2.15.0", - "@sentry-internal/rrweb-snapshot": "2.15.0", + "@sentry-internal/rrweb": "2.20.0", + "@sentry-internal/rrweb-snapshot": "2.20.0", "fflate": "^0.8.1", "jest-matcher-utils": "^29.0.0", "jsdom-worker": "^0.2.1" diff --git a/packages/replay-internal/src/coreHandlers/handleClick.ts b/packages/replay-internal/src/coreHandlers/handleClick.ts index da07474deebb..5aab04e68af6 100644 --- a/packages/replay-internal/src/coreHandlers/handleClick.ts +++ b/packages/replay-internal/src/coreHandlers/handleClick.ts @@ -1,5 +1,6 @@ import { setTimeout } from '@sentry-internal/browser-utils'; -import { IncrementalSource, MouseInteractions, record } from '@sentry-internal/rrweb'; +import { IncrementalSource, MouseInteractions } from '@sentry-internal/rrweb'; +import type { Mirror } from '@sentry-internal/rrweb-snapshot'; import type { Breadcrumb } from '@sentry/types'; import { WINDOW } from '../constants'; @@ -309,7 +310,11 @@ function nowInSeconds(): number { } /** Update the click detector based on a recording event of rrweb. */ -export function updateClickDetectorForRecordingEvent(clickDetector: ReplayClickDetector, event: RecordingEvent): void { +export function updateClickDetectorForRecordingEvent( + clickDetector: ReplayClickDetector, + event: RecordingEvent, + mirror: Mirror, +): void { try { // note: We only consider incremental snapshots here // This means that any full snapshot is ignored for mutation detection - the reason is that we simply cannot know if a mutation happened here. @@ -334,7 +339,7 @@ export function updateClickDetectorForRecordingEvent(clickDetector: ReplayClickD if (isIncrementalMouseInteraction(event)) { const { type, id } = event.data; - const node = record.mirror.getNode(id); + const node = mirror.getNode(id); if (node instanceof HTMLElement && type === MouseInteractions.Click) { clickDetector.registerClick(node); diff --git a/packages/replay-internal/src/coreHandlers/handleDom.ts b/packages/replay-internal/src/coreHandlers/handleDom.ts index a5c20810481b..98990c9dd0d4 100644 --- a/packages/replay-internal/src/coreHandlers/handleDom.ts +++ b/packages/replay-internal/src/coreHandlers/handleDom.ts @@ -1,5 +1,4 @@ -import { record } from '@sentry-internal/rrweb'; -import type { serializedElementNodeWithId, serializedNodeWithId } from '@sentry-internal/rrweb-snapshot'; +import type { Mirror, serializedElementNodeWithId, serializedNodeWithId } from '@sentry-internal/rrweb-snapshot'; import { NodeType } from '@sentry-internal/rrweb-snapshot'; import type { Breadcrumb, HandlerDataDom } from '@sentry/types'; import { htmlTreeAsString } from '@sentry/utils'; @@ -19,7 +18,7 @@ export const handleDomListener: (replay: ReplayContainer) => (handlerData: Handl return; } - const result = handleDom(handlerData); + const result = handleDom(handlerData, replay.getDomMirror()); if (!result) { return; @@ -50,10 +49,10 @@ export const handleDomListener: (replay: ReplayContainer) => (handlerData: Handl }; /** Get the base DOM breadcrumb. */ -export function getBaseDomBreadcrumb(target: Node | null, message: string): Breadcrumb { - const nodeId = record.mirror.getId(target); - const node = nodeId && record.mirror.getNode(nodeId); - const meta = node && record.mirror.getMeta(node); +export function getBaseDomBreadcrumb(target: Node | null, message: string, mirror: Mirror): Breadcrumb { + const nodeId = mirror.getId(target); + const node = nodeId && mirror.getNode(nodeId); + const meta = node && mirror.getMeta(node); const element = meta && isElement(meta) ? meta : null; return { @@ -80,12 +79,12 @@ export function getBaseDomBreadcrumb(target: Node | null, message: string): Brea * An event handler to react to DOM events. * Exported for tests. */ -export function handleDom(handlerData: HandlerDataDom): Breadcrumb | null { +export function handleDom(handlerData: HandlerDataDom, mirror: Mirror): Breadcrumb | null { const { target, message } = getDomTarget(handlerData); return createBreadcrumb({ category: `ui.${handlerData.name}`, - ...getBaseDomBreadcrumb(target, message), + ...getBaseDomBreadcrumb(target, message, mirror), }); } diff --git a/packages/replay-internal/src/coreHandlers/handleKeyboardEvent.ts b/packages/replay-internal/src/coreHandlers/handleKeyboardEvent.ts index 0f7560f39584..4679653d58b7 100644 --- a/packages/replay-internal/src/coreHandlers/handleKeyboardEvent.ts +++ b/packages/replay-internal/src/coreHandlers/handleKeyboardEvent.ts @@ -1,3 +1,4 @@ +import type { Mirror } from '@sentry-internal/rrweb-snapshot'; import type { Breadcrumb } from '@sentry/types'; import { htmlTreeAsString } from '@sentry/utils'; @@ -7,7 +8,7 @@ import { getBaseDomBreadcrumb } from './handleDom'; import { addBreadcrumbEvent } from './util/addBreadcrumbEvent'; /** Handle keyboard events & create breadcrumbs. */ -export function handleKeyboardEvent(replay: ReplayContainer, event: KeyboardEvent): void { +export function handleKeyboardEvent(replay: ReplayContainer, event: KeyboardEvent, mirror: Mirror): void { if (!replay.isEnabled()) { return; } @@ -17,7 +18,7 @@ export function handleKeyboardEvent(replay: ReplayContainer, event: KeyboardEven // session with a single "keydown" breadcrumb is created) replay.updateUserActivity(); - const breadcrumb = getKeyboardBreadcrumb(event); + const breadcrumb = getKeyboardBreadcrumb(event, mirror); if (!breadcrumb) { return; @@ -27,7 +28,7 @@ export function handleKeyboardEvent(replay: ReplayContainer, event: KeyboardEven } /** exported only for tests */ -export function getKeyboardBreadcrumb(event: KeyboardEvent): Breadcrumb | null { +export function getKeyboardBreadcrumb(event: KeyboardEvent, mirror: Mirror): Breadcrumb | null { const { metaKey, shiftKey, ctrlKey, altKey, key, target } = event; // never capture for input fields @@ -46,7 +47,7 @@ export function getKeyboardBreadcrumb(event: KeyboardEvent): Breadcrumb | null { } const message = htmlTreeAsString(target, { maxStringLength: 200 }) || ''; - const baseBreadcrumb = getBaseDomBreadcrumb(target as Node, message); + const baseBreadcrumb = getBaseDomBreadcrumb(target as Node, message, mirror); return createBreadcrumb({ category: 'ui.keyDown', diff --git a/packages/replay-internal/src/replay.ts b/packages/replay-internal/src/replay.ts index 1d3b1ef340c4..600ed30759df 100644 --- a/packages/replay-internal/src/replay.ts +++ b/packages/replay-internal/src/replay.ts @@ -1,5 +1,6 @@ /* eslint-disable max-lines */ // TODO: We might want to split this file up import { EventType, record } from '@sentry-internal/rrweb'; +import type { Mirror } from '@sentry-internal/rrweb-snapshot'; import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, captureException, @@ -143,6 +144,13 @@ export class ReplayContainer implements ReplayContainerInterface { */ private _hasInitializedCoreListeners: boolean; + /** + * The `record` function to use, defaults to package's `record()`, but we can + * opt to pass in a different version, i.e. if we wanted to test a different + * version. + */ + private _recordFn: typeof record; + /** * Function to stop recording */ @@ -212,6 +220,8 @@ export class ReplayContainer implements ReplayContainerInterface { if (slowClickConfig) { this.clickDetector = new ClickDetector(this, slowClickConfig); } + + this._recordFn = options._experiments.recordFn || record; } /** Get the event context. */ @@ -219,6 +229,13 @@ export class ReplayContainer implements ReplayContainerInterface { return this._context; } + /** + * Returns rrweb's mirror + */ + public getDomMirror(): Mirror { + return this._recordFn.mirror; + } + /** If recording is currently enabled. */ public isEnabled(): boolean { return this._isEnabled; @@ -368,7 +385,7 @@ export class ReplayContainer implements ReplayContainerInterface { try { const canvasOptions = this._canvas; - this._stopRecording = record({ + this._stopRecording = this._recordFn({ ...this._recordingOptions, // When running in error sampling mode, we need to overwrite `checkoutEveryNms` // Without this, it would record forever, until an error happens, which we don't want @@ -941,7 +958,7 @@ export class ReplayContainer implements ReplayContainerInterface { /** Ensure page remains active when a key is pressed. */ private _handleKeyboardEvent: (event: KeyboardEvent) => void = (event: KeyboardEvent) => { - handleKeyboardEvent(this, event); + handleKeyboardEvent(this, event, this.getDomMirror()); }; /** @@ -1036,7 +1053,9 @@ export class ReplayContainer implements ReplayContainerInterface { * are included in the replay event before it is finished and sent to Sentry. */ private _addPerformanceEntries(): Promise> { - const performanceEntries = createPerformanceEntries(this.performanceEntries).concat(this.replayPerformanceEntries); + const performanceEntries = createPerformanceEntries(this.performanceEntries, this.getDomMirror()).concat( + this.replayPerformanceEntries, + ); this.performanceEntries = []; this.replayPerformanceEntries = []; diff --git a/packages/replay-internal/src/types/replay.ts b/packages/replay-internal/src/types/replay.ts index 7ebacad9e100..b78f68369489 100644 --- a/packages/replay-internal/src/types/replay.ts +++ b/packages/replay-internal/src/types/replay.ts @@ -1,3 +1,5 @@ +import type { record } from '@sentry-internal/rrweb'; +import type { Mirror } from '@sentry-internal/rrweb-snapshot'; import type { Breadcrumb, ErrorEvent, @@ -232,6 +234,7 @@ export interface ReplayPluginOptions extends ReplayNetworkOptions { _experiments: Partial<{ captureExceptions: boolean; traceInternals: boolean; + recordFn: typeof record; }>; } @@ -465,6 +468,7 @@ export interface ReplayContainer { isPaused(): boolean; isRecordingCanvas(): boolean; getContext(): InternalEventContext; + getDomMirror(): Mirror; initializeSampling(): void; start(): void; stop(options?: { reason?: string; forceflush?: boolean }): Promise; diff --git a/packages/replay-internal/src/types/rrweb.ts b/packages/replay-internal/src/types/rrweb.ts index a490a6e46c1b..cb194e193a5d 100644 --- a/packages/replay-internal/src/types/rrweb.ts +++ b/packages/replay-internal/src/types/rrweb.ts @@ -31,7 +31,7 @@ export type ReplayEventWithTime = { /** * This is a partial copy of rrweb's recording options which only contains the properties - * we specifically us in the SDK. Users can specify additional properties, hence we add the + * we specifically use in the SDK. Users can specify additional properties, hence we add the * Record union type. */ export type RrwebRecordOptions = { @@ -52,6 +52,9 @@ export interface CanvasManagerInterface { lock(): void; unlock(): void; snapshot(): void; + addWindow(win: typeof globalThis & Window): void; + addShadowRoot(shadowRoot: ShadowRoot): void; + resetShadowRoots(): void; } export interface CanvasManagerOptions { diff --git a/packages/replay-internal/src/util/createPerformanceEntries.ts b/packages/replay-internal/src/util/createPerformanceEntries.ts index b7cca6b05ddf..02909ab0c10c 100644 --- a/packages/replay-internal/src/util/createPerformanceEntries.ts +++ b/packages/replay-internal/src/util/createPerformanceEntries.ts @@ -1,4 +1,4 @@ -import { record } from '@sentry-internal/rrweb'; +import type { Mirror } from '@sentry-internal/rrweb-snapshot'; import { browserPerformanceTimeOrigin } from '@sentry/utils'; import { WINDOW } from '../constants'; @@ -17,7 +17,7 @@ import type { // Map entryType -> function to normalize data for event const ENTRY_TYPES: Record< string, - (entry: AllPerformanceEntry) => null | ReplayPerformanceEntry + (entry: AllPerformanceEntry, mirror: Mirror) => null | ReplayPerformanceEntry > = { // @ts-expect-error TODO: entry type does not fit the create* functions entry type resource: createResourceEntry, @@ -56,10 +56,10 @@ interface LayoutShiftAttribution { * Handler creater for web vitals */ export function webVitalHandler( - getter: (metric: Metric) => ReplayPerformanceEntry, + getter: (metric: Metric, mirror: Mirror) => ReplayPerformanceEntry, replay: ReplayContainer, ): (data: { metric: Metric }) => void { - return ({ metric }) => void replay.replayPerformanceEntries.push(getter(metric)); + return ({ metric }) => void replay.replayPerformanceEntries.push(getter(metric, replay.getDomMirror())); } /** @@ -67,16 +67,22 @@ export function webVitalHandler( */ export function createPerformanceEntries( entries: AllPerformanceEntry[], + mirror: Mirror, ): ReplayPerformanceEntry[] { - return entries.map(createPerformanceEntry).filter(Boolean) as ReplayPerformanceEntry[]; + return entries + .map(entry => createPerformanceEntry(entry, mirror)) + .filter(Boolean) as ReplayPerformanceEntry[]; } -function createPerformanceEntry(entry: AllPerformanceEntry): ReplayPerformanceEntry | null { +function createPerformanceEntry( + entry: AllPerformanceEntry, + mirror: Mirror, +): ReplayPerformanceEntry | null { if (!ENTRY_TYPES[entry.entryType]) { return null; } - return ENTRY_TYPES[entry.entryType](entry); + return ENTRY_TYPES[entry.entryType](entry, mirror); } function getAbsoluteTime(time: number): number { @@ -85,7 +91,7 @@ function getAbsoluteTime(time: number): number { return ((browserPerformanceTimeOrigin || WINDOW.performance.timeOrigin) + time) / 1000; } -function createPaintEntry(entry: PerformancePaintTiming): ReplayPerformanceEntry { +function createPaintEntry(entry: PerformancePaintTiming, _mirror: Mirror): ReplayPerformanceEntry { const { duration, entryType, name, startTime } = entry; const start = getAbsoluteTime(startTime); @@ -98,7 +104,10 @@ function createPaintEntry(entry: PerformancePaintTiming): ReplayPerformanceEntry }; } -function createNavigationEntry(entry: PerformanceNavigationTiming): ReplayPerformanceEntry | null { +function createNavigationEntry( + entry: PerformanceNavigationTiming, + _mirror: Mirror, +): ReplayPerformanceEntry | null { const { entryType, name, @@ -145,6 +154,7 @@ function createNavigationEntry(entry: PerformanceNavigationTiming): ReplayPerfor function createResourceEntry( entry: ExperimentalPerformanceResourceTiming, + _mirror: Mirror, ): ReplayPerformanceEntry | null { const { entryType, @@ -180,38 +190,38 @@ function createResourceEntry( /** * Add a LCP event to the replay based on a LCP metric. */ -export function getLargestContentfulPaint(metric: Metric): ReplayPerformanceEntry { +export function getLargestContentfulPaint(metric: Metric, mirror: Mirror): ReplayPerformanceEntry { const lastEntry = metric.entries[metric.entries.length - 1] as (PerformanceEntry & { element?: Node }) | undefined; const node = lastEntry ? lastEntry.element : undefined; - return getWebVital(metric, 'largest-contentful-paint', node); + return getWebVital(metric, 'largest-contentful-paint', node, mirror); } /** * Add a CLS event to the replay based on a CLS metric. */ -export function getCumulativeLayoutShift(metric: Metric): ReplayPerformanceEntry { +export function getCumulativeLayoutShift(metric: Metric, mirror: Mirror): ReplayPerformanceEntry { // get first node that shifts const firstEntry = metric.entries[0] as (PerformanceEntry & { sources?: LayoutShiftAttribution[] }) | undefined; const node = firstEntry ? (firstEntry.sources ? firstEntry.sources[0].node : undefined) : undefined; - return getWebVital(metric, 'cumulative-layout-shift', node); + return getWebVital(metric, 'cumulative-layout-shift', node, mirror); } /** * Add a FID event to the replay based on a FID metric. */ -export function getFirstInputDelay(metric: Metric): ReplayPerformanceEntry { +export function getFirstInputDelay(metric: Metric, mirror: Mirror): ReplayPerformanceEntry { const lastEntry = metric.entries[metric.entries.length - 1] as (PerformanceEntry & { target?: Node }) | undefined; const node = lastEntry ? lastEntry.target : undefined; - return getWebVital(metric, 'first-input-delay', node); + return getWebVital(metric, 'first-input-delay', node, mirror); } /** * Add an INP event to the replay based on an INP metric. */ -export function getInteractionToNextPaint(metric: Metric): ReplayPerformanceEntry { +export function getInteractionToNextPaint(metric: Metric, mirror: Mirror): ReplayPerformanceEntry { const lastEntry = metric.entries[metric.entries.length - 1] as (PerformanceEntry & { target?: Node }) | undefined; const node = lastEntry ? lastEntry.target : undefined; - return getWebVital(metric, 'interaction-to-next-paint', node); + return getWebVital(metric, 'interaction-to-next-paint', node, mirror); } /** @@ -221,6 +231,7 @@ export function getWebVital( metric: Metric, name: string, node: Node | undefined, + mirror: Mirror | undefined, ): ReplayPerformanceEntry { const value = metric.value; const rating = metric.rating; @@ -236,7 +247,7 @@ export function getWebVital( value, size: value, rating, - nodeId: node ? record.mirror.getId(node) : undefined, + nodeId: node && mirror ? mirror.getId(node) : undefined, }, }; diff --git a/packages/replay-internal/src/util/handleRecordingEmit.ts b/packages/replay-internal/src/util/handleRecordingEmit.ts index eaec29be261a..cd7774fdea9e 100644 --- a/packages/replay-internal/src/util/handleRecordingEmit.ts +++ b/packages/replay-internal/src/util/handleRecordingEmit.ts @@ -32,7 +32,7 @@ export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCa hadFirstEvent = true; if (replay.clickDetector) { - updateClickDetectorForRecordingEvent(replay.clickDetector, event); + updateClickDetectorForRecordingEvent(replay.clickDetector, event, replay.getDomMirror()); } // The handler returns `true` if we do not want to trigger debounced flush, `false` if we want to debounce flush. diff --git a/packages/replay-internal/test/integration/beforeAddRecordingEvent.test.ts b/packages/replay-internal/test/integration/beforeAddRecordingEvent.test.ts index 7f8495375864..2eddc656cecf 100644 --- a/packages/replay-internal/test/integration/beforeAddRecordingEvent.test.ts +++ b/packages/replay-internal/test/integration/beforeAddRecordingEvent.test.ts @@ -2,6 +2,7 @@ import { vi } from 'vitest'; import type { MockInstance, MockedFunction } from 'vitest'; import * as SentryBrowserUtils from '@sentry-internal/browser-utils'; +import { createMirror } from '@sentry-internal/rrweb-snapshot'; import * as SentryCore from '@sentry/core'; import type { Transport } from '@sentry/types'; @@ -143,32 +144,35 @@ describe('Integration | beforeAddRecordingEvent', () => { it('handles error in callback', async () => { createPerformanceSpans( replay, - createPerformanceEntries([ - { - name: 'https://sentry.io/foo.js', - entryType: 'resource', - startTime: 176.59999990463257, - duration: 5.600000023841858, - initiatorType: 'link', - nextHopProtocol: 'h2', - workerStart: 177.5, - redirectStart: 0, - redirectEnd: 0, - fetchStart: 177.69999992847443, - domainLookupStart: 177.69999992847443, - domainLookupEnd: 177.69999992847443, - connectStart: 177.69999992847443, - connectEnd: 177.69999992847443, - secureConnectionStart: 177.69999992847443, - requestStart: 177.5, - responseStart: 181, - responseEnd: 182.19999992847443, - transferSize: 0, - encodedBodySize: 0, - decodedBodySize: 0, - serverTiming: [], - } as unknown as PerformanceResourceTiming, - ]), + createPerformanceEntries( + [ + { + name: 'https://sentry.io/foo.js', + entryType: 'resource', + startTime: 176.59999990463257, + duration: 5.600000023841858, + initiatorType: 'link', + nextHopProtocol: 'h2', + workerStart: 177.5, + redirectStart: 0, + redirectEnd: 0, + fetchStart: 177.69999992847443, + domainLookupStart: 177.69999992847443, + domainLookupEnd: 177.69999992847443, + connectStart: 177.69999992847443, + connectEnd: 177.69999992847443, + secureConnectionStart: 177.69999992847443, + requestStart: 177.5, + responseStart: 181, + responseEnd: 182.19999992847443, + transferSize: 0, + encodedBodySize: 0, + decodedBodySize: 0, + serverTiming: [], + } as unknown as PerformanceResourceTiming, + ], + createMirror(), + ), ); await vi.runAllTimersAsync(); diff --git a/packages/replay-internal/test/integration/flush.test.ts b/packages/replay-internal/test/integration/flush.test.ts index 31fd8a91a5e2..fc2fa32ce428 100644 --- a/packages/replay-internal/test/integration/flush.test.ts +++ b/packages/replay-internal/test/integration/flush.test.ts @@ -6,6 +6,7 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); import * as SentryBrowserUtils from '@sentry-internal/browser-utils'; +import { createMirror } from '@sentry-internal/rrweb-snapshot'; import * as SentryUtils from '@sentry/utils'; import { DEFAULT_FLUSH_MIN_DELAY, MAX_REPLAY_DURATION, WINDOW } from '../../src/constants'; @@ -190,32 +191,35 @@ describe('Integration | flush', () => { Promise.all( createPerformanceSpans( replay, - createPerformanceEntries([ - { - name: 'https://sentry.io/foo.js', - entryType: 'resource', - startTime: 176.59999990463257, - duration: 5.600000023841858, - initiatorType: 'link', - nextHopProtocol: 'h2', - workerStart: 177.5, - redirectStart: 0, - redirectEnd: 0, - fetchStart: 177.69999992847443, - domainLookupStart: 177.69999992847443, - domainLookupEnd: 177.69999992847443, - connectStart: 177.69999992847443, - connectEnd: 177.69999992847443, - secureConnectionStart: 177.69999992847443, - requestStart: 177.5, - responseStart: 181, - responseEnd: 182.19999992847443, - transferSize: 0, - encodedBodySize: 0, - decodedBodySize: 0, - serverTiming: [], - } as unknown as PerformanceResourceTiming, - ]), + createPerformanceEntries( + [ + { + name: 'https://sentry.io/foo.js', + entryType: 'resource', + startTime: 176.59999990463257, + duration: 5.600000023841858, + initiatorType: 'link', + nextHopProtocol: 'h2', + workerStart: 177.5, + redirectStart: 0, + redirectEnd: 0, + fetchStart: 177.69999992847443, + domainLookupStart: 177.69999992847443, + domainLookupEnd: 177.69999992847443, + connectStart: 177.69999992847443, + connectEnd: 177.69999992847443, + secureConnectionStart: 177.69999992847443, + requestStart: 177.5, + responseStart: 181, + responseEnd: 182.19999992847443, + transferSize: 0, + encodedBodySize: 0, + decodedBodySize: 0, + serverTiming: [], + } as unknown as PerformanceResourceTiming, + ], + createMirror(), + ), ), ), ); diff --git a/packages/replay-internal/test/unit/coreHandlers/handleDom.test.ts b/packages/replay-internal/test/unit/coreHandlers/handleDom.test.ts index 29f39a6c0a42..ce891cc440bf 100644 --- a/packages/replay-internal/test/unit/coreHandlers/handleDom.test.ts +++ b/packages/replay-internal/test/unit/coreHandlers/handleDom.test.ts @@ -1,8 +1,11 @@ +import { createMirror } from '@sentry-internal/rrweb-snapshot'; import type { HandlerDataDom } from '@sentry/types'; import { handleDom } from '../../../src/coreHandlers/handleDom'; describe('Unit | coreHandlers | handleDom', () => { + const mirror = createMirror(); + test('it works with a basic click event on a div', () => { const parent = document.createElement('body'); const target = document.createElement('div'); @@ -15,7 +18,7 @@ describe('Unit | coreHandlers | handleDom', () => { target, }, }; - const actual = handleDom(handlerData); + const actual = handleDom(handlerData, mirror); expect(actual).toEqual({ category: 'ui.click', data: {}, @@ -37,7 +40,7 @@ describe('Unit | coreHandlers | handleDom', () => { target, }, }; - const actual = handleDom(handlerData); + const actual = handleDom(handlerData, mirror); expect(actual).toEqual({ category: 'ui.click', data: {}, @@ -62,7 +65,7 @@ describe('Unit | coreHandlers | handleDom', () => { target, }, }; - const actual = handleDom(handlerData); + const actual = handleDom(handlerData, mirror); expect(actual).toEqual({ category: 'ui.click', data: {}, @@ -87,7 +90,7 @@ describe('Unit | coreHandlers | handleDom', () => { target, }, }; - const actual = handleDom(handlerData); + const actual = handleDom(handlerData, mirror); expect(actual).toEqual({ category: 'ui.click', data: {}, @@ -118,7 +121,7 @@ describe('Unit | coreHandlers | handleDom', () => { target, }, }; - const actual = handleDom(handlerData); + const actual = handleDom(handlerData, mirror); expect(actual).toEqual({ category: 'ui.click', data: {}, diff --git a/packages/replay-internal/test/unit/coreHandlers/handleKeyboardEvent.test.ts b/packages/replay-internal/test/unit/coreHandlers/handleKeyboardEvent.test.ts index d08f1ef1a800..74a0a198c43d 100644 --- a/packages/replay-internal/test/unit/coreHandlers/handleKeyboardEvent.test.ts +++ b/packages/replay-internal/test/unit/coreHandlers/handleKeyboardEvent.test.ts @@ -1,16 +1,19 @@ +import { createMirror } from '@sentry-internal/rrweb-snapshot'; import { getKeyboardBreadcrumb } from '../../../src/coreHandlers/handleKeyboardEvent'; describe('Unit | coreHandlers | handleKeyboardEvent', () => { + const mirror = createMirror(); + describe('getKeyboardBreadcrumb', () => { it('returns null for event on input', function () { const event = makeKeyboardEvent({ tagName: 'input', key: 'Escape' }); - const actual = getKeyboardBreadcrumb(event); + const actual = getKeyboardBreadcrumb(event, mirror); expect(actual).toBeNull(); }); it('returns null for event on textarea', function () { const event = makeKeyboardEvent({ tagName: 'textarea', key: 'Escape' }); - const actual = getKeyboardBreadcrumb(event); + const actual = getKeyboardBreadcrumb(event, mirror); expect(actual).toBeNull(); }); @@ -24,13 +27,13 @@ describe('Unit | coreHandlers | handleKeyboardEvent', () => { }); const event = makeKeyboardEvent({ target, key: 'Escape' }); - const actual = getKeyboardBreadcrumb(event); + const actual = getKeyboardBreadcrumb(event, mirror); expect(actual).toBeNull(); }); it('returns breadcrumb for Escape event on body', function () { const event = makeKeyboardEvent({ tagName: 'body', key: 'Escape' }); - const actual = getKeyboardBreadcrumb(event); + const actual = getKeyboardBreadcrumb(event, mirror); expect(actual).toEqual({ category: 'ui.keyDown', data: { @@ -48,19 +51,19 @@ describe('Unit | coreHandlers | handleKeyboardEvent', () => { it.each(['a', '1', '!', '~', ']'])('returns null for %s key on body', key => { const event = makeKeyboardEvent({ tagName: 'body', key }); - const actual = getKeyboardBreadcrumb(event); + const actual = getKeyboardBreadcrumb(event, mirror); expect(actual).toEqual(null); }); it.each(['a', '1', '!', '~', ']'])('returns null for %s key + Shift on body', key => { const event = makeKeyboardEvent({ tagName: 'body', key, shiftKey: true }); - const actual = getKeyboardBreadcrumb(event); + const actual = getKeyboardBreadcrumb(event, mirror); expect(actual).toEqual(null); }); it.each(['a', '1', '!', '~', ']'])('returns breadcrumb for %s key + Ctrl on body', key => { const event = makeKeyboardEvent({ tagName: 'body', key, ctrlKey: true }); - const actual = getKeyboardBreadcrumb(event); + const actual = getKeyboardBreadcrumb(event, mirror); expect(actual).toEqual({ category: 'ui.keyDown', data: { diff --git a/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts b/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts index f13d72feecf4..fc90b8fc23aa 100644 --- a/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts +++ b/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts @@ -10,6 +10,7 @@ vi.mock('@sentry/utils', async () => ({ browserPerformanceTimeOrigin: new Date('2023-01-01').getTime(), })); +import { createMirror } from '@sentry-internal/rrweb-snapshot'; import { WINDOW } from '../../../src/constants'; import { createPerformanceEntries, @@ -21,6 +22,8 @@ import { import { PerformanceEntryNavigation } from '../../fixtures/performanceEntry/navigation'; describe('Unit | util | createPerformanceEntries', () => { + const mirror = createMirror(); + beforeEach(function () { if (!WINDOW.performance.getEntriesByType) { WINDOW.performance.getEntriesByType = vi.fn((type: string) => { @@ -65,7 +68,7 @@ describe('Unit | util | createPerformanceEntries', () => { } as const; // @ts-expect-error Needs a PerformanceEntry mock - expect(createPerformanceEntries([data])).toEqual([]); + expect(createPerformanceEntries([data], mirror)).toEqual([]); }); describe('getLargestContentfulPaint', () => { @@ -76,7 +79,7 @@ describe('Unit | util | createPerformanceEntries', () => { entries: [], }; - const event = getLargestContentfulPaint(metric); + const event = getLargestContentfulPaint(metric, mirror); expect(event).toEqual({ type: 'web-vital', diff --git a/yarn.lock b/yarn.lock index e83c8fe9f310..4e669f2aaecd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7127,22 +7127,24 @@ dependencies: "@sentry-internal/rrweb-snapshot" "2.11.0" -"@sentry-internal/rrdom@2.15.0": - version "2.15.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.15.0.tgz#1ac070a7a00664b2c5351c8ba13979369024128a" - integrity sha512-LDy2LbmEytIuV9vKTr2dK4iMCTTFTpNW/eJ6IoapB0syYBc4yuUsbH39s/gamxcR5Y7KjkySSh0XkMnCHyV5gg== +"@sentry-internal/rrdom@2.20.0": + version "2.20.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.20.0.tgz#da72ded3140e1cb3d90ff8387b05b3473461bbad" + integrity sha512-S+3KB6B/pB5WGNprFdjaUOxE21grUqjMElcUYeC59kR01Hlc41snM+895kGv9/smW+YAUwIFqzSsGfk2koQEDQ== dependencies: - "@sentry-internal/rrweb-snapshot" "2.15.0" + "@sentry-internal/rrweb-snapshot" "2.20.0" "@sentry-internal/rrweb-snapshot@2.11.0": version "2.11.0" resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.11.0.tgz#1af79130604afea989d325465b209ac015b27c9a" integrity sha512-1nP22QlplMNooSNvTh+L30NSZ+E3UcfaJyxXSMLxUjQHTGPyM1VkndxZMmxlKhyR5X+rLbxi/+RvuAcpM43VoA== -"@sentry-internal/rrweb-snapshot@2.15.0": - version "2.15.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.15.0.tgz#04c79d3dc723ed80e4f10685d5ebc6c1b90fcf1b" - integrity sha512-g/gqzKab6lQ/YvioIXVWQTaQXrUctepqIgXP7vYvpnU+ZmxmsOVd10gQuryDCSLYt2wQiwkffYyeaP2BVqxbwQ== +"@sentry-internal/rrweb-snapshot@2.20.0": + version "2.20.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.20.0.tgz#005be8b5a2da4a96f7a863dbed3eccc1c68bfd1f" + integrity sha512-T+KTcOWChgaYfAL0PCUv7PQuMI2k9BG+3X8IlVPgb2/NTPKvJYicHxwC7tcwRM5fCqZpf2Erohyb1R6SuLJJPA== + dependencies: + downlevel-dts "https://github.com/getsentry/downlevel-dts" "@sentry-internal/rrweb-types@2.11.0": version "2.11.0" @@ -7151,12 +7153,14 @@ dependencies: "@sentry-internal/rrweb-snapshot" "2.11.0" -"@sentry-internal/rrweb-types@2.15.0": - version "2.15.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-types/-/rrweb-types-2.15.0.tgz#caeabffc227405110946447f30893aa037493b23" - integrity sha512-D3i9+G4h6gLlG/B1lkP3jc3pM84hP2d2WFGrapTBI0bJou822ERD3Wj9KBVPEkwsRM+qDZRqRMrq0PicdAqJAA== +"@sentry-internal/rrweb-types@2.20.0": + version "2.20.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-types/-/rrweb-types-2.20.0.tgz#4e4531edf68c537a791cf0a44daa9bc3ca1e98ae" + integrity sha512-0LZDglrvCjCdyk9Uc1sJPIdS9IdQEDrfB0S/vC3EaaPteUfdsp4CK4yaXn1cCatbb75Uev5tIICtPZkq9zzqkg== dependencies: - "@sentry-internal/rrweb-snapshot" "2.15.0" + "@sentry-internal/rrweb-snapshot" "2.20.0" + "@types/css-font-loading-module" "0.0.7" + downlevel-dts "https://github.com/getsentry/downlevel-dts" "@sentry-internal/rrweb@2.11.0": version "2.11.0" @@ -7172,17 +7176,18 @@ fflate "^0.4.4" mitt "^3.0.0" -"@sentry-internal/rrweb@2.15.0": - version "2.15.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-2.15.0.tgz#a38dff464624c7ab421579b5ec626007e10c9da8" - integrity sha512-WO2QJJMJYVcuc8aq6j4YEzNo512FZ2Ro7/04Ip1MYhPI4BpHhn3KI7lRoHvprZeVNYWXyBtiPy7JFehuVCppdw== +"@sentry-internal/rrweb@2.20.0": + version "2.20.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-2.20.0.tgz#5f8034542c2562da40b875516762afd04d0d5238" + integrity sha512-6uMZwDDVUtBy8KTegCM9IYUc91yHNJ87o7GMdUpBImPOQqmMt9HsHJl5vur/KBnPFTiAUNeRf1Ar8iMH62auQA== dependencies: - "@sentry-internal/rrdom" "2.15.0" - "@sentry-internal/rrweb-snapshot" "2.15.0" - "@sentry-internal/rrweb-types" "2.15.0" + "@sentry-internal/rrdom" "2.20.0" + "@sentry-internal/rrweb-snapshot" "2.20.0" + "@sentry-internal/rrweb-types" "2.20.0" "@types/css-font-loading-module" "0.0.7" "@xstate/fsm" "^1.4.0" base64-arraybuffer "^1.0.1" + downlevel-dts "https://github.com/getsentry/downlevel-dts" fflate "^0.4.4" mitt "^3.0.0" @@ -8591,17 +8596,8 @@ dependencies: "@types/unist" "*" -"@types/history-4@npm:@types/history@4.7.8": - version "4.7.8" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" - integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== - -"@types/history-5@npm:@types/history@4.7.8": - version "4.7.8" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" - integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== - -"@types/history@*": +"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8", "@types/history@*": + name "@types/history-4" version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== @@ -8960,15 +8956,7 @@ "@types/history" "^3" "@types/react" "*" -"@types/react-router-4@npm:@types/react-router@5.1.14": - version "5.1.14" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" - integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== - dependencies: - "@types/history" "*" - "@types/react" "*" - -"@types/react-router-5@npm:@types/react-router@5.1.14": +"@types/react-router-4@npm:@types/react-router@5.1.14", "@types/react-router-5@npm:@types/react-router@5.1.14": version "5.1.14" resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== @@ -14467,6 +14455,14 @@ dotenv@~10.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== +"downlevel-dts@https://github.com/getsentry/downlevel-dts": + version "0.11.0" + resolved "https://github.com/getsentry/downlevel-dts#26342728379689259f494d79053272c6f2f0ea14" + dependencies: + semver "^7.3.2" + shelljs "^0.8.3" + typescript next + downlevel-dts@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/downlevel-dts/-/downlevel-dts-0.11.0.tgz#514a2d723009c5845730c1db6c994484c596ed9c" @@ -26248,7 +26244,8 @@ react-is@^18.0.0: dependencies: "@remix-run/router" "1.0.2" -"react-router-6@npm:react-router@6.3.0": +"react-router-6@npm:react-router@6.3.0", react-router@6.3.0: + name react-router-6 version "6.3.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== @@ -26263,13 +26260,6 @@ react-router-dom@^6.2.2: history "^5.2.0" react-router "6.3.0" -react-router@6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" - integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== - dependencies: - history "^5.2.0" - react@^18.0.0: version "18.0.0" resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96" @@ -28605,7 +28595,8 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0= -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: + name string-width-cjs version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -28631,15 +28622,6 @@ string-width@^2.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -28735,14 +28717,7 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -31369,7 +31344,8 @@ workerpool@^6.4.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.4.0.tgz#f8d5cfb45fde32fa3b7af72ad617c3369567a462" integrity sha512-i3KR1mQMNwY2wx20ozq2EjISGtQWDIfV56We+yGJ5yDs8jTwQiLLaqHlkBHITlCuJnYlVRmXegxFxZg7gqI++A== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + name wrap-ansi-cjs version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -31387,15 +31363,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"