From 9281810b17c6ff2639e636fa12bd88871ab87667 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 8 Apr 2026 16:31:24 -0400 Subject: [PATCH 1/3] feat(replay): Reset replay id from DSC on session expiry/refresh Its possible that a user returns to an old Sentry tab, an error gets thrown and ingested w/ the expired replay id in DSC. This error then gets link in our UI because of the replay id in DSC and causes the duration to appear to be very long (>>> 1 hr). This PR adds a check in handleGlobalEvent to clear the replay id from DSC if the replay session is expired. It also updates the DSC when in session mode and replay session is refreshed. --- .../src/coreHandlers/handleGlobalEvent.ts | 16 +++ packages/replay-internal/src/replay.ts | 13 ++- .../resetReplayIdOnDynamicSamplingContext.ts | 21 ++++ .../coreHandlers/handleGlobalEvent.test.ts | 106 +++++++++++++++++- .../test/integration/session.test.ts | 51 +++++++++ 5 files changed, 205 insertions(+), 2 deletions(-) diff --git a/packages/replay-internal/src/coreHandlers/handleGlobalEvent.ts b/packages/replay-internal/src/coreHandlers/handleGlobalEvent.ts index b28d4547265e..4b86f7476cdc 100644 --- a/packages/replay-internal/src/coreHandlers/handleGlobalEvent.ts +++ b/packages/replay-internal/src/coreHandlers/handleGlobalEvent.ts @@ -4,6 +4,7 @@ import { saveSession } from '../session/saveSession'; import type { ReplayContainer } from '../types'; import { isErrorEvent, isFeedbackEvent, isReplayEvent, isTransactionEvent } from '../util/eventUtils'; import { isRrwebError } from '../util/isRrwebError'; +import { isSessionExpired } from '../util/isSessionExpired'; import { debug } from '../util/logger'; import { resetReplayIdOnDynamicSamplingContext } from '../util/resetReplayIdOnDynamicSamplingContext'; import { addFeedbackBreadcrumb } from './util/addFeedbackBreadcrumb'; @@ -15,6 +16,21 @@ import { shouldSampleForBufferEvent } from './util/shouldSampleForBufferEvent'; export function handleGlobalEventListener(replay: ReplayContainer): (event: Event, hint: EventHint) => Event | null { return Object.assign( (event: Event, hint: EventHint) => { + // Aggressively check for expired session and clean stale replay_id from DSC. + // This must run BEFORE the isEnabled/isPaused guards because when paused, + // the guards short-circuit without cleaning DSC. The cached DSC on the scope + // (set by browserTracingIntegration when the idle span ended) persists the + // stale replay_id indefinitely until explicitly deleted. + if ( + replay.session && + isSessionExpired(replay.session, { + maxReplayDuration: replay.getOptions().maxReplayDuration, + sessionIdleExpire: replay.timeouts.sessionIdleExpire, + }) + ) { + resetReplayIdOnDynamicSamplingContext(); + } + // Do nothing if replay has been disabled or paused if (!replay.isEnabled() || replay.isPaused()) { return event; diff --git a/packages/replay-internal/src/replay.ts b/packages/replay-internal/src/replay.ts index cab408ca9d5d..5342e318bfb4 100644 --- a/packages/replay-internal/src/replay.ts +++ b/packages/replay-internal/src/replay.ts @@ -52,7 +52,10 @@ import { getHandleRecordingEmit } from './util/handleRecordingEmit'; import { isExpired } from './util/isExpired'; import { isSessionExpired } from './util/isSessionExpired'; import { debug } from './util/logger'; -import { resetReplayIdOnDynamicSamplingContext } from './util/resetReplayIdOnDynamicSamplingContext'; +import { + resetReplayIdOnDynamicSamplingContext, + setReplayIdOnDynamicSamplingContext, +} from './util/resetReplayIdOnDynamicSamplingContext'; import { closestElementOfNode } from './util/rrweb'; import { sendReplay } from './util/sendReplay'; import { RateLimitError, ReplayDurationLimitError } from './util/sendReplayRequest'; @@ -863,6 +866,13 @@ export class ReplayContainer implements ReplayContainerInterface { this._isPaused = false; this.startRecording(); + + // Update the cached DSC with the new replay_id when in session mode. + // The cached DSC on the scope (set by browserTracingIntegration) persists + // across session refreshes, and the `createDsc` hook won't fire for it. + if (this.recordingMode === 'session' && this.session) { + setReplayIdOnDynamicSamplingContext(this.session.id); + } } /** @@ -994,6 +1004,7 @@ export class ReplayContainer implements ReplayContainerInterface { }); if (expired) { + resetReplayIdOnDynamicSamplingContext(); return; } diff --git a/packages/replay-internal/src/util/resetReplayIdOnDynamicSamplingContext.ts b/packages/replay-internal/src/util/resetReplayIdOnDynamicSamplingContext.ts index 7d3139aa447d..4839300d7fd2 100644 --- a/packages/replay-internal/src/util/resetReplayIdOnDynamicSamplingContext.ts +++ b/packages/replay-internal/src/util/resetReplayIdOnDynamicSamplingContext.ts @@ -18,3 +18,24 @@ export function resetReplayIdOnDynamicSamplingContext(): void { delete (dsc as Partial).replay_id; } } + +/** + * Set the `replay_id` field on the cached DSC. + * This is needed after a session refresh because the cached DSC on the scope + * (set by browserTracingIntegration when the idle span ended) persists across + * session boundaries. Without updating it, the new session's replay_id would + * never appear in DSC since `getDynamicSamplingContextFromClient` (and its + * `createDsc` hook) is not called when a cached DSC already exists. + */ +export function setReplayIdOnDynamicSamplingContext(replayId: string): void { + const dsc = getCurrentScope().getPropagationContext().dsc; + if (dsc) { + dsc.replay_id = replayId; + } + + const activeSpan = getActiveSpan(); + if (activeSpan) { + const dsc = getDynamicSamplingContextFromSpan(activeSpan); + (dsc as Partial).replay_id = replayId; + } +} diff --git a/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts b/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts index 956b8a93e72b..17ac9ec14227 100644 --- a/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts +++ b/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts @@ -6,7 +6,12 @@ import '../../utils/mock-internal-setTimeout'; import type { Event } from '@sentry/core'; import { getClient } from '@sentry/core'; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; -import { REPLAY_EVENT_NAME, SESSION_IDLE_EXPIRE_DURATION } from '../../../src/constants'; +import { + MAX_REPLAY_DURATION, + REPLAY_EVENT_NAME, + SESSION_IDLE_EXPIRE_DURATION, + SESSION_IDLE_PAUSE_DURATION, +} from '../../../src/constants'; import { handleGlobalEventListener } from '../../../src/coreHandlers/handleGlobalEvent'; import type { ReplayContainer } from '../../../src/replay'; import { makeSession } from '../../../src/session/Session'; @@ -435,4 +440,103 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { expect(resetReplayIdSpy).toHaveBeenCalledTimes(2); }); + + it('resets replayId on DSC when replay is paused and session has expired', () => { + const now = Date.now(); + + replay.session = makeSession({ + id: 'test-session-id', + segmentId: 0, + lastActivity: now - SESSION_IDLE_EXPIRE_DURATION - 1, + started: now - SESSION_IDLE_EXPIRE_DURATION - 1, + sampled: 'session', + }); + + replay['_isPaused'] = true; + + const resetReplayIdSpy = vi.spyOn( + resetReplayIdOnDynamicSamplingContextModule, + 'resetReplayIdOnDynamicSamplingContext', + ); + + const errorEvent = Error(); + handleGlobalEventListener(replay)(errorEvent, {}); + + // Should have been called even though replay is paused + expect(resetReplayIdSpy).toHaveBeenCalledTimes(1); + }); + + it('does not reset replayId on DSC when replay is paused but session is still valid', () => { + const now = Date.now(); + + replay.session = makeSession({ + id: 'test-session-id', + segmentId: 0, + lastActivity: now, + started: now, + sampled: 'session', + }); + + replay['_isPaused'] = true; + + const resetReplayIdSpy = vi.spyOn( + resetReplayIdOnDynamicSamplingContextModule, + 'resetReplayIdOnDynamicSamplingContext', + ); + + const errorEvent = Error(); + handleGlobalEventListener(replay)(errorEvent, {}); + + // Should NOT have been called because session is still valid + expect(resetReplayIdSpy).not.toHaveBeenCalled(); + }); + + it('resets replayId on DSC when replay is paused and session exceeds max duration', () => { + const now = Date.now(); + + replay.session = makeSession({ + id: 'test-session-id', + segmentId: 0, + // Recent activity, but session started too long ago + lastActivity: now, + started: now - MAX_REPLAY_DURATION - 1, + sampled: 'session', + }); + + replay['_isPaused'] = true; + + const resetReplayIdSpy = vi.spyOn( + resetReplayIdOnDynamicSamplingContextModule, + 'resetReplayIdOnDynamicSamplingContext', + ); + + const errorEvent = Error(); + handleGlobalEventListener(replay)(errorEvent, {}); + + expect(resetReplayIdSpy).toHaveBeenCalledTimes(1); + }); + + it('resets replayId on DSC when replay is disabled and session has expired', () => { + const now = Date.now(); + + replay.session = makeSession({ + id: 'test-session-id', + segmentId: 0, + lastActivity: now - SESSION_IDLE_EXPIRE_DURATION - 1, + started: now - SESSION_IDLE_EXPIRE_DURATION - 1, + sampled: 'session', + }); + + replay['_isEnabled'] = false; + + const resetReplayIdSpy = vi.spyOn( + resetReplayIdOnDynamicSamplingContextModule, + 'resetReplayIdOnDynamicSamplingContext', + ); + + const errorEvent = Error(); + handleGlobalEventListener(replay)(errorEvent, {}); + + expect(resetReplayIdSpy).toHaveBeenCalledTimes(1); + }); }); diff --git a/packages/replay-internal/test/integration/session.test.ts b/packages/replay-internal/test/integration/session.test.ts index f867c43efbe8..1c4b49bb1fad 100644 --- a/packages/replay-internal/test/integration/session.test.ts +++ b/packages/replay-internal/test/integration/session.test.ts @@ -438,6 +438,57 @@ describe('Integration | session', () => { ); }); + it('updates DSC with new replay_id after session refresh', async () => { + const { getCurrentScope } = await import('@sentry/core'); + + const initialSession = { ...replay.session } as Session; + + // Simulate a cached DSC on the scope (as browserTracingIntegration does + // when the idle span ends) with the old session's replay_id. + const scope = getCurrentScope(); + scope.setPropagationContext({ + ...scope.getPropagationContext(), + dsc: { + trace_id: 'test-trace-id', + public_key: 'test-public-key', + replay_id: initialSession.id, + }, + }); + + // Idle past expiration + const ELAPSED = SESSION_IDLE_EXPIRE_DURATION + 1; + vi.advanceTimersByTime(ELAPSED); + + // Emit a recording event to put replay into paused state (mirrors the + // "creates a new session" test which does this before clicking) + const TEST_EVENT = getTestEventIncremental({ + data: { name: 'lost event' }, + timestamp: BASE_TIMESTAMP, + }); + mockRecord._emitter(TEST_EVENT); + await new Promise(process.nextTick); + + expect(replay.isPaused()).toBe(true); + + // Trigger user activity to cause session refresh + domHandler({ + name: 'click', + event: new Event('click'), + }); + + // _refreshSession is async (calls await stop() then initializeSampling) + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); + await new Promise(process.nextTick); + + // Should be a new session + expect(replay).not.toHaveSameSession(initialSession); + + // The cached DSC should now have the NEW session's replay_id, not the old one + const dsc = scope.getPropagationContext().dsc; + expect(dsc?.replay_id).toBe(replay.session?.id); + expect(dsc?.replay_id).not.toBe(initialSession.id); + }); + it('increases segment id after each event', async () => { clearSession(replay); replay['_initializeSessionForSampling'](); From e8f86593b0ee0ec83044fea2a2ed944db50de0c4 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Fri, 1 May 2026 12:17:02 -0400 Subject: [PATCH 2/3] remove unused var --- .../coreHandlers/handleGlobalEvent.test.ts | 260 ++++++++++-------- 1 file changed, 140 insertions(+), 120 deletions(-) diff --git a/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts b/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts index 17ac9ec14227..27739c671c72 100644 --- a/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts +++ b/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts @@ -2,27 +2,34 @@ * @vitest-environment jsdom */ -import '../../utils/mock-internal-setTimeout'; -import type { Event } from '@sentry/core'; -import { getClient } from '@sentry/core'; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import "../../utils/mock-internal-setTimeout"; +import type { Event } from "@sentry/core"; +import { getClient } from "@sentry/core"; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from "vitest"; import { MAX_REPLAY_DURATION, REPLAY_EVENT_NAME, SESSION_IDLE_EXPIRE_DURATION, - SESSION_IDLE_PAUSE_DURATION, -} from '../../../src/constants'; -import { handleGlobalEventListener } from '../../../src/coreHandlers/handleGlobalEvent'; -import type { ReplayContainer } from '../../../src/replay'; -import { makeSession } from '../../../src/session/Session'; -import * as resetReplayIdOnDynamicSamplingContextModule from '../../../src/util/resetReplayIdOnDynamicSamplingContext'; -import { Error } from '../../fixtures/error'; -import { Transaction } from '../../fixtures/transaction'; -import { resetSdkMock } from '../../mocks/resetSdkMock'; +} from "../../../src/constants"; +import { handleGlobalEventListener } from "../../../src/coreHandlers/handleGlobalEvent"; +import type { ReplayContainer } from "../../../src/replay"; +import { makeSession } from "../../../src/session/Session"; +import * as resetReplayIdOnDynamicSamplingContextModule from "../../../src/util/resetReplayIdOnDynamicSamplingContext"; +import { Error } from "../../fixtures/error"; +import { Transaction } from "../../fixtures/transaction"; +import { resetSdkMock } from "../../mocks/resetSdkMock"; let replay: ReplayContainer; -describe('Integration | coreHandlers | handleGlobalEvent', () => { +describe("Integration | coreHandlers | handleGlobalEvent", () => { beforeAll(() => { vi.useFakeTimers(); }); @@ -43,10 +50,10 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { replay.stop(); }); - it('deletes breadcrumbs from replay events', () => { + it("deletes breadcrumbs from replay events", () => { const replayEvent = { type: REPLAY_EVENT_NAME, - breadcrumbs: [{ type: 'fakecrumb' }], + breadcrumbs: [{ type: "fakecrumb" }], }; // @ts-expect-error replay event type @@ -55,35 +62,35 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { }); }); - it('does not delete breadcrumbs from error and transaction events', () => { + it("does not delete breadcrumbs from error and transaction events", () => { expect( handleGlobalEventListener(replay)( { - breadcrumbs: [{ type: 'fakecrumb' }], + breadcrumbs: [{ type: "fakecrumb" }], }, {}, ), ).toEqual( expect.objectContaining({ - breadcrumbs: [{ type: 'fakecrumb' }], + breadcrumbs: [{ type: "fakecrumb" }], }), ); expect( handleGlobalEventListener(replay)( { - type: 'transaction', - breadcrumbs: [{ type: 'fakecrumb' }], + type: "transaction", + breadcrumbs: [{ type: "fakecrumb" }], }, {}, ), ).toEqual( expect.objectContaining({ - breadcrumbs: [{ type: 'fakecrumb' }], + breadcrumbs: [{ type: "fakecrumb" }], }), ); }); - it('does not add replayId for transactions in error mode', async () => { + it("does not add replayId for transactions in error mode", async () => { const transaction = Transaction(); const error = Error(); expect(handleGlobalEventListener(replay)(transaction, {})).toEqual( @@ -99,11 +106,11 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { ); }); - it('does not add replayId if replay is not enabled', async () => { + it("does not add replayId if replay is not enabled", async () => { const transaction = Transaction(); const error = Error(); - replay['_isEnabled'] = false; + replay["_isEnabled"] = false; expect(handleGlobalEventListener(replay)(transaction, {})).toEqual( expect.not.objectContaining({ @@ -118,18 +125,18 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { ); }); - it('does not add replayId if replay session is expired', async () => { + it("does not add replayId if replay session is expired", async () => { const transaction = Transaction(); const error = Error(); const now = Date.now(); replay.session = makeSession({ - id: 'test-session-id', + id: "test-session-id", segmentId: 0, lastActivity: now - SESSION_IDLE_EXPIRE_DURATION - 1, started: now - SESSION_IDLE_EXPIRE_DURATION - 1, - sampled: 'session', + sampled: "session", }); expect(handleGlobalEventListener(replay)(transaction, {})).toEqual( @@ -145,9 +152,9 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { ); }); - it('tags errors and transactions with replay id for session samples', async () => { + it("tags errors and transactions with replay id for session samples", async () => { const { replay, integration } = await resetSdkMock({}); - integration['_initialize'](getClient()!); + integration["_initialize"](getClient()!); const transaction = Transaction(); const error = Error(); @@ -163,10 +170,10 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { ); }); - it('does not collect errorIds', async () => { - const error1 = Error({ event_id: 'err1' }); - const error2 = Error({ event_id: 'err2' }); - const error3 = Error({ event_id: 'err3' }); + it("does not collect errorIds", async () => { + const error1 = Error({ event_id: "err1" }); + const error2 = Error({ event_id: "err2" }); + const error3 = Error({ event_id: "err3" }); const handler = handleGlobalEventListener(replay); @@ -177,10 +184,10 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { expect(Array.from(replay.getContext().errorIds)).toEqual([]); }); - it('does not collect traceIds', async () => { - const transaction1 = Transaction('tr1'); - const transaction2 = Transaction('tr2'); - const transaction3 = Transaction('tr3'); + it("does not collect traceIds", async () => { + const transaction1 = Transaction("tr1"); + const transaction2 = Transaction("tr2"); + const transaction3 = Transaction("tr3"); const handler = handleGlobalEventListener(replay); @@ -191,13 +198,13 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { expect(Array.from(replay.getContext().traceIds)).toEqual([]); }); - it('ignores profile & replay events', async () => { - const profileEvent: Event = { type: 'profile' }; - const replayEvent: Event = { type: 'replay_event' }; + it("ignores profile & replay events", async () => { + const profileEvent: Event = { type: "profile" }; + const replayEvent: Event = { type: "replay_event" }; const handler = handleGlobalEventListener(replay); - expect(replay.recordingMode).toBe('buffer'); + expect(replay.recordingMode).toBe("buffer"); handler(profileEvent, {}); handler(replayEvent, {}); @@ -211,205 +218,218 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { expect(Array.from(replay.getContext().errorIds)).toEqual([]); expect(replay.isEnabled()).toBe(true); expect(replay.isPaused()).toBe(false); - expect(replay.recordingMode).toBe('buffer'); + expect(replay.recordingMode).toBe("buffer"); }); - it('does not skip non-rrweb errors', () => { + it("does not skip non-rrweb errors", () => { const errorEvent: Event = { exception: { values: [ { - type: 'TypeError', + type: "TypeError", value: "Cannot read properties of undefined (reading 'contains')", stacktrace: { frames: [ { - filename: 'http://example.com/..node_modules/packages/replay/build/npm/some-other-file.js', - function: 'MutationBuffer.processMutations', + filename: + "http://example.com/..node_modules/packages/replay/build/npm/some-other-file.js", + function: "MutationBuffer.processMutations", in_app: true, lineno: 101, colno: 23, }, { - filename: '', - function: 'Array.forEach', + filename: "", + function: "Array.forEach", in_app: true, }, ], }, mechanism: { - type: 'generic', + type: "generic", handled: true, }, }, ], }, - level: 'error', - event_id: 'ff1616b1e13744c6964281349aecc82a', + level: "error", + event_id: "ff1616b1e13744c6964281349aecc82a", }; - expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual(errorEvent); + expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual( + errorEvent, + ); }); - it('skips exception with __rrweb__ set', () => { + it("skips exception with __rrweb__ set", () => { const errorEvent: Event = { exception: { values: [ { - type: 'TypeError', + type: "TypeError", value: "Cannot read properties of undefined (reading 'contains')", stacktrace: { frames: [ { - filename: 'scrambled.js', - function: 'MutationBuffer.processMutations', + filename: "scrambled.js", + function: "MutationBuffer.processMutations", in_app: true, lineno: 101, colno: 23, }, { - filename: '', - function: 'Array.forEach', + filename: "", + function: "Array.forEach", in_app: true, }, ], }, mechanism: { - type: 'generic', + type: "generic", handled: true, }, }, ], }, - level: 'error', - event_id: 'ff1616b1e13744c6964281349aecc82a', + level: "error", + event_id: "ff1616b1e13744c6964281349aecc82a", }; - const originalException = new window.Error('some exception'); + const originalException = new window.Error("some exception"); // @ts-expect-error this could be set by rrweb originalException.__rrweb__ = true; - expect(handleGlobalEventListener(replay)(errorEvent, { originalException })).toEqual(null); + expect( + handleGlobalEventListener(replay)(errorEvent, { originalException }), + ).toEqual(null); }); - it('handles string exceptions', () => { + it("handles string exceptions", () => { const errorEvent: Event = { exception: { values: [ { - type: 'TypeError', + type: "TypeError", value: "Cannot read properties of undefined (reading 'contains')", stacktrace: { frames: [ { - filename: 'scrambled.js', - function: 'MutationBuffer.processMutations', + filename: "scrambled.js", + function: "MutationBuffer.processMutations", in_app: true, lineno: 101, colno: 23, }, { - filename: '', - function: 'Array.forEach', + filename: "", + function: "Array.forEach", in_app: true, }, ], }, mechanism: { - type: 'generic', + type: "generic", handled: true, }, }, ], }, - level: 'error', - event_id: 'ff1616b1e13744c6964281349aecc82a', + level: "error", + event_id: "ff1616b1e13744c6964281349aecc82a", }; - const originalException = 'some string exception'; + const originalException = "some string exception"; - expect(handleGlobalEventListener(replay)(errorEvent, { originalException })).toEqual(errorEvent); + expect( + handleGlobalEventListener(replay)(errorEvent, { originalException }), + ).toEqual(errorEvent); }); - it('does not skip rrweb internal errors with _experiments.captureExceptions', () => { + it("does not skip rrweb internal errors with _experiments.captureExceptions", () => { const errorEvent: Event = { exception: { values: [ { - type: 'TypeError', + type: "TypeError", value: "Cannot read properties of undefined (reading 'contains')", stacktrace: { frames: [ { filename: - 'http://example.com/..node_modules/packages/replay/build/npm/esm/node_modules/rrweb/es/rrweb/packages/rrweb/src/record/mutation.js?v=90704e8a', - function: 'MutationBuffer.processMutations', + "http://example.com/..node_modules/packages/replay/build/npm/esm/node_modules/rrweb/es/rrweb/packages/rrweb/src/record/mutation.js?v=90704e8a", + function: "MutationBuffer.processMutations", in_app: true, lineno: 101, colno: 23, }, { - filename: '', - function: 'Array.forEach', + filename: "", + function: "Array.forEach", in_app: true, }, ], }, mechanism: { - type: 'generic', + type: "generic", handled: true, }, }, ], }, - level: 'error', - event_id: 'ff1616b1e13744c6964281349aecc82a', + level: "error", + event_id: "ff1616b1e13744c6964281349aecc82a", }; replay.getOptions()._experiments = { captureExceptions: true }; - expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual(errorEvent); + expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual( + errorEvent, + ); }); - it('does not skip non-rrweb errors when no stacktrace exists', () => { + it("does not skip non-rrweb errors when no stacktrace exists", () => { const errorEvent: Event = { exception: { values: [ { - type: 'TypeError', + type: "TypeError", value: "Cannot read properties of undefined (reading 'contains')", stacktrace: { frames: [], }, mechanism: { - type: 'generic', + type: "generic", handled: true, }, }, ], }, - level: 'error', - event_id: 'ff1616b1e13744c6964281349aecc82a', + level: "error", + event_id: "ff1616b1e13744c6964281349aecc82a", }; - expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual(errorEvent); + expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual( + errorEvent, + ); }); - it('does not skip non-rrweb errors when no exception', () => { + it("does not skip non-rrweb errors when no exception", () => { const errorEvent: Event = { exception: undefined, - level: 'error', - event_id: 'ff1616b1e13744c6964281349aecc82a', + level: "error", + event_id: "ff1616b1e13744c6964281349aecc82a", }; - expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual(errorEvent); + expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual( + errorEvent, + ); }); - it('does not add replayId if replay is paused', async () => { + it("does not add replayId if replay is paused", async () => { const transaction = Transaction(); const error = Error(); - replay['_isPaused'] = true; + replay["_isPaused"] = true; expect(handleGlobalEventListener(replay)(transaction, {})).toEqual( expect.not.objectContaining({ @@ -424,15 +444,15 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { ); }); - it('resets replayId on DSC when session expires', () => { + it("resets replayId on DSC when session expires", () => { const errorEvent = Error(); const txEvent = Transaction(); - vi.spyOn(replay, 'checkAndHandleExpiredSession').mockReturnValue(false); + vi.spyOn(replay, "checkAndHandleExpiredSession").mockReturnValue(false); const resetReplayIdSpy = vi.spyOn( resetReplayIdOnDynamicSamplingContextModule, - 'resetReplayIdOnDynamicSamplingContext', + "resetReplayIdOnDynamicSamplingContext", ); handleGlobalEventListener(replay)(errorEvent, {}); @@ -441,22 +461,22 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { expect(resetReplayIdSpy).toHaveBeenCalledTimes(2); }); - it('resets replayId on DSC when replay is paused and session has expired', () => { + it("resets replayId on DSC when replay is paused and session has expired", () => { const now = Date.now(); replay.session = makeSession({ - id: 'test-session-id', + id: "test-session-id", segmentId: 0, lastActivity: now - SESSION_IDLE_EXPIRE_DURATION - 1, started: now - SESSION_IDLE_EXPIRE_DURATION - 1, - sampled: 'session', + sampled: "session", }); - replay['_isPaused'] = true; + replay["_isPaused"] = true; const resetReplayIdSpy = vi.spyOn( resetReplayIdOnDynamicSamplingContextModule, - 'resetReplayIdOnDynamicSamplingContext', + "resetReplayIdOnDynamicSamplingContext", ); const errorEvent = Error(); @@ -466,22 +486,22 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { expect(resetReplayIdSpy).toHaveBeenCalledTimes(1); }); - it('does not reset replayId on DSC when replay is paused but session is still valid', () => { + it("does not reset replayId on DSC when replay is paused but session is still valid", () => { const now = Date.now(); replay.session = makeSession({ - id: 'test-session-id', + id: "test-session-id", segmentId: 0, lastActivity: now, started: now, - sampled: 'session', + sampled: "session", }); - replay['_isPaused'] = true; + replay["_isPaused"] = true; const resetReplayIdSpy = vi.spyOn( resetReplayIdOnDynamicSamplingContextModule, - 'resetReplayIdOnDynamicSamplingContext', + "resetReplayIdOnDynamicSamplingContext", ); const errorEvent = Error(); @@ -491,23 +511,23 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { expect(resetReplayIdSpy).not.toHaveBeenCalled(); }); - it('resets replayId on DSC when replay is paused and session exceeds max duration', () => { + it("resets replayId on DSC when replay is paused and session exceeds max duration", () => { const now = Date.now(); replay.session = makeSession({ - id: 'test-session-id', + id: "test-session-id", segmentId: 0, // Recent activity, but session started too long ago lastActivity: now, started: now - MAX_REPLAY_DURATION - 1, - sampled: 'session', + sampled: "session", }); - replay['_isPaused'] = true; + replay["_isPaused"] = true; const resetReplayIdSpy = vi.spyOn( resetReplayIdOnDynamicSamplingContextModule, - 'resetReplayIdOnDynamicSamplingContext', + "resetReplayIdOnDynamicSamplingContext", ); const errorEvent = Error(); @@ -516,22 +536,22 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { expect(resetReplayIdSpy).toHaveBeenCalledTimes(1); }); - it('resets replayId on DSC when replay is disabled and session has expired', () => { + it("resets replayId on DSC when replay is disabled and session has expired", () => { const now = Date.now(); replay.session = makeSession({ - id: 'test-session-id', + id: "test-session-id", segmentId: 0, lastActivity: now - SESSION_IDLE_EXPIRE_DURATION - 1, started: now - SESSION_IDLE_EXPIRE_DURATION - 1, - sampled: 'session', + sampled: "session", }); - replay['_isEnabled'] = false; + replay["_isEnabled"] = false; const resetReplayIdSpy = vi.spyOn( resetReplayIdOnDynamicSamplingContextModule, - 'resetReplayIdOnDynamicSamplingContext', + "resetReplayIdOnDynamicSamplingContext", ); const errorEvent = Error(); From a099adf3a1a3408ca752ff8955ad9dfaee4b3878 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Fri, 1 May 2026 12:22:57 -0400 Subject: [PATCH 3/3] format --- .../coreHandlers/handleGlobalEvent.test.ts | 263 ++++++++---------- 1 file changed, 119 insertions(+), 144 deletions(-) diff --git a/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts b/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts index 27739c671c72..d8a979c02b94 100644 --- a/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts +++ b/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts @@ -2,34 +2,22 @@ * @vitest-environment jsdom */ -import "../../utils/mock-internal-setTimeout"; -import type { Event } from "@sentry/core"; -import { getClient } from "@sentry/core"; -import { - afterEach, - beforeAll, - beforeEach, - describe, - expect, - it, - vi, -} from "vitest"; -import { - MAX_REPLAY_DURATION, - REPLAY_EVENT_NAME, - SESSION_IDLE_EXPIRE_DURATION, -} from "../../../src/constants"; -import { handleGlobalEventListener } from "../../../src/coreHandlers/handleGlobalEvent"; -import type { ReplayContainer } from "../../../src/replay"; -import { makeSession } from "../../../src/session/Session"; -import * as resetReplayIdOnDynamicSamplingContextModule from "../../../src/util/resetReplayIdOnDynamicSamplingContext"; -import { Error } from "../../fixtures/error"; -import { Transaction } from "../../fixtures/transaction"; -import { resetSdkMock } from "../../mocks/resetSdkMock"; +import '../../utils/mock-internal-setTimeout'; +import type { Event } from '@sentry/core'; +import { getClient } from '@sentry/core'; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { MAX_REPLAY_DURATION, REPLAY_EVENT_NAME, SESSION_IDLE_EXPIRE_DURATION } from '../../../src/constants'; +import { handleGlobalEventListener } from '../../../src/coreHandlers/handleGlobalEvent'; +import type { ReplayContainer } from '../../../src/replay'; +import { makeSession } from '../../../src/session/Session'; +import * as resetReplayIdOnDynamicSamplingContextModule from '../../../src/util/resetReplayIdOnDynamicSamplingContext'; +import { Error } from '../../fixtures/error'; +import { Transaction } from '../../fixtures/transaction'; +import { resetSdkMock } from '../../mocks/resetSdkMock'; let replay: ReplayContainer; -describe("Integration | coreHandlers | handleGlobalEvent", () => { +describe('Integration | coreHandlers | handleGlobalEvent', () => { beforeAll(() => { vi.useFakeTimers(); }); @@ -50,10 +38,10 @@ describe("Integration | coreHandlers | handleGlobalEvent", () => { replay.stop(); }); - it("deletes breadcrumbs from replay events", () => { + it('deletes breadcrumbs from replay events', () => { const replayEvent = { type: REPLAY_EVENT_NAME, - breadcrumbs: [{ type: "fakecrumb" }], + breadcrumbs: [{ type: 'fakecrumb' }], }; // @ts-expect-error replay event type @@ -62,35 +50,35 @@ describe("Integration | coreHandlers | handleGlobalEvent", () => { }); }); - it("does not delete breadcrumbs from error and transaction events", () => { + it('does not delete breadcrumbs from error and transaction events', () => { expect( handleGlobalEventListener(replay)( { - breadcrumbs: [{ type: "fakecrumb" }], + breadcrumbs: [{ type: 'fakecrumb' }], }, {}, ), ).toEqual( expect.objectContaining({ - breadcrumbs: [{ type: "fakecrumb" }], + breadcrumbs: [{ type: 'fakecrumb' }], }), ); expect( handleGlobalEventListener(replay)( { - type: "transaction", - breadcrumbs: [{ type: "fakecrumb" }], + type: 'transaction', + breadcrumbs: [{ type: 'fakecrumb' }], }, {}, ), ).toEqual( expect.objectContaining({ - breadcrumbs: [{ type: "fakecrumb" }], + breadcrumbs: [{ type: 'fakecrumb' }], }), ); }); - it("does not add replayId for transactions in error mode", async () => { + it('does not add replayId for transactions in error mode', async () => { const transaction = Transaction(); const error = Error(); expect(handleGlobalEventListener(replay)(transaction, {})).toEqual( @@ -106,11 +94,11 @@ describe("Integration | coreHandlers | handleGlobalEvent", () => { ); }); - it("does not add replayId if replay is not enabled", async () => { + it('does not add replayId if replay is not enabled', async () => { const transaction = Transaction(); const error = Error(); - replay["_isEnabled"] = false; + replay['_isEnabled'] = false; expect(handleGlobalEventListener(replay)(transaction, {})).toEqual( expect.not.objectContaining({ @@ -125,18 +113,18 @@ describe("Integration | coreHandlers | handleGlobalEvent", () => { ); }); - it("does not add replayId if replay session is expired", async () => { + it('does not add replayId if replay session is expired', async () => { const transaction = Transaction(); const error = Error(); const now = Date.now(); replay.session = makeSession({ - id: "test-session-id", + id: 'test-session-id', segmentId: 0, lastActivity: now - SESSION_IDLE_EXPIRE_DURATION - 1, started: now - SESSION_IDLE_EXPIRE_DURATION - 1, - sampled: "session", + sampled: 'session', }); expect(handleGlobalEventListener(replay)(transaction, {})).toEqual( @@ -152,9 +140,9 @@ describe("Integration | coreHandlers | handleGlobalEvent", () => { ); }); - it("tags errors and transactions with replay id for session samples", async () => { + it('tags errors and transactions with replay id for session samples', async () => { const { replay, integration } = await resetSdkMock({}); - integration["_initialize"](getClient()!); + integration['_initialize'](getClient()!); const transaction = Transaction(); const error = Error(); @@ -170,10 +158,10 @@ describe("Integration | coreHandlers | handleGlobalEvent", () => { ); }); - it("does not collect errorIds", async () => { - const error1 = Error({ event_id: "err1" }); - const error2 = Error({ event_id: "err2" }); - const error3 = Error({ event_id: "err3" }); + it('does not collect errorIds', async () => { + const error1 = Error({ event_id: 'err1' }); + const error2 = Error({ event_id: 'err2' }); + const error3 = Error({ event_id: 'err3' }); const handler = handleGlobalEventListener(replay); @@ -184,10 +172,10 @@ describe("Integration | coreHandlers | handleGlobalEvent", () => { expect(Array.from(replay.getContext().errorIds)).toEqual([]); }); - it("does not collect traceIds", async () => { - const transaction1 = Transaction("tr1"); - const transaction2 = Transaction("tr2"); - const transaction3 = Transaction("tr3"); + it('does not collect traceIds', async () => { + const transaction1 = Transaction('tr1'); + const transaction2 = Transaction('tr2'); + const transaction3 = Transaction('tr3'); const handler = handleGlobalEventListener(replay); @@ -198,13 +186,13 @@ describe("Integration | coreHandlers | handleGlobalEvent", () => { expect(Array.from(replay.getContext().traceIds)).toEqual([]); }); - it("ignores profile & replay events", async () => { - const profileEvent: Event = { type: "profile" }; - const replayEvent: Event = { type: "replay_event" }; + it('ignores profile & replay events', async () => { + const profileEvent: Event = { type: 'profile' }; + const replayEvent: Event = { type: 'replay_event' }; const handler = handleGlobalEventListener(replay); - expect(replay.recordingMode).toBe("buffer"); + expect(replay.recordingMode).toBe('buffer'); handler(profileEvent, {}); handler(replayEvent, {}); @@ -218,218 +206,205 @@ describe("Integration | coreHandlers | handleGlobalEvent", () => { expect(Array.from(replay.getContext().errorIds)).toEqual([]); expect(replay.isEnabled()).toBe(true); expect(replay.isPaused()).toBe(false); - expect(replay.recordingMode).toBe("buffer"); + expect(replay.recordingMode).toBe('buffer'); }); - it("does not skip non-rrweb errors", () => { + it('does not skip non-rrweb errors', () => { const errorEvent: Event = { exception: { values: [ { - type: "TypeError", + type: 'TypeError', value: "Cannot read properties of undefined (reading 'contains')", stacktrace: { frames: [ { - filename: - "http://example.com/..node_modules/packages/replay/build/npm/some-other-file.js", - function: "MutationBuffer.processMutations", + filename: 'http://example.com/..node_modules/packages/replay/build/npm/some-other-file.js', + function: 'MutationBuffer.processMutations', in_app: true, lineno: 101, colno: 23, }, { - filename: "", - function: "Array.forEach", + filename: '', + function: 'Array.forEach', in_app: true, }, ], }, mechanism: { - type: "generic", + type: 'generic', handled: true, }, }, ], }, - level: "error", - event_id: "ff1616b1e13744c6964281349aecc82a", + level: 'error', + event_id: 'ff1616b1e13744c6964281349aecc82a', }; - expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual( - errorEvent, - ); + expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual(errorEvent); }); - it("skips exception with __rrweb__ set", () => { + it('skips exception with __rrweb__ set', () => { const errorEvent: Event = { exception: { values: [ { - type: "TypeError", + type: 'TypeError', value: "Cannot read properties of undefined (reading 'contains')", stacktrace: { frames: [ { - filename: "scrambled.js", - function: "MutationBuffer.processMutations", + filename: 'scrambled.js', + function: 'MutationBuffer.processMutations', in_app: true, lineno: 101, colno: 23, }, { - filename: "", - function: "Array.forEach", + filename: '', + function: 'Array.forEach', in_app: true, }, ], }, mechanism: { - type: "generic", + type: 'generic', handled: true, }, }, ], }, - level: "error", - event_id: "ff1616b1e13744c6964281349aecc82a", + level: 'error', + event_id: 'ff1616b1e13744c6964281349aecc82a', }; - const originalException = new window.Error("some exception"); + const originalException = new window.Error('some exception'); // @ts-expect-error this could be set by rrweb originalException.__rrweb__ = true; - expect( - handleGlobalEventListener(replay)(errorEvent, { originalException }), - ).toEqual(null); + expect(handleGlobalEventListener(replay)(errorEvent, { originalException })).toEqual(null); }); - it("handles string exceptions", () => { + it('handles string exceptions', () => { const errorEvent: Event = { exception: { values: [ { - type: "TypeError", + type: 'TypeError', value: "Cannot read properties of undefined (reading 'contains')", stacktrace: { frames: [ { - filename: "scrambled.js", - function: "MutationBuffer.processMutations", + filename: 'scrambled.js', + function: 'MutationBuffer.processMutations', in_app: true, lineno: 101, colno: 23, }, { - filename: "", - function: "Array.forEach", + filename: '', + function: 'Array.forEach', in_app: true, }, ], }, mechanism: { - type: "generic", + type: 'generic', handled: true, }, }, ], }, - level: "error", - event_id: "ff1616b1e13744c6964281349aecc82a", + level: 'error', + event_id: 'ff1616b1e13744c6964281349aecc82a', }; - const originalException = "some string exception"; + const originalException = 'some string exception'; - expect( - handleGlobalEventListener(replay)(errorEvent, { originalException }), - ).toEqual(errorEvent); + expect(handleGlobalEventListener(replay)(errorEvent, { originalException })).toEqual(errorEvent); }); - it("does not skip rrweb internal errors with _experiments.captureExceptions", () => { + it('does not skip rrweb internal errors with _experiments.captureExceptions', () => { const errorEvent: Event = { exception: { values: [ { - type: "TypeError", + type: 'TypeError', value: "Cannot read properties of undefined (reading 'contains')", stacktrace: { frames: [ { filename: - "http://example.com/..node_modules/packages/replay/build/npm/esm/node_modules/rrweb/es/rrweb/packages/rrweb/src/record/mutation.js?v=90704e8a", - function: "MutationBuffer.processMutations", + 'http://example.com/..node_modules/packages/replay/build/npm/esm/node_modules/rrweb/es/rrweb/packages/rrweb/src/record/mutation.js?v=90704e8a', + function: 'MutationBuffer.processMutations', in_app: true, lineno: 101, colno: 23, }, { - filename: "", - function: "Array.forEach", + filename: '', + function: 'Array.forEach', in_app: true, }, ], }, mechanism: { - type: "generic", + type: 'generic', handled: true, }, }, ], }, - level: "error", - event_id: "ff1616b1e13744c6964281349aecc82a", + level: 'error', + event_id: 'ff1616b1e13744c6964281349aecc82a', }; replay.getOptions()._experiments = { captureExceptions: true }; - expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual( - errorEvent, - ); + expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual(errorEvent); }); - it("does not skip non-rrweb errors when no stacktrace exists", () => { + it('does not skip non-rrweb errors when no stacktrace exists', () => { const errorEvent: Event = { exception: { values: [ { - type: "TypeError", + type: 'TypeError', value: "Cannot read properties of undefined (reading 'contains')", stacktrace: { frames: [], }, mechanism: { - type: "generic", + type: 'generic', handled: true, }, }, ], }, - level: "error", - event_id: "ff1616b1e13744c6964281349aecc82a", + level: 'error', + event_id: 'ff1616b1e13744c6964281349aecc82a', }; - expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual( - errorEvent, - ); + expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual(errorEvent); }); - it("does not skip non-rrweb errors when no exception", () => { + it('does not skip non-rrweb errors when no exception', () => { const errorEvent: Event = { exception: undefined, - level: "error", - event_id: "ff1616b1e13744c6964281349aecc82a", + level: 'error', + event_id: 'ff1616b1e13744c6964281349aecc82a', }; - expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual( - errorEvent, - ); + expect(handleGlobalEventListener(replay)(errorEvent, {})).toEqual(errorEvent); }); - it("does not add replayId if replay is paused", async () => { + it('does not add replayId if replay is paused', async () => { const transaction = Transaction(); const error = Error(); - replay["_isPaused"] = true; + replay['_isPaused'] = true; expect(handleGlobalEventListener(replay)(transaction, {})).toEqual( expect.not.objectContaining({ @@ -444,15 +419,15 @@ describe("Integration | coreHandlers | handleGlobalEvent", () => { ); }); - it("resets replayId on DSC when session expires", () => { + it('resets replayId on DSC when session expires', () => { const errorEvent = Error(); const txEvent = Transaction(); - vi.spyOn(replay, "checkAndHandleExpiredSession").mockReturnValue(false); + vi.spyOn(replay, 'checkAndHandleExpiredSession').mockReturnValue(false); const resetReplayIdSpy = vi.spyOn( resetReplayIdOnDynamicSamplingContextModule, - "resetReplayIdOnDynamicSamplingContext", + 'resetReplayIdOnDynamicSamplingContext', ); handleGlobalEventListener(replay)(errorEvent, {}); @@ -461,22 +436,22 @@ describe("Integration | coreHandlers | handleGlobalEvent", () => { expect(resetReplayIdSpy).toHaveBeenCalledTimes(2); }); - it("resets replayId on DSC when replay is paused and session has expired", () => { + it('resets replayId on DSC when replay is paused and session has expired', () => { const now = Date.now(); replay.session = makeSession({ - id: "test-session-id", + id: 'test-session-id', segmentId: 0, lastActivity: now - SESSION_IDLE_EXPIRE_DURATION - 1, started: now - SESSION_IDLE_EXPIRE_DURATION - 1, - sampled: "session", + sampled: 'session', }); - replay["_isPaused"] = true; + replay['_isPaused'] = true; const resetReplayIdSpy = vi.spyOn( resetReplayIdOnDynamicSamplingContextModule, - "resetReplayIdOnDynamicSamplingContext", + 'resetReplayIdOnDynamicSamplingContext', ); const errorEvent = Error(); @@ -486,22 +461,22 @@ describe("Integration | coreHandlers | handleGlobalEvent", () => { expect(resetReplayIdSpy).toHaveBeenCalledTimes(1); }); - it("does not reset replayId on DSC when replay is paused but session is still valid", () => { + it('does not reset replayId on DSC when replay is paused but session is still valid', () => { const now = Date.now(); replay.session = makeSession({ - id: "test-session-id", + id: 'test-session-id', segmentId: 0, lastActivity: now, started: now, - sampled: "session", + sampled: 'session', }); - replay["_isPaused"] = true; + replay['_isPaused'] = true; const resetReplayIdSpy = vi.spyOn( resetReplayIdOnDynamicSamplingContextModule, - "resetReplayIdOnDynamicSamplingContext", + 'resetReplayIdOnDynamicSamplingContext', ); const errorEvent = Error(); @@ -511,23 +486,23 @@ describe("Integration | coreHandlers | handleGlobalEvent", () => { expect(resetReplayIdSpy).not.toHaveBeenCalled(); }); - it("resets replayId on DSC when replay is paused and session exceeds max duration", () => { + it('resets replayId on DSC when replay is paused and session exceeds max duration', () => { const now = Date.now(); replay.session = makeSession({ - id: "test-session-id", + id: 'test-session-id', segmentId: 0, // Recent activity, but session started too long ago lastActivity: now, started: now - MAX_REPLAY_DURATION - 1, - sampled: "session", + sampled: 'session', }); - replay["_isPaused"] = true; + replay['_isPaused'] = true; const resetReplayIdSpy = vi.spyOn( resetReplayIdOnDynamicSamplingContextModule, - "resetReplayIdOnDynamicSamplingContext", + 'resetReplayIdOnDynamicSamplingContext', ); const errorEvent = Error(); @@ -536,22 +511,22 @@ describe("Integration | coreHandlers | handleGlobalEvent", () => { expect(resetReplayIdSpy).toHaveBeenCalledTimes(1); }); - it("resets replayId on DSC when replay is disabled and session has expired", () => { + it('resets replayId on DSC when replay is disabled and session has expired', () => { const now = Date.now(); replay.session = makeSession({ - id: "test-session-id", + id: 'test-session-id', segmentId: 0, lastActivity: now - SESSION_IDLE_EXPIRE_DURATION - 1, started: now - SESSION_IDLE_EXPIRE_DURATION - 1, - sampled: "session", + sampled: 'session', }); - replay["_isEnabled"] = false; + replay['_isEnabled'] = false; const resetReplayIdSpy = vi.spyOn( resetReplayIdOnDynamicSamplingContextModule, - "resetReplayIdOnDynamicSamplingContext", + 'resetReplayIdOnDynamicSamplingContext', ); const errorEvent = Error();