From 6dbed1c4700feb41e72bfd3cb7931390eeef9372 Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Tue, 7 Apr 2020 10:54:02 +0200 Subject: [PATCH 01/13] =?UTF-8?q?=F0=9F=90=9B=20monitor=20activity=20liste?= =?UTF-8?q?ner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/sessionManagement.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/src/sessionManagement.ts b/packages/core/src/sessionManagement.ts index 1c3d1c73b4..8c07c01000 100644 --- a/packages/core/src/sessionManagement.ts +++ b/packages/core/src/sessionManagement.ts @@ -1,4 +1,5 @@ import { cacheCookieAccess, COOKIE_ACCESS_DELAY, CookieCache } from './cookie' +import { monitor } from './internalMonitoring' import { Observable } from './observable' import { tryOldCookiesMigration } from './oldCookiesMigration' import * as utils from './utils' @@ -105,9 +106,10 @@ export function stopSessionManagement() { let registeredActivityListeners: Array<() => void> = [] export function trackActivity(expandOrRenewSession: () => void) { + const doExpandOrRenewSession = monitor(expandOrRenewSession) const options = { capture: true, passive: true } ;['click', 'touchstart', 'keydown', 'scroll'].forEach((event: string) => { - document.addEventListener(event, expandOrRenewSession, options) - registeredActivityListeners.push(() => document.removeEventListener(event, expandOrRenewSession, options)) + document.addEventListener(event, doExpandOrRenewSession, options) + registeredActivityListeners.push(() => document.removeEventListener(event, doExpandOrRenewSession, options)) }) } From b5db9cb1b0f8b5c057b92e29d50269e593b20dee Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Tue, 7 Apr 2020 10:55:02 +0200 Subject: [PATCH 02/13] =?UTF-8?q?=E2=9C=A8[RUMF-430]=20add=20session=20tim?= =?UTF-8?q?eout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit session automatically expires after 4h --- packages/core/src/sessionManagement.ts | 35 +++- packages/core/test/sessionManagement.spec.ts | 159 +++++++++++++------ 2 files changed, 137 insertions(+), 57 deletions(-) diff --git a/packages/core/src/sessionManagement.ts b/packages/core/src/sessionManagement.ts index 8c07c01000..5aea7f2ee4 100644 --- a/packages/core/src/sessionManagement.ts +++ b/packages/core/src/sessionManagement.ts @@ -6,18 +6,17 @@ import * as utils from './utils' export const SESSION_COOKIE_NAME = '_dd_s' export const EXPIRATION_DELAY = 15 * utils.ONE_MINUTE +export const SESSION_TIME_OUT_DELAY = 4 * utils.ONE_HOUR export interface Session { renewObservable: Observable - getId(): string | undefined - getType(): T | undefined } export interface SessionState { id?: string - + created?: string [key: string]: string | undefined } @@ -26,19 +25,23 @@ export interface SessionState { */ export function startSessionManagement( sessionTypeKey: string, - computeSessionState: (rawType?: string) => { type: Type; isTracked: boolean } + computeSessionState: (rawType?: string) => { type: Type; isTracked: boolean }, + withNewSessionStrategy = false ): Session { const sessionCookie = cacheCookieAccess(SESSION_COOKIE_NAME) tryOldCookiesMigration(sessionCookie) const renewObservable = new Observable() - let currentSessionId = retrieveSession(sessionCookie).id + let currentSessionId = retrieveActiveSession(sessionCookie, withNewSessionStrategy).id const expandOrRenewSession = utils.throttle(() => { - const session = retrieveSession(sessionCookie) + const session = retrieveActiveSession(sessionCookie, withNewSessionStrategy) const { type, isTracked } = computeSessionState(session[sessionTypeKey]) session[sessionTypeKey] = type if (isTracked && !session.id) { session.id = utils.generateUUID() + if (withNewSessionStrategy) { + session.created = String(Date.now()) + } } // save changes and expand session duration persistSession(session, sessionCookie) @@ -55,10 +58,10 @@ export function startSessionManagement( return { getId() { - return retrieveSession(sessionCookie).id + return retrieveActiveSession(sessionCookie, withNewSessionStrategy).id }, getType() { - return retrieveSession(sessionCookie)[sessionTypeKey] as Type | undefined + return retrieveActiveSession(sessionCookie, withNewSessionStrategy)[sessionTypeKey] as Type | undefined }, renewObservable, } @@ -75,6 +78,22 @@ export function isValidSessionString(sessionString: string | undefined): session ) } +function retrieveActiveSession(sessionCookie: CookieCache, withNewSessionStrategy: boolean): SessionState { + const session = retrieveSession(sessionCookie) + if (!withNewSessionStrategy || isActiveSession(session)) { + return session + } + const timedOutSession = {} + persistSession(timedOutSession, sessionCookie) + return timedOutSession +} + +function isActiveSession(session: SessionState) { + // created can be undefined for versions which was not storing created date + // this check could be removed when older versions will not be available/live anymore + return session.created === undefined || Date.now() - Number(session.created) < SESSION_TIME_OUT_DELAY +} + function retrieveSession(sessionCookie: CookieCache): SessionState { const sessionString = sessionCookie.get() const session: SessionState = {} diff --git a/packages/core/test/sessionManagement.spec.ts b/packages/core/test/sessionManagement.spec.ts index d407b77d31..8c0034eff7 100644 --- a/packages/core/test/sessionManagement.spec.ts +++ b/packages/core/test/sessionManagement.spec.ts @@ -1,5 +1,11 @@ import { cacheCookieAccess, COOKIE_ACCESS_DELAY, CookieCache, getCookie, setCookie } from '../src/cookie' -import { Session, SESSION_COOKIE_NAME, startSessionManagement, stopSessionManagement } from '../src/sessionManagement' +import { + Session, + SESSION_COOKIE_NAME, + SESSION_TIME_OUT_DELAY, + startSessionManagement, + stopSessionManagement, +} from '../src/sessionManagement' import { isIE } from '../src/specHelper' describe('cacheCookieAccess', () => { @@ -90,48 +96,50 @@ describe('startSessionManagement', () => { stopSessionManagement() }) - it('when tracked, should store session type and id', () => { - const session = startSessionManagement(FIRST_SESSION_TYPE_KEY, () => ({ - isTracked: true, - type: FakeSessionType.TRACKED, - })) + describe('cookie management', () => { + it('when tracked, should store session type and id', () => { + const session = startSessionManagement(FIRST_SESSION_TYPE_KEY, () => ({ + isTracked: true, + type: FakeSessionType.TRACKED, + })) - expectSessionIdToBeDefined(session) - expectSessionTypeToBe(session, FIRST_SESSION_TYPE_KEY, FakeSessionType.TRACKED) - }) + expectSessionIdToBeDefined(session) + expectSessionTypeToBe(session, FIRST_SESSION_TYPE_KEY, FakeSessionType.TRACKED) + }) - it('when not tracked should store session type', () => { - const session = startSessionManagement(FIRST_SESSION_TYPE_KEY, () => ({ - isTracked: false, - type: FakeSessionType.NOT_TRACKED, - })) + it('when not tracked should store session type', () => { + const session = startSessionManagement(FIRST_SESSION_TYPE_KEY, () => ({ + isTracked: false, + type: FakeSessionType.NOT_TRACKED, + })) - expectSessionIdToNotBeDefined(session) - expectSessionTypeToBe(session, FIRST_SESSION_TYPE_KEY, FakeSessionType.NOT_TRACKED) - }) + expectSessionIdToNotBeDefined(session) + expectSessionTypeToBe(session, FIRST_SESSION_TYPE_KEY, FakeSessionType.NOT_TRACKED) + }) - it('when tracked should keep existing session type and id', () => { - setCookie(SESSION_COOKIE_NAME, 'id=abcdef&first=tracked', DURATION) + it('when tracked should keep existing session type and id', () => { + setCookie(SESSION_COOKIE_NAME, 'id=abcdef&first=tracked', DURATION) - const session = startSessionManagement(FIRST_SESSION_TYPE_KEY, () => ({ - isTracked: true, - type: FakeSessionType.TRACKED, - })) + const session = startSessionManagement(FIRST_SESSION_TYPE_KEY, () => ({ + isTracked: true, + type: FakeSessionType.TRACKED, + })) - expectSessionIdToBe(session, 'abcdef') - expectSessionTypeToBe(session, FIRST_SESSION_TYPE_KEY, FakeSessionType.TRACKED) - }) + expectSessionIdToBe(session, 'abcdef') + expectSessionTypeToBe(session, FIRST_SESSION_TYPE_KEY, FakeSessionType.TRACKED) + }) - it('when not tracked should keep existing session type', () => { - setCookie(SESSION_COOKIE_NAME, 'first=not-tracked', DURATION) + it('when not tracked should keep existing session type', () => { + setCookie(SESSION_COOKIE_NAME, 'first=not-tracked', DURATION) - const session = startSessionManagement(FIRST_SESSION_TYPE_KEY, () => ({ - isTracked: false, - type: FakeSessionType.NOT_TRACKED, - })) + const session = startSessionManagement(FIRST_SESSION_TYPE_KEY, () => ({ + isTracked: false, + type: FakeSessionType.NOT_TRACKED, + })) - expectSessionIdToNotBeDefined(session) - expectSessionTypeToBe(session, FIRST_SESSION_TYPE_KEY, FakeSessionType.NOT_TRACKED) + expectSessionIdToNotBeDefined(session) + expectSessionTypeToBe(session, FIRST_SESSION_TYPE_KEY, FakeSessionType.NOT_TRACKED) + }) }) describe('computeSessionState', () => { @@ -165,25 +173,27 @@ describe('startSessionManagement', () => { }) }) - it('should renew on activity after expiration', () => { - const session = startSessionManagement(FIRST_SESSION_TYPE_KEY, () => ({ - isTracked: true, - type: FakeSessionType.TRACKED, - })) - const renewSessionSpy = jasmine.createSpy() - session.renewObservable.subscribe(renewSessionSpy) + describe('session renewal', () => { + it('should renew on activity after expiration', () => { + const session = startSessionManagement(FIRST_SESSION_TYPE_KEY, () => ({ + isTracked: true, + type: FakeSessionType.TRACKED, + })) + const renewSessionSpy = jasmine.createSpy() + session.renewObservable.subscribe(renewSessionSpy) - expireSession() + expireSession() - expect(renewSessionSpy).not.toHaveBeenCalled() - expectSessionIdToNotBeDefined(session) - expectSessionTypeToNotBeDefined(session, FIRST_SESSION_TYPE_KEY) + expect(renewSessionSpy).not.toHaveBeenCalled() + expectSessionIdToNotBeDefined(session) + expectSessionTypeToNotBeDefined(session, FIRST_SESSION_TYPE_KEY) - document.dispatchEvent(new CustomEvent('click')) + document.dispatchEvent(new CustomEvent('click')) - expect(renewSessionSpy).toHaveBeenCalled() - expectSessionIdToBeDefined(session) - expectSessionTypeToBe(session, FIRST_SESSION_TYPE_KEY, FakeSessionType.TRACKED) + expect(renewSessionSpy).toHaveBeenCalled() + expectSessionIdToBeDefined(session) + expectSessionTypeToBe(session, FIRST_SESSION_TYPE_KEY, FakeSessionType.TRACKED) + }) }) describe('multiple startSessionManagement calls', () => { @@ -243,4 +253,55 @@ describe('startSessionManagement', () => { expect(renewSessionBSpy).toHaveBeenCalled() }) }) + + describe('session timeout', () => { + it('should expire the session when the time out delay is reached', () => { + const session = startSessionManagement( + FIRST_SESSION_TYPE_KEY, + () => ({ + isTracked: true, + type: FakeSessionType.TRACKED, + }), + true + ) + expect(session.getId()).toBeDefined() + expect(getCookie(SESSION_COOKIE_NAME)).toBeDefined() + + jasmine.clock().tick(SESSION_TIME_OUT_DELAY) + expect(session.getId()).toBeUndefined() + expect(getCookie(SESSION_COOKIE_NAME)).toBeUndefined() + }) + + it('should renew an existing timed out session', () => { + setCookie(SESSION_COOKIE_NAME, `id=abcde&first=tracked&created=${Date.now() - SESSION_TIME_OUT_DELAY}`, DURATION) + + const session = startSessionManagement( + FIRST_SESSION_TYPE_KEY, + () => ({ + isTracked: true, + type: FakeSessionType.TRACKED, + }), + true + ) + + expect(session.getId()).not.toBe('abcde') + expect(getCookie(SESSION_COOKIE_NAME)).toContain(`created=${Date.now()}`) + }) + + it('should not add created date to an existing session from an older versions', () => { + setCookie(SESSION_COOKIE_NAME, `id=abcde&first=tracked`, DURATION) + + const session = startSessionManagement( + FIRST_SESSION_TYPE_KEY, + () => ({ + isTracked: true, + type: FakeSessionType.TRACKED, + }), + true + ) + + expect(session.getId()).toBe('abcde') + expect(getCookie(SESSION_COOKIE_NAME)).not.toContain('created=') + }) + }) }) From 34e43da59452712b0d7e5eb1c7e9ae843bb104aa Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Tue, 7 Apr 2020 10:56:02 +0200 Subject: [PATCH 03/13] =?UTF-8?q?=E2=9C=A8[RUMF-430]=20enforce=20session?= =?UTF-8?q?=20expiration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit do not rely on browser expiration mechanism allow to test this logic --- packages/core/src/sessionManagement.ts | 27 ++++++++----- packages/core/src/utils.ts | 4 ++ .../core/test/oldCookiesMigration.spec.ts | 12 +++--- packages/core/test/sessionManagement.spec.ts | 39 +++++++++++++++++++ 4 files changed, 66 insertions(+), 16 deletions(-) diff --git a/packages/core/src/sessionManagement.ts b/packages/core/src/sessionManagement.ts index 5aea7f2ee4..e1f3fc759b 100644 --- a/packages/core/src/sessionManagement.ts +++ b/packages/core/src/sessionManagement.ts @@ -5,7 +5,7 @@ import { tryOldCookiesMigration } from './oldCookiesMigration' import * as utils from './utils' export const SESSION_COOKIE_NAME = '_dd_s' -export const EXPIRATION_DELAY = 15 * utils.ONE_MINUTE +export const SESSION_EXPIRATION_DELAY = 15 * utils.ONE_MINUTE export const SESSION_TIME_OUT_DELAY = 4 * utils.ONE_HOUR export interface Session { @@ -17,6 +17,7 @@ export interface Session { export interface SessionState { id?: string created?: string + expire?: string [key: string]: string | undefined } @@ -44,7 +45,7 @@ export function startSessionManagement( } } // save changes and expand session duration - persistSession(session, sessionCookie) + persistSession(session, sessionCookie, withNewSessionStrategy) // If the session id has changed, notify that the session has been renewed if (isTracked && currentSessionId !== session.id) { @@ -83,15 +84,18 @@ function retrieveActiveSession(sessionCookie: CookieCache, withNewSessionStrateg if (!withNewSessionStrategy || isActiveSession(session)) { return session } - const timedOutSession = {} - persistSession(timedOutSession, sessionCookie) - return timedOutSession + const inactiveSession = {} + persistSession(inactiveSession, sessionCookie, withNewSessionStrategy) + return inactiveSession } function isActiveSession(session: SessionState) { - // created can be undefined for versions which was not storing created date - // this check could be removed when older versions will not be available/live anymore - return session.created === undefined || Date.now() - Number(session.created) < SESSION_TIME_OUT_DELAY + // created and expire can be undefined for versions which was not storing them + // these checks could be removed when older versions will not be available/live anymore + return ( + (session.created === undefined || Date.now() - Number(session.created) < SESSION_TIME_OUT_DELAY) && + (session.expire === undefined || Date.now() < Number(session.expire)) + ) } function retrieveSession(sessionCookie: CookieCache): SessionState { @@ -109,12 +113,15 @@ function retrieveSession(sessionCookie: CookieCache): SessionState { return session } -export function persistSession(session: SessionState, cookie: CookieCache) { +export function persistSession(session: SessionState, cookie: CookieCache, withNewSessionStrategy = false) { + if (withNewSessionStrategy && !utils.isEmptyObject(session)) { + session.expire = String(Date.now() + SESSION_EXPIRATION_DELAY) + } const cookieString = utils .objectEntries(session) .map(([key, value]) => `${key}=${value}`) .join(SESSION_ENTRY_SEPARATOR) - cookie.set(cookieString, EXPIRATION_DELAY) + cookie.set(cookieString, SESSION_EXPIRATION_DELAY) } export function stopSessionManagement() { diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index eb40ba7d09..43a935447f 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -223,6 +223,10 @@ export function objectEntries(object: { [key: string]: unknown }) { return Object.keys(object).map((key) => [key, object[key]]) } +export function isEmptyObject(object: object) { + return Object.keys(object).length === 0 +} + export function getGlobalObject(): T { // tslint:disable-next-line: function-constructor no-function-constructor-with-string-args return (typeof globalThis === 'object' ? globalThis : Function('return this')()) as T diff --git a/packages/core/test/oldCookiesMigration.spec.ts b/packages/core/test/oldCookiesMigration.spec.ts index b540f6f8b6..a76ec03450 100644 --- a/packages/core/test/oldCookiesMigration.spec.ts +++ b/packages/core/test/oldCookiesMigration.spec.ts @@ -6,11 +6,11 @@ import { OLD_SESSION_COOKIE_NAME, tryOldCookiesMigration, } from '../src/oldCookiesMigration' -import { EXPIRATION_DELAY } from '../src/sessionManagement' +import { SESSION_EXPIRATION_DELAY } from '../src/sessionManagement' describe('old cookies migration', () => { it('should not touch current cookie', () => { - setCookie(SESSION_COOKIE_NAME, 'id=abcde&rum=0&logs=1', EXPIRATION_DELAY) + setCookie(SESSION_COOKIE_NAME, 'id=abcde&rum=0&logs=1', SESSION_EXPIRATION_DELAY) tryOldCookiesMigration(cacheCookieAccess(SESSION_COOKIE_NAME)) @@ -18,9 +18,9 @@ describe('old cookies migration', () => { }) it('should create new cookie from old cookie values', () => { - setCookie(OLD_SESSION_COOKIE_NAME, 'abcde', EXPIRATION_DELAY) - setCookie(OLD_LOGS_COOKIE_NAME, '1', EXPIRATION_DELAY) - setCookie(OLD_RUM_COOKIE_NAME, '0', EXPIRATION_DELAY) + setCookie(OLD_SESSION_COOKIE_NAME, 'abcde', SESSION_EXPIRATION_DELAY) + setCookie(OLD_LOGS_COOKIE_NAME, '1', SESSION_EXPIRATION_DELAY) + setCookie(OLD_RUM_COOKIE_NAME, '0', SESSION_EXPIRATION_DELAY) tryOldCookiesMigration(cacheCookieAccess(SESSION_COOKIE_NAME)) @@ -30,7 +30,7 @@ describe('old cookies migration', () => { }) it('should create new cookie from a single old cookie', () => { - setCookie(OLD_RUM_COOKIE_NAME, '0', EXPIRATION_DELAY) + setCookie(OLD_RUM_COOKIE_NAME, '0', SESSION_EXPIRATION_DELAY) tryOldCookiesMigration(cacheCookieAccess(SESSION_COOKIE_NAME)) diff --git a/packages/core/test/sessionManagement.spec.ts b/packages/core/test/sessionManagement.spec.ts index 8c0034eff7..d2c0972332 100644 --- a/packages/core/test/sessionManagement.spec.ts +++ b/packages/core/test/sessionManagement.spec.ts @@ -2,6 +2,7 @@ import { cacheCookieAccess, COOKIE_ACCESS_DELAY, CookieCache, getCookie, setCook import { Session, SESSION_COOKIE_NAME, + SESSION_EXPIRATION_DELAY, SESSION_TIME_OUT_DELAY, startSessionManagement, stopSessionManagement, @@ -304,4 +305,42 @@ describe('startSessionManagement', () => { expect(getCookie(SESSION_COOKIE_NAME)).not.toContain('created=') }) }) + + describe('session expiration', () => { + it('should expire the session after expiration delay', () => { + const session = startSessionManagement( + FIRST_SESSION_TYPE_KEY, + () => ({ + isTracked: true, + type: FakeSessionType.TRACKED, + }), + true + ) + expectSessionIdToBeDefined(session) + + jasmine.clock().tick(SESSION_EXPIRATION_DELAY) + expectSessionIdToNotBeDefined(session) + }) + + it('should expand duration on activity', () => { + const session = startSessionManagement( + FIRST_SESSION_TYPE_KEY, + () => ({ + isTracked: true, + type: FakeSessionType.TRACKED, + }), + true + ) + expectSessionIdToBeDefined(session) + + jasmine.clock().tick(SESSION_EXPIRATION_DELAY - 10) + document.dispatchEvent(new CustomEvent('click')) + + jasmine.clock().tick(10) + expectSessionIdToBeDefined(session) + + jasmine.clock().tick(SESSION_EXPIRATION_DELAY) + expectSessionIdToNotBeDefined(session) + }) + }) }) From 742d842d369d396b367ebfa0e31e9d551f95569b Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Tue, 7 Apr 2020 10:57:02 +0200 Subject: [PATCH 04/13] =?UTF-8?q?=E2=9C=A8[RUMF-430]=20expand=20session=20?= =?UTF-8?q?on=20visibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/sessionManagement.ts | 37 +++++++++++++++++--- packages/core/test/sessionManagement.spec.ts | 37 +++++++++++++++++--- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/packages/core/src/sessionManagement.ts b/packages/core/src/sessionManagement.ts index e1f3fc759b..677c236af2 100644 --- a/packages/core/src/sessionManagement.ts +++ b/packages/core/src/sessionManagement.ts @@ -7,6 +7,7 @@ import * as utils from './utils' export const SESSION_COOKIE_NAME = '_dd_s' export const SESSION_EXPIRATION_DELAY = 15 * utils.ONE_MINUTE export const SESSION_TIME_OUT_DELAY = 4 * utils.ONE_HOUR +export const VISIBILITY_CHECK_DELAY = utils.ONE_MINUTE export interface Session { renewObservable: Observable @@ -27,7 +28,8 @@ export interface SessionState { export function startSessionManagement( sessionTypeKey: string, computeSessionState: (rawType?: string) => { type: Type; isTracked: boolean }, - withNewSessionStrategy = false + withNewSessionStrategy = false, + visibilityStateProvider = () => document.visibilityState ): Session { const sessionCookie = cacheCookieAccess(SESSION_COOKIE_NAME) tryOldCookiesMigration(sessionCookie) @@ -54,8 +56,16 @@ export function startSessionManagement( } }, COOKIE_ACCESS_DELAY) + const expandSession = () => { + const session = retrieveActiveSession(sessionCookie, withNewSessionStrategy) + persistSession(session, sessionCookie, withNewSessionStrategy) + } + expandOrRenewSession() trackActivity(expandOrRenewSession) + if (withNewSessionStrategy) { + trackVisibility(expandSession, visibilityStateProvider) + } return { getId() { @@ -125,17 +135,34 @@ export function persistSession(session: SessionState, cookie: CookieCache, withN } export function stopSessionManagement() { - registeredActivityListeners.forEach((e) => e()) - registeredActivityListeners = [] + registeredListeners.forEach((e) => e()) + registeredIntervals.forEach((interval) => clearInterval(interval)) + registeredListeners = [] + registeredIntervals = [] } -let registeredActivityListeners: Array<() => void> = [] +let registeredListeners: Array<() => void> = [] +let registeredIntervals: number[] = [] export function trackActivity(expandOrRenewSession: () => void) { const doExpandOrRenewSession = monitor(expandOrRenewSession) const options = { capture: true, passive: true } ;['click', 'touchstart', 'keydown', 'scroll'].forEach((event: string) => { document.addEventListener(event, doExpandOrRenewSession, options) - registeredActivityListeners.push(() => document.removeEventListener(event, doExpandOrRenewSession, options)) + registeredListeners.push(() => document.removeEventListener(event, doExpandOrRenewSession, options)) }) } + +function trackVisibility(expandSession: () => void, visibilityStateProvider: () => VisibilityState) { + const expandSessionWhenVisible = monitor(() => { + if (visibilityStateProvider() === 'visible') { + expandSession() + } + }) + + const visibilityCheckInterval = window.setInterval(expandSessionWhenVisible, VISIBILITY_CHECK_DELAY) + document.addEventListener('visibilitychange', expandSessionWhenVisible) + + registeredIntervals.push(visibilityCheckInterval) + registeredListeners.push(() => document.removeEventListener('visibilitychange', expandSessionWhenVisible)) +} diff --git a/packages/core/test/sessionManagement.spec.ts b/packages/core/test/sessionManagement.spec.ts index d2c0972332..2531789503 100644 --- a/packages/core/test/sessionManagement.spec.ts +++ b/packages/core/test/sessionManagement.spec.ts @@ -1,3 +1,4 @@ +import { ONE_HOUR } from '../src' import { cacheCookieAccess, COOKIE_ACCESS_DELAY, CookieCache, getCookie, setCookie } from '../src/cookie' import { Session, @@ -6,6 +7,7 @@ import { SESSION_TIME_OUT_DELAY, startSessionManagement, stopSessionManagement, + VISIBILITY_CHECK_DELAY, } from '../src/sessionManagement' import { isIE } from '../src/specHelper' @@ -91,10 +93,11 @@ describe('startSessionManagement', () => { }) afterEach(() => { + // remove intervals first + stopSessionManagement() // flush pending callbacks to avoid random failures - jasmine.clock().tick(new Date().getTime()) + jasmine.clock().tick(ONE_HOUR) jasmine.clock().uninstall() - stopSessionManagement() }) describe('cookie management', () => { @@ -314,7 +317,8 @@ describe('startSessionManagement', () => { isTracked: true, type: FakeSessionType.TRACKED, }), - true + true, + () => 'hidden' ) expectSessionIdToBeDefined(session) @@ -329,7 +333,8 @@ describe('startSessionManagement', () => { isTracked: true, type: FakeSessionType.TRACKED, }), - true + true, + () => 'hidden' ) expectSessionIdToBeDefined(session) @@ -342,5 +347,29 @@ describe('startSessionManagement', () => { jasmine.clock().tick(SESSION_EXPIRATION_DELAY) expectSessionIdToNotBeDefined(session) }) + + it('should expand session on visibility', () => { + let visibility: VisibilityState = 'visible' + + const session = startSessionManagement( + FIRST_SESSION_TYPE_KEY, + () => ({ + isTracked: true, + type: FakeSessionType.TRACKED, + }), + true, + () => visibility + ) + + jasmine.clock().tick(3 * VISIBILITY_CHECK_DELAY) + visibility = 'hidden' + expectSessionIdToBeDefined(session) + + jasmine.clock().tick(SESSION_EXPIRATION_DELAY - 10) + expectSessionIdToBeDefined(session) + + jasmine.clock().tick(10) + expectSessionIdToNotBeDefined(session) + }) }) }) From 3ac7581ede6f72a20ecd761613ea9a6f7b659d78 Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Tue, 7 Apr 2020 10:58:02 +0200 Subject: [PATCH 05/13] =?UTF-8?q?=F0=9F=9A=A9=20add=20'new-session'=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/logs/src/loggerSession.ts | 6 +++++- packages/logs/test/loggerSession.spec.ts | 7 ++++--- packages/rum/src/rumSession.ts | 6 +++++- packages/rum/test/rumSession.spec.ts | 6 ++++-- test/static/bundle-e2e-page.html | 1 + 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/logs/src/loggerSession.ts b/packages/logs/src/loggerSession.ts index f6401aa272..9f2091cdb2 100644 --- a/packages/logs/src/loggerSession.ts +++ b/packages/logs/src/loggerSession.ts @@ -20,7 +20,11 @@ export function startLoggerSession(configuration: Configuration, areCookieAuthor isTracked: () => isTracked, } } - const session = startSessionManagement(LOGGER_SESSION_KEY, (rawType) => computeSessionState(configuration, rawType)) + const session = startSessionManagement( + LOGGER_SESSION_KEY, + (rawType) => computeSessionState(configuration, rawType), + configuration.isEnabled('new-session') + ) return { getId: session.getId, isTracked: () => session.getType() === LoggerSessionType.TRACKED, diff --git a/packages/logs/test/loggerSession.spec.ts b/packages/logs/test/loggerSession.spec.ts index 58d5303171..d8ded11cd1 100644 --- a/packages/logs/test/loggerSession.spec.ts +++ b/packages/logs/test/loggerSession.spec.ts @@ -11,7 +11,7 @@ import { LOGGER_SESSION_KEY, LoggerSessionType, startLoggerSession } from '../sr describe('logger session', () => { const DURATION = 123456 - const configuration: Partial = { sampleRate: 0.5 } + const configuration: Partial = { isEnabled: () => true, sampleRate: 0.5 } let tracked = true beforeEach(() => { @@ -21,10 +21,11 @@ describe('logger session', () => { }) afterEach(() => { + // remove intervals first + stopSessionManagement() // flush pending callbacks to avoid random failures jasmine.clock().tick(new Date().getTime()) jasmine.clock().uninstall() - stopSessionManagement() }) it('when tracked should store session type and id', () => { @@ -59,7 +60,7 @@ describe('logger session', () => { startLoggerSession(configuration as Configuration, true) - expect(getCookie(SESSION_COOKIE_NAME)).toBe(`${LOGGER_SESSION_KEY}=${LoggerSessionType.NOT_TRACKED}`) + expect(getCookie(SESSION_COOKIE_NAME)).toContain(`${LOGGER_SESSION_KEY}=${LoggerSessionType.NOT_TRACKED}`) }) it('should renew on activity after expiration', () => { diff --git a/packages/rum/src/rumSession.ts b/packages/rum/src/rumSession.ts index 6861258b22..c342edca75 100644 --- a/packages/rum/src/rumSession.ts +++ b/packages/rum/src/rumSession.ts @@ -16,7 +16,11 @@ export enum RumSessionType { } export function startRumSession(configuration: Configuration, lifeCycle: LifeCycle): RumSession { - const session = startSessionManagement(RUM_SESSION_KEY, (rawType) => computeSessionState(configuration, rawType)) + const session = startSessionManagement( + RUM_SESSION_KEY, + (rawType) => computeSessionState(configuration, rawType), + configuration.isEnabled('new-session') + ) session.renewObservable.subscribe(() => { lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) diff --git a/packages/rum/test/rumSession.spec.ts b/packages/rum/test/rumSession.spec.ts index b32b01984e..47d3a9a267 100644 --- a/packages/rum/test/rumSession.spec.ts +++ b/packages/rum/test/rumSession.spec.ts @@ -20,6 +20,7 @@ describe('rum session', () => { const DURATION = 123456 const configuration: Partial = { ...DEFAULT_CONFIGURATION, + isEnabled: () => true, resourceSampleRate: 0.5, sampleRate: 0.5, } @@ -38,10 +39,11 @@ describe('rum session', () => { }) afterEach(() => { + // remove intervals first + stopSessionManagement() // flush pending callbacks to avoid random failures jasmine.clock().tick(new Date().getTime()) jasmine.clock().uninstall() - stopSessionManagement() }) it('when tracked with resources should store session type and id', () => { @@ -90,7 +92,7 @@ describe('rum session', () => { startRumSession(configuration as Configuration, lifeCycle) expect(renewSessionSpy).not.toHaveBeenCalled() - expect(getCookie(SESSION_COOKIE_NAME)).toBe(`${RUM_SESSION_KEY}=${RumSessionType.NOT_TRACKED}`) + expect(getCookie(SESSION_COOKIE_NAME)).toContain(`${RUM_SESSION_KEY}=${RumSessionType.NOT_TRACKED}`) }) it('should renew on activity after expiration', () => { diff --git a/test/static/bundle-e2e-page.html b/test/static/bundle-e2e-page.html index 81b1c85495..c1683c65fc 100644 --- a/test/static/bundle-e2e-page.html +++ b/test/static/bundle-e2e-page.html @@ -16,6 +16,7 @@ logsEndpoint: `${intakeOrigin}/logs`, rumEndpoint: `${intakeOrigin}/rum`, forwardErrorsToLogs: true, + enableExperimentalFeatures: ['new-session'], }) From c07170ff65e1283a85380eaa2ed6a7e05a619f13 Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Tue, 7 Apr 2020 15:20:32 +0200 Subject: [PATCH 06/13] =?UTF-8?q?=F0=9F=91=8C=20extract=20DOM=5FEVENT=20en?= =?UTF-8?q?um?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add missing monitor --- packages/core/src/sessionManagement.ts | 16 ++++++++++------ packages/core/src/transport.ts | 8 ++++---- packages/core/src/utils.ts | 12 +++++++++++- packages/rum/src/performanceCollection.ts | 10 +++++----- packages/rum/src/viewTracker.ts | 11 +++++++---- 5 files changed, 37 insertions(+), 20 deletions(-) diff --git a/packages/core/src/sessionManagement.ts b/packages/core/src/sessionManagement.ts index 677c236af2..f1de3befee 100644 --- a/packages/core/src/sessionManagement.ts +++ b/packages/core/src/sessionManagement.ts @@ -147,10 +147,12 @@ let registeredIntervals: number[] = [] export function trackActivity(expandOrRenewSession: () => void) { const doExpandOrRenewSession = monitor(expandOrRenewSession) const options = { capture: true, passive: true } - ;['click', 'touchstart', 'keydown', 'scroll'].forEach((event: string) => { - document.addEventListener(event, doExpandOrRenewSession, options) - registeredListeners.push(() => document.removeEventListener(event, doExpandOrRenewSession, options)) - }) + ;[utils.DOM_EVENT.CLICK, utils.DOM_EVENT.TOUCH_START, utils.DOM_EVENT.KEY_DOWN, utils.DOM_EVENT.SCROLL].forEach( + (event: string) => { + document.addEventListener(event, doExpandOrRenewSession, options) + registeredListeners.push(() => document.removeEventListener(event, doExpandOrRenewSession, options)) + } + ) } function trackVisibility(expandSession: () => void, visibilityStateProvider: () => VisibilityState) { @@ -161,8 +163,10 @@ function trackVisibility(expandSession: () => void, visibilityStateProvider: () }) const visibilityCheckInterval = window.setInterval(expandSessionWhenVisible, VISIBILITY_CHECK_DELAY) - document.addEventListener('visibilitychange', expandSessionWhenVisible) + document.addEventListener(utils.DOM_EVENT.VISIBILITY_CHANGE, expandSessionWhenVisible) registeredIntervals.push(visibilityCheckInterval) - registeredListeners.push(() => document.removeEventListener('visibilitychange', expandSessionWhenVisible)) + registeredListeners.push(() => + document.removeEventListener(utils.DOM_EVENT.VISIBILITY_CHANGE, expandSessionWhenVisible) + ) } diff --git a/packages/core/src/transport.ts b/packages/core/src/transport.ts index db912258fb..45cd0ceae7 100644 --- a/packages/core/src/transport.ts +++ b/packages/core/src/transport.ts @@ -1,7 +1,7 @@ import lodashMerge from 'lodash.merge' import { monitor } from './internalMonitoring' -import { Context, jsonStringify, objectValues } from './utils' +import { Context, DOM_EVENT, jsonStringify, objectValues } from './utils' /** * Use POST request without content type to: @@ -161,7 +161,7 @@ export class Batch { * caveat: unload can still be canceled by another listener */ window.addEventListener( - 'beforeunload', + DOM_EVENT.BEFORE_UNLOAD, monitor(() => { this.beforeFlushOnUnloadHandlers.forEach((handler) => handler()) }) @@ -172,7 +172,7 @@ export class Batch { * (e.g. when user switches to a different application, goes to homescreen, etc), or is being unloaded. */ document.addEventListener( - 'visibilitychange', + DOM_EVENT.VISIBILITY_CHANGE, monitor(() => { if (document.visibilityState === 'hidden') { this.flush() @@ -184,7 +184,7 @@ export class Batch { * - a visibility change during doc unload (cf: https://bugs.webkit.org/show_bug.cgi?id=194897) * - a page hide transition (cf: https://bugs.webkit.org/show_bug.cgi?id=188329) */ - window.addEventListener('beforeunload', monitor(() => this.flush())) + window.addEventListener(DOM_EVENT.BEFORE_UNLOAD, monitor(() => this.flush())) } } } diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 43a935447f..f8551017da 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -3,9 +3,19 @@ export type Omit = Pick> export const ONE_SECOND = 1000 export const ONE_MINUTE = 60 * ONE_SECOND export const ONE_HOUR = 60 * ONE_MINUTE -export const ONE_DAY = 24 * ONE_HOUR export const ONE_KILO_BYTE = 1024 +export enum DOM_EVENT { + BEFORE_UNLOAD = 'beforeunload', + CLICK = 'click', + KEY_DOWN = 'keydown', + LOAD = 'load', + POP_STATE = 'popstate', + SCROLL = 'scroll', + TOUCH_START = 'touchstart', + VISIBILITY_CHANGE = 'visibilitychange', +} + export enum ResourceKind { DOCUMENT = 'document', XHR = 'xhr', diff --git a/packages/rum/src/performanceCollection.ts b/packages/rum/src/performanceCollection.ts index 85eba5e637..b300cc57d2 100644 --- a/packages/rum/src/performanceCollection.ts +++ b/packages/rum/src/performanceCollection.ts @@ -1,4 +1,4 @@ -import { getRelativeTime, isNumber, monitor } from '@datadog/browser-core' +import { DOM_EVENT, getRelativeTime, isNumber, monitor } from '@datadog/browser-core' import { LifeCycle, LifeCycleEventType } from './lifeCycle' import { FAKE_INITIAL_DOCUMENT } from './resourceUtils' @@ -99,13 +99,13 @@ function retrieveNavigationTimingWhenLoaded(callback: (timing: PerformanceNaviga if (document.readyState === 'complete') { sendFakeTiming() } else { - const listener = () => { - window.removeEventListener('load', listener) + const listener = monitor(() => { + window.removeEventListener(DOM_EVENT.LOAD, listener) // Send it a bit after the actual load event, so the "loadEventEnd" timing is accurate setTimeout(monitor(sendFakeTiming)) - } + }) - window.addEventListener('load', listener) + window.addEventListener(DOM_EVENT.LOAD, listener) } } diff --git a/packages/rum/src/viewTracker.ts b/packages/rum/src/viewTracker.ts index 4ab7a08ac3..9aa4543f93 100644 --- a/packages/rum/src/viewTracker.ts +++ b/packages/rum/src/viewTracker.ts @@ -1,4 +1,4 @@ -import { generateUUID, getTimestamp, monitor, msToNs, throttle } from '@datadog/browser-core' +import { DOM_EVENT, generateUUID, getTimestamp, monitor, msToNs, throttle } from '@datadog/browser-core' import { LifeCycle, LifeCycleEventType } from './lifeCycle' import { PerformancePaintTiming, RumEvent, RumEventCategory } from './rum' @@ -100,9 +100,12 @@ function trackHistory(location: Location, session: RumSession, upsertRumEvent: ( originalReplaceState.apply(this, arguments as any) onUrlChange(location, session, upsertRumEvent) }) - window.addEventListener('popstate', () => { - onUrlChange(location, session, upsertRumEvent) - }) + window.addEventListener( + DOM_EVENT.POP_STATE, + monitor(() => { + onUrlChange(location, session, upsertRumEvent) + }) + ) } function onUrlChange(location: Location, session: RumSession, upsertRumEvent: (event: RumEvent, key: string) => void) { From 88260f9b0df628bd4276c80cc51ac60767ff2ef7 Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Wed, 8 Apr 2020 10:59:07 +0200 Subject: [PATCH 07/13] =?UTF-8?q?=F0=9F=91=8C=20make=20clear=20session=20m?= =?UTF-8?q?ore=20explicit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/sessionManagement.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/src/sessionManagement.ts b/packages/core/src/sessionManagement.ts index f1de3befee..e62b9f50d0 100644 --- a/packages/core/src/sessionManagement.ts +++ b/packages/core/src/sessionManagement.ts @@ -94,6 +94,7 @@ function retrieveActiveSession(sessionCookie: CookieCache, withNewSessionStrateg if (!withNewSessionStrategy || isActiveSession(session)) { return session } + // clear session const inactiveSession = {} persistSession(inactiveSession, sessionCookie, withNewSessionStrategy) return inactiveSession @@ -124,7 +125,12 @@ function retrieveSession(sessionCookie: CookieCache): SessionState { } export function persistSession(session: SessionState, cookie: CookieCache, withNewSessionStrategy = false) { - if (withNewSessionStrategy && !utils.isEmptyObject(session)) { + if (utils.isEmptyObject(session)) { + // clear session + cookie.set('', 0) + return + } + if (withNewSessionStrategy) { session.expire = String(Date.now() + SESSION_EXPIRATION_DELAY) } const cookieString = utils From a0969e74bbc893ebea39d4e0fa96919dbfb18dd7 Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Wed, 8 Apr 2020 11:38:41 +0200 Subject: [PATCH 08/13] =?UTF-8?q?=F0=9F=91=8C=20merge=20remove=20listener?= =?UTF-8?q?=20and=20stop=20interval=20callbacks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/sessionManagement.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/core/src/sessionManagement.ts b/packages/core/src/sessionManagement.ts index e62b9f50d0..ec23a89602 100644 --- a/packages/core/src/sessionManagement.ts +++ b/packages/core/src/sessionManagement.ts @@ -141,14 +141,11 @@ export function persistSession(session: SessionState, cookie: CookieCache, withN } export function stopSessionManagement() { - registeredListeners.forEach((e) => e()) - registeredIntervals.forEach((interval) => clearInterval(interval)) - registeredListeners = [] - registeredIntervals = [] + stopCallbacks.forEach((e) => e()) + stopCallbacks = [] } -let registeredListeners: Array<() => void> = [] -let registeredIntervals: number[] = [] +let stopCallbacks: Array<() => void> = [] export function trackActivity(expandOrRenewSession: () => void) { const doExpandOrRenewSession = monitor(expandOrRenewSession) @@ -156,7 +153,7 @@ export function trackActivity(expandOrRenewSession: () => void) { ;[utils.DOM_EVENT.CLICK, utils.DOM_EVENT.TOUCH_START, utils.DOM_EVENT.KEY_DOWN, utils.DOM_EVENT.SCROLL].forEach( (event: string) => { document.addEventListener(event, doExpandOrRenewSession, options) - registeredListeners.push(() => document.removeEventListener(event, doExpandOrRenewSession, options)) + stopCallbacks.push(() => document.removeEventListener(event, doExpandOrRenewSession, options)) } ) } @@ -171,8 +168,8 @@ function trackVisibility(expandSession: () => void, visibilityStateProvider: () const visibilityCheckInterval = window.setInterval(expandSessionWhenVisible, VISIBILITY_CHECK_DELAY) document.addEventListener(utils.DOM_EVENT.VISIBILITY_CHANGE, expandSessionWhenVisible) - registeredIntervals.push(visibilityCheckInterval) - registeredListeners.push(() => + stopCallbacks.push(() => { + clearInterval(visibilityCheckInterval) document.removeEventListener(utils.DOM_EVENT.VISIBILITY_CHANGE, expandSessionWhenVisible) - ) + }) } From f96e1cd5b4b654fb260378e2db1c9a1b6b677197 Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Wed, 8 Apr 2020 12:02:07 +0200 Subject: [PATCH 09/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20use=20DOM=5FEVENT=20?= =?UTF-8?q?for=20user=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/rum/src/userActionCollection.ts | 6 +++--- packages/rum/test/userActionCollection.spec.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/rum/src/userActionCollection.ts b/packages/rum/src/userActionCollection.ts index 37d922a5c1..20d438ad3a 100644 --- a/packages/rum/src/userActionCollection.ts +++ b/packages/rum/src/userActionCollection.ts @@ -1,4 +1,4 @@ -import { generateUUID, monitor, Observable } from '@datadog/browser-core' +import { DOM_EVENT, generateUUID, monitor, Observable } from '@datadog/browser-core' import { getElementContent } from './getElementContent' import { LifeCycle, LifeCycleEventType, Subscription } from './lifeCycle' import { UserActionType } from './rum' @@ -60,11 +60,11 @@ export function startUserActionCollection(lifeCycle: LifeCycle) { }) } - addEventListener('click', processClick, { capture: true }) + addEventListener(DOM_EVENT.CLICK, processClick, { capture: true }) return { stop() { - removeEventListener('click', processClick, { capture: true }) + removeEventListener(DOM_EVENT.CLICK, processClick, { capture: true }) }, } } diff --git a/packages/rum/test/userActionCollection.spec.ts b/packages/rum/test/userActionCollection.spec.ts index be7f71bd54..4e030fd79b 100644 --- a/packages/rum/test/userActionCollection.spec.ts +++ b/packages/rum/test/userActionCollection.spec.ts @@ -1,4 +1,4 @@ -import { Observable, RequestCompleteEvent } from '@datadog/browser-core' +import { DOM_EVENT, Observable, RequestCompleteEvent } from '@datadog/browser-core' import { LifeCycle, LifeCycleEventType } from '../src/lifeCycle' import { UserActionType } from '../src/rum' import { @@ -334,7 +334,7 @@ describe('startUserActionCollection', () => { }) it('starts a user action when clicking on an element', () => { - button.addEventListener('click', () => { + button.addEventListener(DOM_EVENT.CLICK, () => { clock.tick(50) // Since we don't collect dom mutations for this test, manually dispatch one lifeCycle.notify(LifeCycleEventType.DOM_MUTATED) From b6c783e169c4f2e9956878eb758d6309c42e57f3 Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Wed, 8 Apr 2020 12:06:27 +0200 Subject: [PATCH 10/13] =?UTF-8?q?=F0=9F=8F=B7=20fix=20spec=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/test/requestCollection.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/test/requestCollection.spec.ts b/packages/core/test/requestCollection.spec.ts index d324ecd55f..2b5f5394fd 100644 --- a/packages/core/test/requestCollection.spec.ts +++ b/packages/core/test/requestCollection.spec.ts @@ -9,7 +9,7 @@ import { trackFetch, trackXhr, } from '../src/requestCollection' -import { FetchStub, FetchStubBuilder, FetchStubPromise, isFirefox, isIE } from '../src/specHelper' +import { FetchStub, FetchStubBuilder, FetchStubPromise, isIE } from '../src/specHelper' import { find, includes } from '../src/utils' describe('fetch tracker', () => { @@ -211,7 +211,7 @@ describe('xhr tracker', () => { expectedMethod: string | jasmine.Any expectedStatus: number | jasmine.Any expectedURL: string - expectedResponse?: string | jasmine.Any + expectedResponse?: string | jasmine.AsymmetricMatcher expectXHR?: (xhr: XMLHttpRequest) => void }) { const xhr = new XMLHttpRequest() From 3b732ab546036ee2001e76cfd2efeedac2a3cb69 Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Wed, 8 Apr 2020 14:14:11 +0200 Subject: [PATCH 11/13] =?UTF-8?q?=F0=9F=91=8C=20extract=20clearSession?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/sessionManagement.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/core/src/sessionManagement.ts b/packages/core/src/sessionManagement.ts index ec23a89602..e0226d48c2 100644 --- a/packages/core/src/sessionManagement.ts +++ b/packages/core/src/sessionManagement.ts @@ -126,8 +126,7 @@ function retrieveSession(sessionCookie: CookieCache): SessionState { export function persistSession(session: SessionState, cookie: CookieCache, withNewSessionStrategy = false) { if (utils.isEmptyObject(session)) { - // clear session - cookie.set('', 0) + clearSession(cookie) return } if (withNewSessionStrategy) { @@ -140,6 +139,10 @@ export function persistSession(session: SessionState, cookie: CookieCache, withN cookie.set(cookieString, SESSION_EXPIRATION_DELAY) } +function clearSession(cookie: CookieCache) { + cookie.set('', 0) +} + export function stopSessionManagement() { stopCallbacks.forEach((e) => e()) stopCallbacks = [] From b3e79c823ec7bfafcec65acdc266bc8c669c4f9a Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Wed, 8 Apr 2020 14:33:16 +0200 Subject: [PATCH 12/13] =?UTF-8?q?=F0=9F=91=8C=20mock=20page=20visibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/sessionManagement.ts | 9 +++--- packages/core/test/sessionManagement.spec.ts | 34 +++++++++++++++----- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/packages/core/src/sessionManagement.ts b/packages/core/src/sessionManagement.ts index e0226d48c2..f3bc11994c 100644 --- a/packages/core/src/sessionManagement.ts +++ b/packages/core/src/sessionManagement.ts @@ -28,8 +28,7 @@ export interface SessionState { export function startSessionManagement( sessionTypeKey: string, computeSessionState: (rawType?: string) => { type: Type; isTracked: boolean }, - withNewSessionStrategy = false, - visibilityStateProvider = () => document.visibilityState + withNewSessionStrategy = false ): Session { const sessionCookie = cacheCookieAccess(SESSION_COOKIE_NAME) tryOldCookiesMigration(sessionCookie) @@ -64,7 +63,7 @@ export function startSessionManagement( expandOrRenewSession() trackActivity(expandOrRenewSession) if (withNewSessionStrategy) { - trackVisibility(expandSession, visibilityStateProvider) + trackVisibility(expandSession) } return { @@ -161,9 +160,9 @@ export function trackActivity(expandOrRenewSession: () => void) { ) } -function trackVisibility(expandSession: () => void, visibilityStateProvider: () => VisibilityState) { +function trackVisibility(expandSession: () => void) { const expandSessionWhenVisible = monitor(() => { - if (visibilityStateProvider() === 'visible') { + if (document.visibilityState === 'visible') { expandSession() } }) diff --git a/packages/core/test/sessionManagement.spec.ts b/packages/core/test/sessionManagement.spec.ts index 2531789503..e0fe3dc06f 100644 --- a/packages/core/test/sessionManagement.spec.ts +++ b/packages/core/test/sessionManagement.spec.ts @@ -310,6 +310,27 @@ describe('startSessionManagement', () => { }) describe('session expiration', () => { + function setPageVisibility(visibility: 'visible' | 'hidden') { + Object.defineProperty(document, 'visibilityState', { + get() { + return visibility + }, + configurable: true, + }) + } + + function restorePageVisibility() { + delete (document as any).visibilityState + } + + beforeEach(() => { + setPageVisibility('hidden') + }) + + afterEach(() => { + restorePageVisibility() + }) + it('should expire the session after expiration delay', () => { const session = startSessionManagement( FIRST_SESSION_TYPE_KEY, @@ -317,8 +338,7 @@ describe('startSessionManagement', () => { isTracked: true, type: FakeSessionType.TRACKED, }), - true, - () => 'hidden' + true ) expectSessionIdToBeDefined(session) @@ -333,8 +353,7 @@ describe('startSessionManagement', () => { isTracked: true, type: FakeSessionType.TRACKED, }), - true, - () => 'hidden' + true ) expectSessionIdToBeDefined(session) @@ -349,7 +368,7 @@ describe('startSessionManagement', () => { }) it('should expand session on visibility', () => { - let visibility: VisibilityState = 'visible' + setPageVisibility('visible') const session = startSessionManagement( FIRST_SESSION_TYPE_KEY, @@ -357,12 +376,11 @@ describe('startSessionManagement', () => { isTracked: true, type: FakeSessionType.TRACKED, }), - true, - () => visibility + true ) jasmine.clock().tick(3 * VISIBILITY_CHECK_DELAY) - visibility = 'hidden' + setPageVisibility('hidden') expectSessionIdToBeDefined(session) jasmine.clock().tick(SESSION_EXPIRATION_DELAY - 10) From c5bb1e7c274e9633ef3bf92986af5a54256783f9 Mon Sep 17 00:00:00 2001 From: Bastien Caudan Date: Wed, 8 Apr 2020 15:45:13 +0200 Subject: [PATCH 13/13] =?UTF-8?q?=F0=9F=91=8C=20use=20clear=20session?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/sessionManagement.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/core/src/sessionManagement.ts b/packages/core/src/sessionManagement.ts index f3bc11994c..9ce77de2c5 100644 --- a/packages/core/src/sessionManagement.ts +++ b/packages/core/src/sessionManagement.ts @@ -93,10 +93,8 @@ function retrieveActiveSession(sessionCookie: CookieCache, withNewSessionStrateg if (!withNewSessionStrategy || isActiveSession(session)) { return session } - // clear session - const inactiveSession = {} - persistSession(inactiveSession, sessionCookie, withNewSessionStrategy) - return inactiveSession + clearSession(sessionCookie) + return {} } function isActiveSession(session: SessionState) {