@leanbase.com/js 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,103 @@
1
+ import { window } from './utils'
2
+ import { Leanbase } from './leanbase'
3
+ import { addEventListener } from './utils'
4
+ import { isArray } from '@posthog/core'
5
+
6
+ export interface ScrollContext {
7
+ // scroll is how far down the page the user has scrolled,
8
+ // content is how far down the page the user can view content
9
+ // (e.g. if the page is 1000 tall, but the user's screen is only 500 tall,
10
+ // and they don't scroll at all, then scroll is 0 and content is 500)
11
+ maxScrollHeight?: number
12
+ maxScrollY?: number
13
+ lastScrollY?: number
14
+ maxContentHeight?: number
15
+ maxContentY?: number
16
+ lastContentY?: number
17
+ }
18
+
19
+ // This class is responsible for tracking scroll events and maintaining the scroll context
20
+ export class ScrollManager {
21
+ private _context: ScrollContext | undefined
22
+
23
+ constructor(private _instance: Leanbase) {}
24
+
25
+ getContext(): ScrollContext | undefined {
26
+ return this._context
27
+ }
28
+
29
+ resetContext(): ScrollContext | undefined {
30
+ const ctx = this._context
31
+
32
+ // update the scroll properties for the new page, but wait until the next tick
33
+ // of the event loop
34
+ setTimeout(this._updateScrollData, 0)
35
+
36
+ return ctx
37
+ }
38
+
39
+ private _updateScrollData = () => {
40
+ if (!this._context) {
41
+ this._context = {}
42
+ }
43
+
44
+ const el = this.scrollElement()
45
+
46
+ const scrollY = this.scrollY()
47
+ const scrollHeight = el ? Math.max(0, el.scrollHeight - el.clientHeight) : 0
48
+ const contentY = scrollY + (el?.clientHeight || 0)
49
+ const contentHeight = el?.scrollHeight || 0
50
+
51
+ this._context.lastScrollY = Math.ceil(scrollY)
52
+ this._context.maxScrollY = Math.max(scrollY, this._context.maxScrollY ?? 0)
53
+ this._context.maxScrollHeight = Math.max(scrollHeight, this._context.maxScrollHeight ?? 0)
54
+
55
+ this._context.lastContentY = contentY
56
+ this._context.maxContentY = Math.max(contentY, this._context.maxContentY ?? 0)
57
+ this._context.maxContentHeight = Math.max(contentHeight, this._context.maxContentHeight ?? 0)
58
+ }
59
+
60
+ // `capture: true` is required to get scroll events for other scrollable elements
61
+ // on the page, not just the window
62
+ // see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#usecapture
63
+ startMeasuringScrollPosition() {
64
+ addEventListener(window, 'scroll', this._updateScrollData, { capture: true })
65
+ addEventListener(window, 'scrollend', this._updateScrollData, { capture: true })
66
+ addEventListener(window, 'resize', this._updateScrollData)
67
+ }
68
+
69
+ public scrollElement(): Element | undefined {
70
+ if (this._instance.config.scroll_root_selector) {
71
+ const selectors = isArray(this._instance.config.scroll_root_selector)
72
+ ? this._instance.config.scroll_root_selector
73
+ : [this._instance.config.scroll_root_selector]
74
+ for (const selector of selectors) {
75
+ const element = window?.document.querySelector(selector)
76
+ if (element) {
77
+ return element
78
+ }
79
+ }
80
+ return undefined
81
+ } else {
82
+ return window?.document.documentElement
83
+ }
84
+ }
85
+
86
+ public scrollY(): number {
87
+ if (this._instance.config.scroll_root_selector) {
88
+ const element = this.scrollElement()
89
+ return (element && element.scrollTop) || 0
90
+ } else {
91
+ return window ? window.scrollY || window.pageYOffset || window.document.documentElement.scrollTop || 0 : 0
92
+ }
93
+ }
94
+
95
+ public scrollX(): number {
96
+ if (this._instance.config.scroll_root_selector) {
97
+ const element = this.scrollElement()
98
+ return (element && element.scrollLeft) || 0
99
+ } else {
100
+ return window ? window.scrollX || window.pageXOffset || window.document.documentElement.scrollLeft || 0 : 0
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,114 @@
1
+ /* Store some session-level attribution-related properties in the persistence layer
2
+ *
3
+ * These have the same lifespan as a session_id, meaning that if the session_id changes, these properties will be reset.
4
+ *
5
+ * We only store the entry URL and referrer, and derive many props (such as utm tags) from those.
6
+ *
7
+ * Given that the cookie is limited to 4K bytes, we don't want to store too much data, so we chose not to store device
8
+ * properties (such as browser, OS, etc) here, as usually getting the current value of those from event properties is
9
+ * sufficient.
10
+ */
11
+ import { getPersonInfo, getPersonPropsFromInfo } from './utils/event-utils'
12
+ import type { SessionIdManager } from './sessionid'
13
+ import type { LeanbasePersistence } from './leanbase-persistence'
14
+ import { CLIENT_SESSION_PROPS } from './constants'
15
+ import type { Leanbase } from './leanbase'
16
+ import { each, stripEmptyProperties } from './utils'
17
+ import { stripLeadingDollar } from '@posthog/core'
18
+
19
+ interface LegacySessionSourceProps {
20
+ initialPathName: string
21
+ referringDomain: string // Is actually referring host, but named referring domain for internal consistency. Should contain a port if there is one.
22
+ utm_medium?: string
23
+ utm_source?: string
24
+ utm_campaign?: string
25
+ utm_content?: string
26
+ utm_term?: string
27
+ }
28
+
29
+ interface CurrentSessionSourceProps {
30
+ r: string // Referring host
31
+ u: string | undefined // full URL
32
+ }
33
+
34
+ interface StoredSessionSourceProps {
35
+ sessionId: string
36
+ props: LegacySessionSourceProps | CurrentSessionSourceProps
37
+ }
38
+
39
+ const generateSessionSourceParams = (posthog?: Leanbase): LegacySessionSourceProps | CurrentSessionSourceProps => {
40
+ return getPersonInfo(posthog?.config.mask_personal_data_properties, posthog?.config.custom_personal_data_properties)
41
+ }
42
+
43
+ export class SessionPropsManager {
44
+ private readonly _instance: Leanbase
45
+ private readonly _sessionIdManager: SessionIdManager
46
+ private readonly _persistence: LeanbasePersistence
47
+ private readonly _sessionSourceParamGenerator: (
48
+ instance?: Leanbase
49
+ ) => LegacySessionSourceProps | CurrentSessionSourceProps
50
+
51
+ constructor(
52
+ instance: Leanbase,
53
+ sessionIdManager: SessionIdManager,
54
+ persistence: LeanbasePersistence,
55
+ sessionSourceParamGenerator?: (instance?: Leanbase) => LegacySessionSourceProps | CurrentSessionSourceProps
56
+ ) {
57
+ this._instance = instance
58
+ this._sessionIdManager = sessionIdManager
59
+ this._persistence = persistence
60
+ this._sessionSourceParamGenerator = sessionSourceParamGenerator || generateSessionSourceParams
61
+
62
+ this._sessionIdManager.onSessionId(this._onSessionIdCallback)
63
+ }
64
+
65
+ _getStored(): StoredSessionSourceProps | undefined {
66
+ return this._persistence.props[CLIENT_SESSION_PROPS]
67
+ }
68
+
69
+ _onSessionIdCallback = (sessionId: string) => {
70
+ const stored = this._getStored()
71
+ if (stored && stored.sessionId === sessionId) {
72
+ return
73
+ }
74
+
75
+ const newProps: StoredSessionSourceProps = {
76
+ sessionId,
77
+ props: this._sessionSourceParamGenerator(this._instance),
78
+ }
79
+ this._persistence.register({ [CLIENT_SESSION_PROPS]: newProps })
80
+ }
81
+
82
+ getSetOnceProps() {
83
+ const p = this._getStored()?.props
84
+ if (!p) {
85
+ return {}
86
+ }
87
+ if ('r' in p) {
88
+ return getPersonPropsFromInfo(p)
89
+ } else {
90
+ return {
91
+ $referring_domain: p.referringDomain,
92
+ $pathname: p.initialPathName,
93
+ utm_source: p.utm_source,
94
+ utm_campaign: p.utm_campaign,
95
+ utm_medium: p.utm_medium,
96
+ utm_content: p.utm_content,
97
+ utm_term: p.utm_term,
98
+ }
99
+ }
100
+ }
101
+
102
+ getSessionProps() {
103
+ // it's the same props, but don't include null for unset properties, and add a prefix
104
+ const p: Record<string, any> = {}
105
+ each(stripEmptyProperties(this.getSetOnceProps()), (v, k) => {
106
+ if (k === '$current_url') {
107
+ // $session_entry_current_url would be a weird name, call it $session_entry_url instead
108
+ k = 'url'
109
+ }
110
+ p[`$session_entry_${stripLeadingDollar(k)}`] = v
111
+ })
112
+ return p
113
+ }
114
+ }
@@ -0,0 +1,330 @@
1
+ import { LeanbasePersistence } from './leanbase-persistence'
2
+ import { SESSION_ID } from './constants'
3
+ import { sessionStore } from './storage'
4
+ import { LeanbaseConfig, SessionIdChangedCallback } from './types'
5
+ import { uuid7ToTimestampMs, uuidv7 } from './uuidv7'
6
+ import { window } from './utils'
7
+
8
+ import { isArray, isNumber, isUndefined, clampToRange, Logger } from '@posthog/core'
9
+ import { Leanbase } from './leanbase'
10
+ import { addEventListener } from './utils'
11
+ import { SimpleEventEmitter } from './utils/simple-event-emitter'
12
+ import { logger } from './leanbase-logger'
13
+
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
16
+ const MIN_SESSION_IDLE_TIMEOUT_SECONDS = 60 // 1 minute
17
+ const SESSION_LENGTH_LIMIT_MILLISECONDS = 24 * 3600 * 1000 // 24 hours
18
+
19
+ export class SessionIdManager {
20
+ private readonly _sessionIdGenerator: () => string
21
+ private readonly _windowIdGenerator: () => string
22
+ private _config: Partial<LeanbaseConfig>
23
+ private _persistence: LeanbasePersistence
24
+ private _windowId: string | null | undefined
25
+ private _sessionId: string | null | undefined
26
+ private readonly _window_id_storage_key: string
27
+ private readonly _primary_window_exists_storage_key: string
28
+ private _sessionStartTimestamp: number | null
29
+
30
+ private _sessionActivityTimestamp: number | null
31
+ private _sessionIdChangedHandlers: SessionIdChangedCallback[] = []
32
+ private readonly _sessionTimeoutMs: number
33
+
34
+ // we track activity so we can end the session proactively when it has passed the idle timeout
35
+ private _enforceIdleTimeout: ReturnType<typeof setTimeout> | undefined
36
+
37
+ private _beforeUnloadListener: (() => void) | undefined = undefined
38
+
39
+ private _eventEmitter: SimpleEventEmitter = new SimpleEventEmitter()
40
+ public on(event: 'forcedIdleReset', handler: () => void): () => void {
41
+ return this._eventEmitter.on(event, handler)
42
+ }
43
+
44
+ constructor(instance: Leanbase, sessionIdGenerator?: () => string, windowIdGenerator?: () => string) {
45
+ if (!instance.persistence) {
46
+ throw new Error('SessionIdManager requires a LeanbasePersistence instance')
47
+ }
48
+ if (instance.config.cookieless_mode === 'always') {
49
+ throw new Error('SessionIdManager cannot be used with cookieless_mode="always"')
50
+ }
51
+
52
+ this._config = instance.config
53
+ this._persistence = instance.persistence
54
+ this._windowId = undefined
55
+ this._sessionId = undefined
56
+ this._sessionStartTimestamp = null
57
+ this._sessionActivityTimestamp = null
58
+ this._sessionIdGenerator = sessionIdGenerator || uuidv7
59
+ this._windowIdGenerator = windowIdGenerator || uuidv7
60
+
61
+ const persistenceName = this._config['persistence_name'] || this._config['token']
62
+
63
+ const desiredTimeout = this._config['session_idle_timeout_seconds'] || DEFAULT_SESSION_IDLE_TIMEOUT_SECONDS
64
+ this._sessionTimeoutMs =
65
+ clampToRange(
66
+ desiredTimeout,
67
+ MIN_SESSION_IDLE_TIMEOUT_SECONDS,
68
+ MAX_SESSION_IDLE_TIMEOUT_SECONDS,
69
+ logger as Logger,
70
+ DEFAULT_SESSION_IDLE_TIMEOUT_SECONDS
71
+ ) * 1000
72
+
73
+ instance.register({ $configured_session_timeout_ms: this._sessionTimeoutMs })
74
+ this._resetIdleTimer()
75
+
76
+ this._window_id_storage_key = 'ph_' + persistenceName + '_window_id'
77
+ this._primary_window_exists_storage_key = 'ph_' + persistenceName + '_primary_window_exists'
78
+
79
+ // primary_window_exists is set when the DOM has been loaded and is cleared on unload
80
+ // if it exists here it means there was no unload which suggests this window is opened as a tab duplication, window.open, etc.
81
+ if (this._canUseSessionStorage()) {
82
+ const lastWindowId = sessionStore._parse(this._window_id_storage_key)
83
+
84
+ const primaryWindowExists = sessionStore._parse(this._primary_window_exists_storage_key)
85
+ if (lastWindowId && !primaryWindowExists) {
86
+ // Persist window from previous storage state
87
+ this._windowId = lastWindowId
88
+ } else {
89
+ // Wipe any reference to previous window id
90
+ sessionStore._remove(this._window_id_storage_key)
91
+ }
92
+ // Flag this session as having a primary window
93
+ sessionStore._set(this._primary_window_exists_storage_key, true)
94
+ }
95
+
96
+ if (this._config.bootstrap?.sessionID) {
97
+ try {
98
+ const sessionStartTimestamp = uuid7ToTimestampMs(this._config.bootstrap.sessionID)
99
+ this._setSessionId(this._config.bootstrap.sessionID, new Date().getTime(), sessionStartTimestamp)
100
+ } catch (e) {
101
+ logger.error('Invalid sessionID in bootstrap', e)
102
+ }
103
+ }
104
+
105
+ this._listenToReloadWindow()
106
+ }
107
+
108
+ get sessionTimeoutMs(): number {
109
+ return this._sessionTimeoutMs
110
+ }
111
+
112
+ onSessionId(callback: SessionIdChangedCallback): () => void {
113
+ // KLUDGE: when running in tests the handlers array was always undefined
114
+ // it's yucky but safe to set it here so that it's always definitely available
115
+ if (isUndefined(this._sessionIdChangedHandlers)) {
116
+ this._sessionIdChangedHandlers = []
117
+ }
118
+
119
+ this._sessionIdChangedHandlers.push(callback)
120
+ if (this._sessionId) {
121
+ callback(this._sessionId, this._windowId)
122
+ }
123
+ return () => {
124
+ this._sessionIdChangedHandlers = this._sessionIdChangedHandlers.filter((h) => h !== callback)
125
+ }
126
+ }
127
+
128
+ private _canUseSessionStorage(): boolean {
129
+ // We only want to use sessionStorage if persistence is enabled and not memory storage
130
+ return this._config.persistence !== 'memory' && !this._persistence._disabled && sessionStore._is_supported()
131
+ }
132
+
133
+ // Note: this tries to store the windowId in sessionStorage. SessionStorage is unique to the current window/tab,
134
+ // and persists page loads/reloads. So it's uniquely suited for storing the windowId. This function also respects
135
+ // when persistence is disabled (by user config) and when sessionStorage is not supported (it *should* be supported on all browsers),
136
+ // and in that case, it falls back to memory (which sadly, won't persist page loads)
137
+ private _setWindowId(windowId: string): void {
138
+ if (windowId !== this._windowId) {
139
+ this._windowId = windowId
140
+ if (this._canUseSessionStorage()) {
141
+ sessionStore._set(this._window_id_storage_key, windowId)
142
+ }
143
+ }
144
+ }
145
+
146
+ private _getWindowId(): string | null {
147
+ if (this._windowId) {
148
+ return this._windowId
149
+ }
150
+ if (this._canUseSessionStorage()) {
151
+ return sessionStore._parse(this._window_id_storage_key)
152
+ }
153
+ // New window id will be generated
154
+ return null
155
+ }
156
+
157
+ // Note: 'this.persistence.register' can be disabled in the config.
158
+ // In that case, this works by storing sessionId and the timestamp in memory.
159
+ private _setSessionId(
160
+ sessionId: string | null,
161
+ sessionActivityTimestamp: number | null,
162
+ sessionStartTimestamp: number | null
163
+ ): void {
164
+ if (
165
+ sessionId !== this._sessionId ||
166
+ sessionActivityTimestamp !== this._sessionActivityTimestamp ||
167
+ sessionStartTimestamp !== this._sessionStartTimestamp
168
+ ) {
169
+ this._sessionStartTimestamp = sessionStartTimestamp
170
+ this._sessionActivityTimestamp = sessionActivityTimestamp
171
+ this._sessionId = sessionId
172
+
173
+ this._persistence.register({
174
+ [SESSION_ID]: [sessionActivityTimestamp, sessionId, sessionStartTimestamp],
175
+ })
176
+ }
177
+ }
178
+
179
+ private _getSessionId(): [number, string, number] {
180
+ if (this._sessionId && this._sessionActivityTimestamp && this._sessionStartTimestamp) {
181
+ return [this._sessionActivityTimestamp, this._sessionId, this._sessionStartTimestamp]
182
+ }
183
+ const sessionIdInfo = this._persistence.props[SESSION_ID]
184
+
185
+ if (isArray(sessionIdInfo) && sessionIdInfo.length === 2) {
186
+ // Storage does not yet have a session start time. Add the last activity timestamp as the start time
187
+ sessionIdInfo.push(sessionIdInfo[0])
188
+ }
189
+
190
+ return sessionIdInfo || [0, null, 0]
191
+ }
192
+
193
+ // Resets the session id by setting it to null. On the subsequent call to checkAndGetSessionAndWindowId,
194
+ // new ids will be generated.
195
+ resetSessionId(): void {
196
+ this._setSessionId(null, null, null)
197
+ }
198
+
199
+ /**
200
+ * Cleans up resources used by SessionIdManager.
201
+ * Should be called when the SessionIdManager is no longer needed to prevent memory leaks.
202
+ */
203
+ destroy(): void {
204
+ // Clear the idle timeout timer
205
+ clearTimeout(this._enforceIdleTimeout)
206
+ this._enforceIdleTimeout = undefined
207
+
208
+ // Remove the beforeunload event listener
209
+ if (this._beforeUnloadListener && window) {
210
+ window.removeEventListener('beforeunload', this._beforeUnloadListener, { capture: false } as any)
211
+ this._beforeUnloadListener = undefined
212
+ }
213
+
214
+ // Clear session id changed handlers
215
+ this._sessionIdChangedHandlers = []
216
+ }
217
+
218
+ /*
219
+ * Listens to window unloads and removes the primaryWindowExists key from sessionStorage.
220
+ * Reloaded or fresh tabs created after a DOM unloads (reloading the same tab) WILL NOT have this primaryWindowExists flag in session storage.
221
+ * Cloned sessions (new tab, tab duplication, window.open(), ...) WILL have this primaryWindowExists flag in their copied session storage.
222
+ * We conditionally check the primaryWindowExists value in the constructor to decide if the window id in the last session storage should be carried over.
223
+ */
224
+ private _listenToReloadWindow(): void {
225
+ this._beforeUnloadListener = () => {
226
+ if (this._canUseSessionStorage()) {
227
+ sessionStore._remove(this._primary_window_exists_storage_key)
228
+ }
229
+ }
230
+ addEventListener(window, 'beforeunload', this._beforeUnloadListener, { capture: false })
231
+ }
232
+
233
+ private _sessionHasBeenIdleTooLong = (timestamp: number, lastActivityTimestamp: number) => {
234
+ return Math.abs(timestamp - lastActivityTimestamp) > this.sessionTimeoutMs
235
+ }
236
+
237
+ /*
238
+ * This function returns the current sessionId and windowId. It should be used to
239
+ * access these values over directly calling `._sessionId` or `._windowId`.
240
+ * In addition to returning the sessionId and windowId, this function also manages cycling the
241
+ * sessionId and windowId when appropriate by doing the following:
242
+ *
243
+ * 1. If the sessionId or windowId is not set, it will generate a new one and store it.
244
+ * 2. If the readOnly param is set to false, it will:
245
+ * a. Check if it has been > SESSION_CHANGE_THRESHOLD since the last call with this flag set.
246
+ * If so, it will generate a new sessionId and store it.
247
+ * b. Update the timestamp stored with the sessionId to ensure the current session is extended
248
+ * for the appropriate amount of time.
249
+ *
250
+ * @param {boolean} readOnly (optional) Defaults to False. Should be set to True when the call to the function should not extend or cycle the session (e.g. being called for non-user generated events)
251
+ * @param {Number} timestamp (optional) Defaults to the current time. The timestamp to be stored with the sessionId (used when determining if a new sessionId should be generated)
252
+ */
253
+ checkAndGetSessionAndWindowId(readOnly = false, _timestamp: number | null = null) {
254
+ if (this._config.cookieless_mode === 'always') {
255
+ throw new Error('checkAndGetSessionAndWindowId should not be called with cookieless_mode="always"')
256
+ }
257
+ const timestamp = _timestamp || new Date().getTime()
258
+
259
+ // eslint-disable-next-line prefer-const
260
+ let [lastActivityTimestamp, sessionId, startTimestamp] = this._getSessionId()
261
+ let windowId = this._getWindowId()
262
+
263
+ const sessionPastMaximumLength =
264
+ isNumber(startTimestamp) &&
265
+ startTimestamp > 0 &&
266
+ Math.abs(timestamp - startTimestamp) > SESSION_LENGTH_LIMIT_MILLISECONDS
267
+
268
+ let valuesChanged = false
269
+ const noSessionId = !sessionId
270
+ const activityTimeout = !readOnly && this._sessionHasBeenIdleTooLong(timestamp, lastActivityTimestamp)
271
+ if (noSessionId || activityTimeout || sessionPastMaximumLength) {
272
+ sessionId = this._sessionIdGenerator()
273
+ windowId = this._windowIdGenerator()
274
+ logger.info('new session ID generated', {
275
+ sessionId,
276
+ windowId,
277
+ changeReason: { noSessionId, activityTimeout, sessionPastMaximumLength },
278
+ })
279
+ startTimestamp = timestamp
280
+ valuesChanged = true
281
+ } else if (!windowId) {
282
+ windowId = this._windowIdGenerator()
283
+ valuesChanged = true
284
+ }
285
+
286
+ const newActivityTimestamp =
287
+ lastActivityTimestamp === 0 || !readOnly || sessionPastMaximumLength ? timestamp : lastActivityTimestamp
288
+ const sessionStartTimestamp = startTimestamp === 0 ? new Date().getTime() : startTimestamp
289
+
290
+ this._setWindowId(windowId)
291
+ this._setSessionId(sessionId, newActivityTimestamp, sessionStartTimestamp)
292
+
293
+ if (!readOnly) {
294
+ this._resetIdleTimer()
295
+ }
296
+
297
+ if (valuesChanged) {
298
+ this._sessionIdChangedHandlers.forEach((handler) =>
299
+ handler(
300
+ sessionId,
301
+ windowId,
302
+ valuesChanged ? { noSessionId, activityTimeout, sessionPastMaximumLength } : undefined
303
+ )
304
+ )
305
+ }
306
+
307
+ return {
308
+ sessionId,
309
+ windowId,
310
+ sessionStartTimestamp,
311
+ changeReason: valuesChanged ? { noSessionId, activityTimeout, sessionPastMaximumLength } : undefined,
312
+ lastActivityTimestamp: lastActivityTimestamp,
313
+ }
314
+ }
315
+
316
+ private _resetIdleTimer() {
317
+ clearTimeout(this._enforceIdleTimeout)
318
+ this._enforceIdleTimeout = setTimeout(() => {
319
+ // enforce idle timeout a little after the session timeout to ensure the session is reset even without activity
320
+ // we need to check session activity first in case a different window has kept the session active
321
+ // while this window has been idle - and the timer has not progressed - e.g. window memory frozen while hidden
322
+ const [lastActivityTimestamp] = this._getSessionId()
323
+ if (this._sessionHasBeenIdleTooLong(new Date().getTime(), lastActivityTimestamp)) {
324
+ const idleSessionId = this._sessionId
325
+ this.resetSessionId()
326
+ this._eventEmitter.emit('forcedIdleReset', { idleSessionId })
327
+ }
328
+ }, this.sessionTimeoutMs * 1.1)
329
+ }
330
+ }