diff --git a/packages/replay-internal/src/replay.ts b/packages/replay-internal/src/replay.ts index 6588a9e4d79d..16f7a6e7d884 100644 --- a/packages/replay-internal/src/replay.ts +++ b/packages/replay-internal/src/replay.ts @@ -28,6 +28,7 @@ import { clearSession } from './session/clearSession'; import { loadOrCreateSession } from './session/loadOrCreateSession'; import { saveSession } from './session/saveSession'; import { shouldRefreshSession } from './session/shouldRefreshSession'; + import type { AddEventResult, AddUpdateCallback, @@ -295,6 +296,12 @@ export class ReplayContainer implements ReplayContainerInterface { logInfoNextTick('[Replay] Starting replay in session mode', this._options._experiments.traceInternals); + // Required as user activity is initially set in + // constructor, so if `start()` is called after + // session idle expiration, a replay will not be + // created due to an idle timeout. + this._updateUserActivity(); + const session = loadOrCreateSession( { maxReplayDuration: this._options.maxReplayDuration, diff --git a/packages/replay-internal/test/integration/start.test.ts b/packages/replay-internal/test/integration/start.test.ts new file mode 100644 index 000000000000..dff5df38b53d --- /dev/null +++ b/packages/replay-internal/test/integration/start.test.ts @@ -0,0 +1,52 @@ +import { vi } from 'vitest'; + +import { getClient } from '@sentry/core'; +import type { Transport } from '@sentry/types'; + +import { DEFAULT_FLUSH_MIN_DELAY, SESSION_IDLE_EXPIRE_DURATION } from '../../src/constants'; +import type { Replay } from '../../src/integration'; +import type { ReplayContainer } from '../../src/replay'; +import { BASE_TIMESTAMP } from '../index'; +import { resetSdkMock } from '../mocks/resetSdkMock'; +import { useFakeTimers } from '../utils/use-fake-timers'; + +useFakeTimers(); + +describe('Integration | start', () => { + let replay: ReplayContainer; + let integration: Replay; + + beforeEach(async () => { + ({ replay, integration } = await resetSdkMock({ + replayOptions: { + stickySession: false, + }, + sentryOptions: { + replaysSessionSampleRate: 0.0, + }, + })); + + const mockTransport = getClient()?.getTransport()?.send as vi.MockedFunction; + mockTransport?.mockClear(); + await vi.runAllTimersAsync(); + }); + + afterEach(async () => { + integration.stop(); + + await vi.runAllTimersAsync(); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); + }); + + it('sends replay when calling `start()` after [SESSION_IDLE_EXPIRE_DURATION]ms', async () => { + await vi.advanceTimersByTimeAsync(SESSION_IDLE_EXPIRE_DURATION + 1); + + integration.start(); + + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); + + expect(replay).toHaveLastSentReplay({ + recordingPayloadHeader: { segment_id: 0 }, + }); + }); +});