Skip to content

Commit 1f70328

Browse files
committed
fix
1 parent c0c6634 commit 1f70328

File tree

6 files changed

+155
-38
lines changed

6 files changed

+155
-38
lines changed

src/__tests__/extensions/replay/sessionrecording.test.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,11 @@ describe('SessionRecording', () => {
235235
const postHogPersistence = new PostHogPersistence(config)
236236
postHogPersistence.clear()
237237

238-
sessionManager = new SessionIdManager(config, postHogPersistence, sessionIdGeneratorMock, windowIdGeneratorMock)
238+
sessionManager = new SessionIdManager(
239+
{ config, persistence: postHogPersistence, register: jest.fn() } as unknown as PostHog,
240+
sessionIdGeneratorMock,
241+
windowIdGeneratorMock
242+
)
239243

240244
// add capture hook returns an unsubscribe function
241245
removeCaptureHookMock = jest.fn()
@@ -1130,7 +1134,11 @@ describe('SessionRecording', () => {
11301134
let unsubscribeCallback: () => void
11311135

11321136
beforeEach(() => {
1133-
sessionManager = new SessionIdManager(config, new PostHogPersistence(config))
1137+
sessionManager = new SessionIdManager({
1138+
config,
1139+
persistence: new PostHogPersistence(config),
1140+
register: jest.fn(),
1141+
} as unknown as PostHog)
11341142
posthog.sessionManager = sessionManager
11351143

11361144
mockCallback = jest.fn()
@@ -1216,7 +1224,11 @@ describe('SessionRecording', () => {
12161224

12171225
describe('with a real session id manager', () => {
12181226
beforeEach(() => {
1219-
sessionManager = new SessionIdManager(config, new PostHogPersistence(config))
1227+
sessionManager = new SessionIdManager({
1228+
config,
1229+
persistence: new PostHogPersistence(config),
1230+
register: jest.fn(),
1231+
} as unknown as PostHog)
12201232
posthog.sessionManager = sessionManager
12211233

12221234
sessionRecording.startIfEnabledOrStop()

src/__tests__/sessionid.test.ts

+37-10
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { SessionIdManager } from '../sessionid'
1+
import { DEFAULT_SESSION_IDLE_TIMEOUT_SECONDS, MAX_SESSION_IDLE_TIMEOUT_SECONDS, SessionIdManager } from '../sessionid'
22
import { SESSION_ID } from '../constants'
33
import { sessionStore } from '../storage'
44
import { uuid7ToTimestampMs, uuidv7 } from '../uuidv7'
55
import { BootstrapConfig, PostHogConfig, Properties } from '../types'
66
import { PostHogPersistence } from '../posthog-persistence'
77
import { assignableWindow } from '../utils/globals'
8+
import { PostHog } from '../posthog-core'
89

910
jest.mock('../uuidv7')
1011
jest.mock('../storage')
@@ -13,14 +14,22 @@ describe('Session ID manager', () => {
1314
let timestamp: number | undefined
1415
let now: number
1516
let timestampOfSessionStart: number
17+
let registerMock: jest.Mock
18+
1619
const config: Partial<PostHogConfig> = {
1720
persistence_name: 'persistance-name',
1821
}
1922

2023
let persistence: { props: Properties } & Partial<PostHogPersistence>
2124

22-
const sessionIdMgr = (phPersistence: Partial<PostHogPersistence>) =>
23-
new SessionIdManager(config, phPersistence as PostHogPersistence)
25+
const sessionIdMgr = (phPersistence: Partial<PostHogPersistence>) => {
26+
registerMock = jest.fn()
27+
return new SessionIdManager({
28+
config,
29+
persistence: phPersistence as PostHogPersistence,
30+
register: registerMock,
31+
} as unknown as PostHog)
32+
}
2433

2534
const originalDate = Date
2635

@@ -70,7 +79,11 @@ describe('Session ID manager', () => {
7079
const bootstrap: BootstrapConfig = {
7180
sessionID: bootstrapSessionId,
7281
}
73-
const sessionIdManager = new SessionIdManager({ ...config, bootstrap }, persistence as PostHogPersistence)
82+
const sessionIdManager = new SessionIdManager({
83+
config: { ...config, bootstrap },
84+
persistence: persistence as PostHogPersistence,
85+
register: jest.fn(),
86+
} as unknown as PostHog)
7487

7588
// act
7689
const { sessionId, sessionStartTimestamp } = sessionIdManager.checkAndGetSessionAndWindowId(false, now)
@@ -79,6 +92,15 @@ describe('Session ID manager', () => {
7992
expect(sessionId).toEqual(bootstrapSessionId)
8093
expect(sessionStartTimestamp).toEqual(timestamp)
8194
})
95+
96+
it('registers the session timeout as an event property', () => {
97+
config.session_idle_timeout_seconds = 8 * 60 * 60
98+
const sessionIdManager = sessionIdMgr(persistence)
99+
sessionIdManager.checkAndGetSessionAndWindowId(undefined, timestamp)
100+
expect(registerMock).toHaveBeenCalledWith({
101+
$configured_session_timeout_ms: config.session_idle_timeout_seconds * 1000,
102+
})
103+
})
82104
})
83105

84106
describe('stored session data', () => {
@@ -317,12 +339,13 @@ describe('Session ID manager', () => {
317339

318340
describe('custom session_idle_timeout_seconds', () => {
319341
const mockSessionManager = (timeout: number | undefined) =>
320-
new SessionIdManager(
321-
{
342+
new SessionIdManager({
343+
config: {
322344
session_idle_timeout_seconds: timeout,
323345
},
324-
persistence as PostHogPersistence
325-
)
346+
persistence: persistence as PostHogPersistence,
347+
register: jest.fn(),
348+
} as unknown as PostHog)
326349

327350
beforeEach(() => {
328351
console.warn = jest.fn()
@@ -336,10 +359,14 @@ describe('Session ID manager', () => {
336359
expect(console.warn).toBeCalledTimes(1)
337360
expect(mockSessionManager(30 * 60 - 1)['_sessionTimeoutMs']).toEqual((30 * 60 - 1) * 1000)
338361
expect(console.warn).toBeCalledTimes(1)
339-
expect(mockSessionManager(30 * 60 + 1)['_sessionTimeoutMs']).toEqual(30 * 60 * 1000)
362+
expect(mockSessionManager(MAX_SESSION_IDLE_TIMEOUT_SECONDS + 1)['_sessionTimeoutMs']).toEqual(
363+
MAX_SESSION_IDLE_TIMEOUT_SECONDS * 1000
364+
)
340365
expect(console.warn).toBeCalledTimes(2)
341366
// @ts-expect-error - test invalid input
342-
expect(mockSessionManager('foobar')['_sessionTimeoutMs']).toEqual(30 * 60 * 1000)
367+
expect(mockSessionManager('foobar')['_sessionTimeoutMs']).toEqual(
368+
DEFAULT_SESSION_IDLE_TIMEOUT_SECONDS * 1000
369+
)
343370
expect(console.warn).toBeCalledTimes(3)
344371
})
345372
})

src/__tests__/utils/number-utils.test.ts

+72-8
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,78 @@ jest.mock('../../utils/logger', () => ({
1010
describe('number-utils', () => {
1111
describe('clampToRange', () => {
1212
it.each([
13-
// [value, result, min, max, expected result, test description]
14-
['returns max when value is not a number', null, 10, 100, 100],
15-
['returns max when value is not a number', 'not-a-number', 10, 100, 100],
16-
['returns max when value is greater than max', 150, 10, 100, 100],
17-
['returns min when value is less than min', 5, 10, 100, 10],
18-
['returns the value when it is within the range', 50, 10, 100, 50],
19-
])('%s', (_description, value, min, max, expected) => {
20-
const result = clampToRange(value, min, max, 'Test Label')
13+
[
14+
'returns max when value is not a number',
15+
{
16+
value: null,
17+
min: 10,
18+
max: 100,
19+
expected: 100,
20+
fallback: undefined,
21+
},
22+
],
23+
[
24+
'returns max when value is not a number',
25+
{
26+
value: 'not-a-number',
27+
min: 10,
28+
max: 100,
29+
expected: 100,
30+
fallback: undefined,
31+
},
32+
],
33+
[
34+
'returns max when value is greater than max',
35+
{
36+
value: 150,
37+
min: 10,
38+
max: 100,
39+
expected: 100,
40+
fallback: undefined,
41+
},
42+
],
43+
[
44+
'returns min when value is less than min',
45+
{
46+
value: 5,
47+
min: 10,
48+
max: 100,
49+
expected: 10,
50+
fallback: undefined,
51+
},
52+
],
53+
[
54+
'returns the value when it is within the range',
55+
{
56+
value: 50,
57+
min: 10,
58+
max: 100,
59+
expected: 50,
60+
fallback: undefined,
61+
},
62+
],
63+
[
64+
'returns the fallback value when provided is not valid',
65+
{
66+
value: 'invalid',
67+
min: 10,
68+
max: 100,
69+
expected: 20,
70+
fallback: 20,
71+
},
72+
],
73+
[
74+
'returns the max value when fallback is not valid',
75+
{
76+
value: 'invalid',
77+
min: 10,
78+
max: 75,
79+
expected: 75,
80+
fallback: '20',
81+
},
82+
],
83+
])('%s', (_description, { value, min, max, expected, fallback }) => {
84+
const result = clampToRange(value, min, max, 'Test Label', fallback)
2185
expect(result).toBe(expected)
2286
})
2387

src/posthog-core.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ export class PostHog {
427427
this._retryQueue = new RetryQueue(this)
428428
this.__request_queue = []
429429

430-
this.sessionManager = new SessionIdManager(this.config, this.persistence)
430+
this.sessionManager = new SessionIdManager(this)
431431
this.sessionPropsManager = new SessionPropsManager(this.sessionManager, this.persistence)
432432

433433
new TracingHeaders(this).startIfEnabledOrStop()

src/sessionid.ts

+16-13
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import { isArray, isNumber, isUndefined } from './utils/type-utils'
99
import { logger } from './utils/logger'
1010

1111
import { clampToRange } from './utils/number-utils'
12+
import { PostHog } from './posthog-core'
1213

13-
const DEFAULT_SESSION_IDLE_TIMEOUT_SECONDS = 30 * 60 // 30 minutes
14-
const MAX_SESSION_IDLE_TIMEOUT_SECONDS = 10 * 60 * 60 // 10 hours
14+
export const DEFAULT_SESSION_IDLE_TIMEOUT_SECONDS = 30 * 60 // 30 minutes
15+
export const MAX_SESSION_IDLE_TIMEOUT_SECONDS = 10 * 60 * 60 // 10 hours
1516
const MIN_SESSION_IDLE_TIMEOUT_SECONDS = 60 // 1 minute
1617
const SESSION_LENGTH_LIMIT_MILLISECONDS = 24 * 3600 * 1000 // 24 hours
1718

@@ -30,32 +31,34 @@ export class SessionIdManager {
3031
private _sessionIdChangedHandlers: SessionIdChangedCallback[] = []
3132
private readonly _sessionTimeoutMs: number
3233

33-
constructor(
34-
config: Partial<PostHogConfig>,
35-
persistence: PostHogPersistence,
36-
sessionIdGenerator?: () => string,
37-
windowIdGenerator?: () => string
38-
) {
39-
this.config = config
40-
this.persistence = persistence
34+
constructor(instance: PostHog, sessionIdGenerator?: () => string, windowIdGenerator?: () => string) {
35+
if (!instance.persistence) {
36+
throw new Error('SessionIdManager requires a PostHogPersistence instance')
37+
}
38+
39+
this.config = instance.config
40+
this.persistence = instance.persistence
4141
this._windowId = undefined
4242
this._sessionId = undefined
4343
this._sessionStartTimestamp = null
4444
this._sessionActivityTimestamp = null
4545
this._sessionIdGenerator = sessionIdGenerator || uuidv7
4646
this._windowIdGenerator = windowIdGenerator || uuidv7
4747

48-
const persistenceName = config['persistence_name'] || config['token']
48+
const persistenceName = this.config['persistence_name'] || this.config['token']
4949

50-
const desiredTimeout = config['session_idle_timeout_seconds'] || DEFAULT_SESSION_IDLE_TIMEOUT_SECONDS
50+
const desiredTimeout = this.config['session_idle_timeout_seconds'] || DEFAULT_SESSION_IDLE_TIMEOUT_SECONDS
5151
this._sessionTimeoutMs =
5252
clampToRange(
5353
desiredTimeout,
5454
MIN_SESSION_IDLE_TIMEOUT_SECONDS,
5555
MAX_SESSION_IDLE_TIMEOUT_SECONDS,
56-
'session_idle_timeout_seconds'
56+
'session_idle_timeout_seconds',
57+
DEFAULT_SESSION_IDLE_TIMEOUT_SECONDS
5758
) * 1000
5859

60+
instance.register({ $configured_session_timeout_ms: this._sessionTimeoutMs })
61+
5962
this._window_id_storage_key = 'ph_' + persistenceName + '_window_id'
6063
this._primary_window_exists_storage_key = 'ph_' + persistenceName + '_primary_window_exists'
6164

src/utils/number-utils.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
import { isNumber } from './type-utils'
22
import { logger } from './logger'
33

4-
export function clampToRange(value: unknown, min: number, max: number, label?: string): number {
4+
/**
5+
* Clamps a value to a range.
6+
* @param value the value to clamp
7+
* @param min the minimum value
8+
* @param max the maximum value
9+
* @param label if provided then enables logging and prefixes all logs with labels
10+
* @param fallbackValue if provided then returns this value if the value is not a valid number
11+
*/
12+
export function clampToRange(value: unknown, min: number, max: number, label?: string, fallbackValue?: number): number {
513
if (min > max) {
614
logger.warn('min cannot be greater than max.')
715
min = max
816
}
917

1018
if (!isNumber(value)) {
11-
label && logger.warn(label + ' must be a number. Defaulting to max value:' + max)
12-
return max
19+
label &&
20+
logger.warn(
21+
label + ' must be a number. using max or fallback. max: ' + max + ', fallback: ' + fallbackValue
22+
)
23+
return clampToRange(fallbackValue || max, min, max, label)
1324
} else if (value > max) {
1425
label && logger.warn(label + ' cannot be greater than max: ' + max + '. Using max value instead.')
1526
return max

0 commit comments

Comments
 (0)