From c89b5788b260c31045394f77991e77eaa82eb12e Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Mon, 4 Nov 2024 14:38:44 +0100 Subject: [PATCH] fix: rotate session id proactively --- src/sessionid.ts | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/sessionid.ts b/src/sessionid.ts index b2e81a413..8b03c5b3c 100644 --- a/src/sessionid.ts +++ b/src/sessionid.ts @@ -29,6 +29,9 @@ export class SessionIdManager { private _sessionIdChangedHandlers: SessionIdChangedCallback[] = [] private readonly _sessionTimeoutMs: number + // we track activity so we can end the session proactively when it has passed the idle timeout + private _enforceIdleTimeout: ReturnType | undefined + constructor( config: Partial, persistence: PostHogPersistence, @@ -162,14 +165,14 @@ export class SessionIdManager { if (this._sessionId && this._sessionActivityTimestamp && this._sessionStartTimestamp) { return [this._sessionActivityTimestamp, this._sessionId, this._sessionStartTimestamp] } - const sessionId = this.persistence.props[SESSION_ID] + const sessionIdInfo = this.persistence.props[SESSION_ID] - if (isArray(sessionId) && sessionId.length === 2) { + if (isArray(sessionIdInfo) && sessionIdInfo.length === 2) { // Storage does not yet have a session start time. Add the last activity timestamp as the start time - sessionId.push(sessionId[0]) + sessionIdInfo.push(sessionIdInfo[0]) } - return sessionId || [0, null, 0] + return sessionIdInfo || [0, null, 0] } // Resets the session id by setting it to null. On the subsequent call to checkAndGetSessionAndWindowId, @@ -212,7 +215,7 @@ export class SessionIdManager { const timestamp = _timestamp || new Date().getTime() // eslint-disable-next-line prefer-const - let [lastTimestamp, sessionId, startTimestamp] = this._getSessionId() + let [lastActivityTimestamp, sessionId, startTimestamp] = this._getSessionId() let windowId = this._getWindowId() const sessionPastMaximumLength = @@ -222,7 +225,7 @@ export class SessionIdManager { let valuesChanged = false const noSessionId = !sessionId - const activityTimeout = !readOnly && Math.abs(timestamp - lastTimestamp) > this.sessionTimeoutMs + const activityTimeout = !readOnly && Math.abs(timestamp - lastActivityTimestamp) > this.sessionTimeoutMs if (noSessionId || activityTimeout || sessionPastMaximumLength) { sessionId = this._sessionIdGenerator() windowId = this._windowIdGenerator() @@ -238,11 +241,20 @@ export class SessionIdManager { valuesChanged = true } - const newTimestamp = lastTimestamp === 0 || !readOnly || sessionPastMaximumLength ? timestamp : lastTimestamp + const newActivityTimestamp = + lastActivityTimestamp === 0 || !readOnly || sessionPastMaximumLength ? timestamp : lastActivityTimestamp const sessionStartTimestamp = startTimestamp === 0 ? new Date().getTime() : startTimestamp this._setWindowId(windowId) - this._setSessionId(sessionId, newTimestamp, sessionStartTimestamp) + this._setSessionId(sessionId, newActivityTimestamp, sessionStartTimestamp) + + if (!readOnly) { + clearTimeout(this._enforceIdleTimeout) + this._enforceIdleTimeout = setTimeout(() => { + // enforce idle timeout a little after the session timeout to ensure the session is reset even without activity + this.resetSessionId() + }, this.sessionTimeoutMs * 1.1) + } if (valuesChanged) { this._sessionIdChangedHandlers.forEach((handler) => @@ -259,7 +271,7 @@ export class SessionIdManager { windowId, sessionStartTimestamp, changeReason: valuesChanged ? { noSessionId, activityTimeout, sessionPastMaximumLength } : undefined, - lastActivityTimestamp: lastTimestamp, + lastActivityTimestamp: lastActivityTimestamp, } } }