Skip to content

Commit 945c19e

Browse files
authored
fix(replay): Fix user activity not being updated in start() (#12001)
Replays will fail to start recording when using `start()` specifically when manually recording and after the user has been idle for a long period of time. We need to reset the user activity state when we call `start()`, otherwise the session will be [incorrectly] considered to be idle and unable to send any replay events. Closes #11983
1 parent 9510771 commit 945c19e

File tree

2 files changed

+59
-0
lines changed

2 files changed

+59
-0
lines changed

packages/replay-internal/src/replay.ts

+7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { clearSession } from './session/clearSession';
2828
import { loadOrCreateSession } from './session/loadOrCreateSession';
2929
import { saveSession } from './session/saveSession';
3030
import { shouldRefreshSession } from './session/shouldRefreshSession';
31+
3132
import type {
3233
AddEventResult,
3334
AddUpdateCallback,
@@ -295,6 +296,12 @@ export class ReplayContainer implements ReplayContainerInterface {
295296

296297
logInfoNextTick('[Replay] Starting replay in session mode', this._options._experiments.traceInternals);
297298

299+
// Required as user activity is initially set in
300+
// constructor, so if `start()` is called after
301+
// session idle expiration, a replay will not be
302+
// created due to an idle timeout.
303+
this._updateUserActivity();
304+
298305
const session = loadOrCreateSession(
299306
{
300307
maxReplayDuration: this._options.maxReplayDuration,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { vi } from 'vitest';
2+
3+
import { getClient } from '@sentry/core';
4+
import type { Transport } from '@sentry/types';
5+
6+
import { DEFAULT_FLUSH_MIN_DELAY, SESSION_IDLE_EXPIRE_DURATION } from '../../src/constants';
7+
import type { Replay } from '../../src/integration';
8+
import type { ReplayContainer } from '../../src/replay';
9+
import { BASE_TIMESTAMP } from '../index';
10+
import { resetSdkMock } from '../mocks/resetSdkMock';
11+
import { useFakeTimers } from '../utils/use-fake-timers';
12+
13+
useFakeTimers();
14+
15+
describe('Integration | start', () => {
16+
let replay: ReplayContainer;
17+
let integration: Replay;
18+
19+
beforeEach(async () => {
20+
({ replay, integration } = await resetSdkMock({
21+
replayOptions: {
22+
stickySession: false,
23+
},
24+
sentryOptions: {
25+
replaysSessionSampleRate: 0.0,
26+
},
27+
}));
28+
29+
const mockTransport = getClient()?.getTransport()?.send as vi.MockedFunction<Transport['send']>;
30+
mockTransport?.mockClear();
31+
await vi.runAllTimersAsync();
32+
});
33+
34+
afterEach(async () => {
35+
integration.stop();
36+
37+
await vi.runAllTimersAsync();
38+
vi.setSystemTime(new Date(BASE_TIMESTAMP));
39+
});
40+
41+
it('sends replay when calling `start()` after [SESSION_IDLE_EXPIRE_DURATION]ms', async () => {
42+
await vi.advanceTimersByTimeAsync(SESSION_IDLE_EXPIRE_DURATION + 1);
43+
44+
integration.start();
45+
46+
await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY);
47+
48+
expect(replay).toHaveLastSentReplay({
49+
recordingPayloadHeader: { segment_id: 0 },
50+
});
51+
});
52+
});

0 commit comments

Comments
 (0)