From ecdb40c989363f4476b43730fae051bb275f80e2 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 25 Apr 2023 15:12:18 +0200 Subject: [PATCH] feat(replay): Extend session idle time until expire to 15min Now, a session will only expire & trigger a new session if no user activity happened for 15min. After 5min of inactivity, we will pause recording events. If the user resumes in the next 10 minutes, we'll resume the session, else re-create it if they resume later. --- .../suites/replay/sessionExpiry/init.js | 3 +- .../suites/replay/sessionInactive/init.js | 5 +- .../suites/replay/sessionInactive/test.ts | 17 ++- .../suites/replay/sessionMaxAge/init.js | 3 +- packages/replay/src/constants.ts | 9 +- packages/replay/src/replay.ts | 17 ++- packages/replay/src/types.ts | 8 +- packages/replay/src/util/addEvent.ts | 2 +- packages/replay/src/util/isSessionExpired.ts | 4 +- .../test/integration/errorSampleRate.test.ts | 18 ++-- .../replay/test/integration/session.test.ts | 101 ++++++++++++++++-- .../test/unit/session/getSession.test.ts | 28 +++-- .../test/unit/util/isSessionExpired.test.ts | 40 ++++++- 13 files changed, 193 insertions(+), 62 deletions(-) diff --git a/packages/browser-integration-tests/suites/replay/sessionExpiry/init.js b/packages/browser-integration-tests/suites/replay/sessionExpiry/init.js index 3e685021e1fe..46af904118a6 100644 --- a/packages/browser-integration-tests/suites/replay/sessionExpiry/init.js +++ b/packages/browser-integration-tests/suites/replay/sessionExpiry/init.js @@ -17,6 +17,7 @@ Sentry.init({ }); window.Replay._replay.timeouts = { - sessionIdle: 2000, // this is usually 5min, but we want to test this with shorter times + sessionIdlePause: 1000, // this is usually 5min, but we want to test this with shorter times + sessionIdleExpire: 2000, // this is usually 15min, but we want to test this with shorter times maxSessionLife: 3600000, // default: 60min }; diff --git a/packages/browser-integration-tests/suites/replay/sessionInactive/init.js b/packages/browser-integration-tests/suites/replay/sessionInactive/init.js index a3da64ec3bae..4c641d160d79 100644 --- a/packages/browser-integration-tests/suites/replay/sessionInactive/init.js +++ b/packages/browser-integration-tests/suites/replay/sessionInactive/init.js @@ -17,6 +17,7 @@ Sentry.init({ }); window.Replay._replay.timeouts = { - sessionIdle: 1000, // default: 5min - maxSessionLife: 2000, // this is usually 60min, but we want to test this with shorter times + sessionIdlePause: 1000, // this is usually 5min, but we want to test this with shorter times + sessionIdleExpire: 900000, // defayult: 15min + maxSessionLife: 3600000, // default: 60min }; diff --git a/packages/browser-integration-tests/suites/replay/sessionInactive/test.ts b/packages/browser-integration-tests/suites/replay/sessionInactive/test.ts index ed53f155feea..ef2b841cbea3 100644 --- a/packages/browser-integration-tests/suites/replay/sessionInactive/test.ts +++ b/packages/browser-integration-tests/suites/replay/sessionInactive/test.ts @@ -11,8 +11,8 @@ import { waitForReplayRequest, } from '../../../utils/replayHelpers'; -// Session should expire after 2s - keep in sync with init.js -const SESSION_TIMEOUT = 2000; +// Session should be paused after 2s - keep in sync with init.js +const SESSION_PAUSED = 2000; sentryTest('handles an inactive session', async ({ getLocalTestPath, page }) => { if (shouldSkipReplayTest()) { @@ -44,11 +44,8 @@ sentryTest('handles an inactive session', async ({ getLocalTestPath, page }) => await page.click('#button1'); - // We wait for another segment 0 - const reqPromise1 = waitForReplayRequest(page, 0); - // Now we wait for the session timeout, nothing should be sent in the meanwhile - await new Promise(resolve => setTimeout(resolve, SESSION_TIMEOUT)); + await new Promise(resolve => setTimeout(resolve, SESSION_PAUSED)); // nothing happened because no activity/inactivity was detected const replay = await getReplaySnapshot(page); @@ -64,7 +61,10 @@ sentryTest('handles an inactive session', async ({ getLocalTestPath, page }) => expect(replay2._isEnabled).toEqual(true); expect(replay2._isPaused).toEqual(true); - // Trigger an action, should re-start the recording + // We wait for next segment to be sent once we resume the session + const reqPromise1 = waitForReplayRequest(page); + + // Trigger an action, should resume the recording await page.click('#button2'); const req1 = await reqPromise1; @@ -72,9 +72,6 @@ sentryTest('handles an inactive session', async ({ getLocalTestPath, page }) => expect(replay3._isEnabled).toEqual(true); expect(replay3._isPaused).toEqual(false); - const replayEvent1 = getReplayEvent(req1); - expect(replayEvent1).toEqual(getExpectedReplayEvent({})); - const fullSnapshots1 = getFullRecordingSnapshots(req1); expect(fullSnapshots1.length).toEqual(1); const stringifiedSnapshot1 = normalize(fullSnapshots1[0]); diff --git a/packages/browser-integration-tests/suites/replay/sessionMaxAge/init.js b/packages/browser-integration-tests/suites/replay/sessionMaxAge/init.js index cf98205a5576..0c16dc6ca3a1 100644 --- a/packages/browser-integration-tests/suites/replay/sessionMaxAge/init.js +++ b/packages/browser-integration-tests/suites/replay/sessionMaxAge/init.js @@ -17,6 +17,7 @@ Sentry.init({ }); window.Replay._replay.timeouts = { - sessionIdle: 300000, // default: 5min + sessionIdlePause: 300000, // default: 5min + sessionIdleExpire: 900000, // default: 15min maxSessionLife: 4000, // this is usually 60min, but we want to test this with shorter times }; diff --git a/packages/replay/src/constants.ts b/packages/replay/src/constants.ts index e7b5fe2f1b52..f1f8627c120c 100644 --- a/packages/replay/src/constants.ts +++ b/packages/replay/src/constants.ts @@ -11,11 +11,14 @@ export const REPLAY_EVENT_NAME = 'replay_event'; export const RECORDING_EVENT_NAME = 'replay_recording'; export const UNABLE_TO_SEND_REPLAY = 'Unable to send Replay'; -// The idle limit for a session -export const SESSION_IDLE_DURATION = 300_000; // 5 minutes in ms +// The idle limit for a session after which recording is paused. +export const SESSION_IDLE_PAUSE_DURATION = 300_000; // 5 minutes in ms + +// The idle limit for a session after which the session expires. +export const SESSION_IDLE_EXPIRE_DURATION = 900_000; // 15 minutes in ms // The maximum length of a session -export const MAX_SESSION_LIFE = 3_600_000; // 60 minutes +export const MAX_SESSION_LIFE = 3_600_000; // 60 minutes in ms /** Default flush delays */ export const DEFAULT_FLUSH_MIN_DELAY = 5_000; diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index 990ca825266e..ff0124018bfb 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -4,7 +4,13 @@ import { captureException, getCurrentHub } from '@sentry/core'; import type { Breadcrumb, ReplayRecordingMode } from '@sentry/types'; import { logger } from '@sentry/utils'; -import { ERROR_CHECKOUT_TIME, MAX_SESSION_LIFE, SESSION_IDLE_DURATION, WINDOW } from './constants'; +import { + ERROR_CHECKOUT_TIME, + MAX_SESSION_LIFE, + SESSION_IDLE_EXPIRE_DURATION, + SESSION_IDLE_PAUSE_DURATION, + WINDOW, +} from './constants'; import { setupPerformanceObserver } from './coreHandlers/performanceObserver'; import { createEventBuffer } from './eventBuffer'; import { getSession } from './session/getSession'; @@ -61,7 +67,8 @@ export class ReplayContainer implements ReplayContainerInterface { * @hidden */ public readonly timeouts: Timeouts = { - sessionIdle: SESSION_IDLE_DURATION, + sessionIdlePause: SESSION_IDLE_PAUSE_DURATION, + sessionIdleExpire: SESSION_IDLE_EXPIRE_DURATION, maxSessionLife: MAX_SESSION_LIFE, } as const; @@ -423,12 +430,12 @@ export class ReplayContainer implements ReplayContainerInterface { const oldSessionId = this.getSessionId(); // Prevent starting a new session if the last user activity is older than - // SESSION_IDLE_DURATION. Otherwise non-user activity can trigger a new + // SESSION_IDLE_PAUSE_DURATION. Otherwise non-user activity can trigger a new // session+recording. This creates noisy replays that do not have much // content in them. if ( this._lastActivity && - isExpired(this._lastActivity, this.timeouts.sessionIdle) && + isExpired(this._lastActivity, this.timeouts.sessionIdlePause) && this.session && this.session.sampled === 'session' ) { @@ -638,7 +645,7 @@ export class ReplayContainer implements ReplayContainerInterface { const isSessionActive = this.checkAndHandleExpiredSession(); if (!isSessionActive) { - // If the user has come back to the page within SESSION_IDLE_DURATION + // If the user has come back to the page within SESSION_IDLE_PAUSE_DURATION // ms, we will re-use the existing session, otherwise create a new // session __DEBUG_BUILD__ && logger.log('[Replay] Document has become active, but session has expired'); diff --git a/packages/replay/src/types.ts b/packages/replay/src/types.ts index 684e5442ed9c..65dbdc072115 100644 --- a/packages/replay/src/types.ts +++ b/packages/replay/src/types.ts @@ -25,7 +25,8 @@ export interface SendReplayData { } export interface Timeouts { - sessionIdle: number; + sessionIdlePause: number; + sessionIdleExpire: number; maxSessionLife: number; } @@ -455,10 +456,7 @@ export interface ReplayContainer { performanceEvents: AllPerformanceEntry[]; session: Session | undefined; recordingMode: ReplayRecordingMode; - timeouts: { - sessionIdle: number; - maxSessionLife: number; - }; + timeouts: Timeouts; isEnabled(): boolean; isPaused(): boolean; getContext(): InternalEventContext; diff --git a/packages/replay/src/util/addEvent.ts b/packages/replay/src/util/addEvent.ts index b32050665519..4dd2055bb0e7 100644 --- a/packages/replay/src/util/addEvent.ts +++ b/packages/replay/src/util/addEvent.ts @@ -31,7 +31,7 @@ export async function addEvent( // page has been left open and idle for a long period of time and user // comes back to trigger a new session. The performance entries rely on // `performance.timeOrigin`, which is when the page first opened. - if (timestampInMs + replay.timeouts.sessionIdle < Date.now()) { + if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) { return null; } diff --git a/packages/replay/src/util/isSessionExpired.ts b/packages/replay/src/util/isSessionExpired.ts index b7025a19cbf6..a51104fd5a47 100644 --- a/packages/replay/src/util/isSessionExpired.ts +++ b/packages/replay/src/util/isSessionExpired.ts @@ -9,7 +9,7 @@ export function isSessionExpired(session: Session, timeouts: Timeouts, targetTim // First, check that maximum session length has not been exceeded isExpired(session.started, timeouts.maxSessionLife, targetTime) || // check that the idle timeout has not been exceeded (i.e. user has - // performed an action within the last `idleTimeout` ms) - isExpired(session.lastActivity, timeouts.sessionIdle, targetTime) + // performed an action within the last `sessionIdleExpire` ms) + isExpired(session.lastActivity, timeouts.sessionIdleExpire, targetTime) ); } diff --git a/packages/replay/test/integration/errorSampleRate.test.ts b/packages/replay/test/integration/errorSampleRate.test.ts index 0b5baa8e2512..03cb35ae0d75 100644 --- a/packages/replay/test/integration/errorSampleRate.test.ts +++ b/packages/replay/test/integration/errorSampleRate.test.ts @@ -5,7 +5,7 @@ import { ERROR_CHECKOUT_TIME, MAX_SESSION_LIFE, REPLAY_SESSION_KEY, - SESSION_IDLE_DURATION, + SESSION_IDLE_EXPIRE_DURATION, WINDOW, } from '../../src/constants'; import type { ReplayContainer } from '../../src/replay'; @@ -252,7 +252,7 @@ describe('Integration | errorSampleRate', () => { }); }); - it('does not send a replay when triggering a full dom snapshot when document becomes visible after [SESSION_IDLE_DURATION]ms', async () => { + it('does not send a replay when triggering a full dom snapshot when document becomes visible after [SESSION_IDLE_EXPIRE_DURATION]ms', async () => { Object.defineProperty(document, 'visibilityState', { configurable: true, get: function () { @@ -260,7 +260,7 @@ describe('Integration | errorSampleRate', () => { }, }); - jest.advanceTimersByTime(SESSION_IDLE_DURATION + 1); + jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); document.dispatchEvent(new Event('visibilitychange')); @@ -284,8 +284,8 @@ describe('Integration | errorSampleRate', () => { expect(replay).not.toHaveLastSentReplay(); - // User comes back before `SESSION_IDLE_DURATION` elapses - jest.advanceTimersByTime(SESSION_IDLE_DURATION - 100); + // User comes back before `SESSION_IDLE_EXPIRE_DURATION` elapses + jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION - 100); Object.defineProperty(document, 'visibilityState', { configurable: true, get: function () { @@ -403,9 +403,9 @@ describe('Integration | errorSampleRate', () => { }); // Should behave the same as above test - it('stops replay if user has been idle for more than SESSION_IDLE_DURATION and does not start a new session thereafter', async () => { + it('stops replay if user has been idle for more than SESSION_IDLE_EXPIRE_DURATION and does not start a new session thereafter', async () => { // Idle for 15 minutes - jest.advanceTimersByTime(SESSION_IDLE_DURATION + 1); + jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); const TEST_EVENT = { data: { name: 'lost event' }, @@ -418,7 +418,7 @@ describe('Integration | errorSampleRate', () => { jest.runAllTimers(); await new Promise(process.nextTick); - // We stop recording after SESSION_IDLE_DURATION of inactivity in error mode + // We stop recording after SESSION_IDLE_EXPIRE_DURATION of inactivity in error mode expect(replay).not.toHaveLastSentReplay(); expect(replay.isEnabled()).toBe(false); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); @@ -544,7 +544,7 @@ describe('Integration | errorSampleRate', () => { expect(replay).not.toHaveLastSentReplay(); // Go idle - jest.advanceTimersByTime(SESSION_IDLE_DURATION + 1); + jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); await new Promise(process.nextTick); mockRecord._emitter(TEST_EVENT); diff --git a/packages/replay/test/integration/session.test.ts b/packages/replay/test/integration/session.test.ts index 4720e7b65bc6..b88aa6bd67cc 100644 --- a/packages/replay/test/integration/session.test.ts +++ b/packages/replay/test/integration/session.test.ts @@ -5,7 +5,8 @@ import { DEFAULT_FLUSH_MIN_DELAY, MAX_SESSION_LIFE, REPLAY_SESSION_KEY, - SESSION_IDLE_DURATION, + SESSION_IDLE_EXPIRE_DURATION, + SESSION_IDLE_PAUSE_DURATION, WINDOW, } from '../../src/constants'; import type { ReplayContainer } from '../../src/replay'; @@ -58,7 +59,7 @@ describe('Integration | session', () => { // Require a "user interaction" to start a new session, visibility is not enough. This can be noisy // (e.g. rapidly switching tabs/window focus) and leads to empty sessions. - it('does not create a new session when document becomes visible after [SESSION_IDLE_DURATION]ms', () => { + it('does not create a new session when document becomes visible after [SESSION_IDLE_EXPIRE_DURATION]ms', () => { Object.defineProperty(document, 'visibilityState', { configurable: true, get: function () { @@ -68,7 +69,7 @@ describe('Integration | session', () => { const initialSession = { ...replay.session } as Session; - jest.advanceTimersByTime(SESSION_IDLE_DURATION + 1); + jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); document.dispatchEvent(new Event('visibilitychange')); @@ -76,10 +77,10 @@ describe('Integration | session', () => { expect(replay).toHaveSameSession(initialSession); }); - it('does not create a new session when document becomes focused after [SESSION_IDLE_DURATION]ms', () => { + it('does not create a new session when document becomes focused after [SESSION_IDLE_EXPIRE_DURATION]ms', () => { const initialSession = { ...replay.session } as Session; - jest.advanceTimersByTime(SESSION_IDLE_DURATION + 1); + jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); WINDOW.dispatchEvent(new Event('focus')); @@ -87,7 +88,7 @@ describe('Integration | session', () => { expect(replay).toHaveSameSession(initialSession); }); - it('does not create a new session if user hides the tab and comes back within [SESSION_IDLE_DURATION] seconds', () => { + it('does not create a new session if user hides the tab and comes back within [SESSION_IDLE_EXPIRE_DURATION] seconds', () => { const initialSession = { ...replay.session } as Session; Object.defineProperty(document, 'visibilityState', { @@ -100,8 +101,8 @@ describe('Integration | session', () => { expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); expect(replay).toHaveSameSession(initialSession); - // User comes back before `SESSION_IDLE_DURATION` elapses - jest.advanceTimersByTime(SESSION_IDLE_DURATION - 1); + // User comes back before `SESSION_IDLE_EXPIRE_DURATION` elapses + jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION - 1); Object.defineProperty(document, 'visibilityState', { configurable: true, get: function () { @@ -115,7 +116,7 @@ describe('Integration | session', () => { expect(replay).toHaveSameSession(initialSession); }); - it('creates a new session if user has been idle for more than SESSION_IDLE_DURATION and comes back to click their mouse', async () => { + it('creates a new session if user has been idle for more than SESSION_IDLE_EXPIRE_DURATION and comes back to click their mouse', async () => { const initialSession = { ...replay.session } as Session; expect(initialSession?.id).toBeDefined(); @@ -131,7 +132,7 @@ describe('Integration | session', () => { value: new URL(url), }); - const ELAPSED = SESSION_IDLE_DURATION + 1; + const ELAPSED = SESSION_IDLE_EXPIRE_DURATION + 1; jest.advanceTimersByTime(ELAPSED); // Session has become in an idle state @@ -230,6 +231,86 @@ describe('Integration | session', () => { }); }); + it('pauses and resumes a session if user has been idle for more than SESSION_IDLE_PASUE_DURATION and comes back to click their mouse', async () => { + const initialSession = { ...replay.session } as Session; + + expect(initialSession?.id).toBeDefined(); + expect(replay.getContext()).toEqual( + expect.objectContaining({ + initialUrl: 'http://localhost/', + initialTimestamp: BASE_TIMESTAMP, + }), + ); + + const url = 'http://dummy/'; + Object.defineProperty(WINDOW, 'location', { + value: new URL(url), + }); + + const ELAPSED = SESSION_IDLE_PAUSE_DURATION + 1; + jest.advanceTimersByTime(ELAPSED); + + // Session has become in an idle state + // + // This event will put the Replay SDK into a paused state + const TEST_EVENT = { + data: { name: 'lost event' }, + timestamp: BASE_TIMESTAMP, + type: 3, + }; + mockRecord._emitter(TEST_EVENT); + + // performance events can still be collected while recording is stopped + // TODO: we may want to prevent `addEvent` from adding to buffer when user is inactive + replay.addUpdate(() => { + createPerformanceSpans(replay, [ + { + type: 'navigation.navigate' as const, + name: 'foo', + start: BASE_TIMESTAMP + ELAPSED, + end: BASE_TIMESTAMP + ELAPSED + 100, + data: { + decodedBodySize: 1, + encodedBodySize: 2, + duration: 0, + domInteractive: 0, + domContentLoadedEventEnd: 0, + domContentLoadedEventStart: 0, + loadEventStart: 0, + loadEventEnd: 0, + domComplete: 0, + redirectCount: 0, + size: 0, + }, + }, + ]); + return true; + }); + + await new Promise(process.nextTick); + + expect(replay).not.toHaveLastSentReplay(); + expect(replay.isPaused()).toBe(true); + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + expect(replay).toHaveSameSession(initialSession); + expect(mockRecord).toHaveBeenCalledTimes(1); + + // Now do a click which will create a new session and start recording again + domHandler({ + name: 'click', + }); + + // Should be same session + expect(replay).toHaveSameSession(initialSession); + + // Replay does not send immediately + expect(replay).not.toHaveLastSentReplay(); + + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + + expect(replay).toHaveLastSentReplay(); + }); + it('should have a session after setup', () => { expect(replay.session).toMatchObject({ lastActivity: BASE_TIMESTAMP, diff --git a/packages/replay/test/unit/session/getSession.test.ts b/packages/replay/test/unit/session/getSession.test.ts index 2e7d3ec969b7..be01f0602e51 100644 --- a/packages/replay/test/unit/session/getSession.test.ts +++ b/packages/replay/test/unit/session/getSession.test.ts @@ -1,4 +1,9 @@ -import { MAX_SESSION_LIFE, SESSION_IDLE_DURATION, WINDOW } from '../../../src/constants'; +import { + MAX_SESSION_LIFE, + SESSION_IDLE_EXPIRE_DURATION, + SESSION_IDLE_PAUSE_DURATION, + WINDOW, +} from '../../../src/constants'; import * as CreateSession from '../../../src/session/createSession'; import * as FetchSession from '../../../src/session/fetchSession'; import { getSession } from '../../../src/session/getSession'; @@ -43,7 +48,8 @@ describe('Unit | session | getSession', () => { it('creates a non-sticky session when one does not exist', function () { const { session } = getSession({ timeouts: { - sessionIdle: SESSION_IDLE_DURATION, + sessionIdlePause: SESSION_IDLE_PAUSE_DURATION, + sessionIdleExpire: SESSION_IDLE_EXPIRE_DURATION, maxSessionLife: MAX_SESSION_LIFE, }, stickySession: false, @@ -70,7 +76,8 @@ describe('Unit | session | getSession', () => { const { session } = getSession({ timeouts: { - sessionIdle: 1000, + sessionIdlePause: SESSION_IDLE_PAUSE_DURATION, + sessionIdleExpire: 1000, maxSessionLife: MAX_SESSION_LIFE, }, stickySession: false, @@ -86,7 +93,8 @@ describe('Unit | session | getSession', () => { it('creates a non-sticky session, when one is expired', function () { const { session } = getSession({ timeouts: { - sessionIdle: 1000, + sessionIdlePause: SESSION_IDLE_PAUSE_DURATION, + sessionIdleExpire: 1000, maxSessionLife: MAX_SESSION_LIFE, }, stickySession: false, @@ -112,7 +120,8 @@ describe('Unit | session | getSession', () => { const { session } = getSession({ timeouts: { - sessionIdle: SESSION_IDLE_DURATION, + sessionIdlePause: SESSION_IDLE_PAUSE_DURATION, + sessionIdleExpire: SESSION_IDLE_EXPIRE_DURATION, maxSessionLife: MAX_SESSION_LIFE, }, stickySession: true, @@ -147,7 +156,8 @@ describe('Unit | session | getSession', () => { const { session } = getSession({ timeouts: { - sessionIdle: 1000, + sessionIdlePause: SESSION_IDLE_PAUSE_DURATION, + sessionIdleExpire: 1000, maxSessionLife: MAX_SESSION_LIFE, }, stickySession: true, @@ -173,7 +183,8 @@ describe('Unit | session | getSession', () => { const { session } = getSession({ timeouts: { - sessionIdle: 1000, + sessionIdlePause: SESSION_IDLE_PAUSE_DURATION, + sessionIdleExpire: 1000, maxSessionLife: MAX_SESSION_LIFE, }, stickySession: true, @@ -192,7 +203,8 @@ describe('Unit | session | getSession', () => { it('fetches a non-expired non-sticky session', function () { const { session } = getSession({ timeouts: { - sessionIdle: 1000, + sessionIdlePause: SESSION_IDLE_PAUSE_DURATION, + sessionIdleExpire: 1000, maxSessionLife: MAX_SESSION_LIFE, }, stickySession: false, diff --git a/packages/replay/test/unit/util/isSessionExpired.test.ts b/packages/replay/test/unit/util/isSessionExpired.test.ts index 627105d322f0..38b24056d36f 100644 --- a/packages/replay/test/unit/util/isSessionExpired.test.ts +++ b/packages/replay/test/unit/util/isSessionExpired.test.ts @@ -1,4 +1,4 @@ -import { MAX_SESSION_LIFE } from '../../../src/constants'; +import { MAX_SESSION_LIFE, SESSION_IDLE_PAUSE_DURATION } from '../../../src/constants'; import { makeSession } from '../../../src/session/Session'; import { isSessionExpired } from '../../../src/util/isSessionExpired'; @@ -15,14 +15,28 @@ function createSession(extra?: Record) { describe('Unit | util | isSessionExpired', () => { it('session last activity is older than expiry time', function () { - expect(isSessionExpired(createSession(), { maxSessionLife: MAX_SESSION_LIFE, sessionIdle: 100 }, 200)).toBe(true); // Session expired at ts = 100 + expect( + isSessionExpired( + createSession(), + { + maxSessionLife: MAX_SESSION_LIFE, + sessionIdlePause: SESSION_IDLE_PAUSE_DURATION, + sessionIdleExpire: 100, + }, + 200, + ), + ).toBe(true); // Session expired at ts = 100 }); it('session last activity is not older than expiry time', function () { expect( isSessionExpired( createSession({ lastActivity: 100 }), - { maxSessionLife: MAX_SESSION_LIFE, sessionIdle: 150 }, + { + maxSessionLife: MAX_SESSION_LIFE, + sessionIdlePause: SESSION_IDLE_PAUSE_DURATION, + sessionIdleExpire: 150, + }, 200, ), ).toBe(false); // Session expires at ts >= 250 @@ -30,13 +44,29 @@ describe('Unit | util | isSessionExpired', () => { it('session age is not older than max session life', function () { expect( - isSessionExpired(createSession(), { maxSessionLife: MAX_SESSION_LIFE, sessionIdle: 1_800_000 }, 50_000), + isSessionExpired( + createSession(), + { + maxSessionLife: MAX_SESSION_LIFE, + sessionIdlePause: SESSION_IDLE_PAUSE_DURATION, + sessionIdleExpire: 1_800_000, + }, + 50_000, + ), ).toBe(false); }); it('session age is older than max session life', function () { expect( - isSessionExpired(createSession(), { maxSessionLife: MAX_SESSION_LIFE, sessionIdle: 1_800_000 }, 1_800_001), + isSessionExpired( + createSession(), + { + maxSessionLife: MAX_SESSION_LIFE, + sessionIdlePause: SESSION_IDLE_PAUSE_DURATION, + sessionIdleExpire: 1_800_000, + }, + 1_800_001, + ), ).toBe(true); // Session expires at ts >= 1_800_000 }); });