Skip to content

Commit

Permalink
♻️ [RUMF-1083] extract session store from session management (#1166)
Browse files Browse the repository at this point in the history
* 🔥 remove dead code

* ♻️ extract session directory

* ♻️ make old cookie migrations handle its own cookie access

* ♻️ extract session store from session management

* 👌 minor cleanup
  • Loading branch information
bcaudan authored Nov 17, 2021
1 parent 807dbae commit 9583f46
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 90 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { cacheCookieAccess, CookieOptions, getCookie, setCookie } from '../browser/cookie'
import { CookieOptions, getCookie, setCookie } from '../../browser/cookie'
import {
OLD_LOGS_COOKIE_NAME,
OLD_RUM_COOKIE_NAME,
OLD_SESSION_COOKIE_NAME,
tryOldCookiesMigration,
} from './oldCookiesMigration'
import { SESSION_COOKIE_NAME, SESSION_EXPIRATION_DELAY } from './sessionManagement'
import { SESSION_COOKIE_NAME, SESSION_EXPIRATION_DELAY } from './sessionStore'

describe('old cookies migration', () => {
const options: CookieOptions = {}

it('should not touch current cookie', () => {
setCookie(SESSION_COOKIE_NAME, 'id=abcde&rum=0&logs=1', SESSION_EXPIRATION_DELAY)

tryOldCookiesMigration(cacheCookieAccess(SESSION_COOKIE_NAME, options))
tryOldCookiesMigration(options)

expect(getCookie(SESSION_COOKIE_NAME)).toBe('id=abcde&rum=0&logs=1')
})
Expand All @@ -23,7 +23,7 @@ describe('old cookies migration', () => {
setCookie(OLD_LOGS_COOKIE_NAME, '1', SESSION_EXPIRATION_DELAY)
setCookie(OLD_RUM_COOKIE_NAME, '0', SESSION_EXPIRATION_DELAY)

tryOldCookiesMigration(cacheCookieAccess(SESSION_COOKIE_NAME, options))
tryOldCookiesMigration(options)

expect(getCookie(SESSION_COOKIE_NAME)).toContain('id=abcde')
expect(getCookie(SESSION_COOKIE_NAME)).toContain('rum=0')
Expand All @@ -33,7 +33,7 @@ describe('old cookies migration', () => {
it('should create new cookie from a single old cookie', () => {
setCookie(OLD_RUM_COOKIE_NAME, '0', SESSION_EXPIRATION_DELAY)

tryOldCookiesMigration(cacheCookieAccess(SESSION_COOKIE_NAME, options))
tryOldCookiesMigration(options)

expect(getCookie(SESSION_COOKIE_NAME)).not.toContain('id=')
expect(getCookie(SESSION_COOKIE_NAME)).toContain('rum=0')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CookieCache, getCookie } from '../browser/cookie'
import { persistSession, SessionState } from './sessionManagement'
import { getCookie, CookieOptions, cacheCookieAccess } from '../../browser/cookie'
import { persistSession, SessionState, SESSION_COOKIE_NAME } from './sessionStore'

export const OLD_SESSION_COOKIE_NAME = '_dd'
export const OLD_RUM_COOKIE_NAME = '_dd_r'
Expand All @@ -13,7 +13,8 @@ export const LOGS_SESSION_KEY = 'logs'
* This migration should remain in the codebase as long as older versions are available/live
* to allow older sdk versions to be upgraded to newer versions without compatibility issues.
*/
export function tryOldCookiesMigration(sessionCookie: CookieCache) {
export function tryOldCookiesMigration(options: CookieOptions) {
const sessionCookie = cacheCookieAccess(SESSION_COOKIE_NAME, options)
const sessionString = sessionCookie.get()
const oldSessionId = getCookie(OLD_SESSION_COOKIE_NAME)
const oldRumType = getCookie(OLD_RUM_COOKIE_NAME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,12 @@ import {
CookieOptions,
getCookie,
setCookie,
} from '../browser/cookie'
import { Clock, mockClock, restorePageVisibility, setPageVisibility, createNewEvent } from '../../test/specHelper'
import { ONE_HOUR, DOM_EVENT } from '../tools/utils'
import { isIE } from '../tools/browserDetection'
import {
Session,
SESSION_COOKIE_NAME,
SESSION_EXPIRATION_DELAY,
SESSION_TIME_OUT_DELAY,
startSessionManagement,
stopSessionManagement,
VISIBILITY_CHECK_DELAY,
} from './sessionManagement'
} from '../../browser/cookie'
import { Clock, mockClock, restorePageVisibility, setPageVisibility, createNewEvent } from '../../../test/specHelper'
import { ONE_HOUR, DOM_EVENT } from '../../tools/utils'
import { isIE } from '../../tools/browserDetection'
import { Session, startSessionManagement, stopSessionManagement, VISIBILITY_CHECK_DELAY } from './sessionManagement'
import { SESSION_COOKIE_NAME, SESSION_TIME_OUT_DELAY, SESSION_EXPIRATION_DELAY } from './sessionStore'

describe('cacheCookieAccess', () => {
const TEST_COOKIE = 'test'
Expand Down
66 changes: 66 additions & 0 deletions packages/core/src/domain/session/sessionManagement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { CookieOptions } from '../../browser/cookie'
import { Observable } from '../../tools/observable'
import * as utils from '../../tools/utils'
import { monitor } from '../internalMonitoring'
import { tryOldCookiesMigration } from './oldCookiesMigration'
import { startSessionStore } from './sessionStore'

export const VISIBILITY_CHECK_DELAY = utils.ONE_MINUTE

export interface Session<T> {
renewObservable: Observable<void>
getId: () => string | undefined
getTrackingType: () => T | undefined
}

export function startSessionManagement<TrackingType extends string>(
options: CookieOptions,
productKey: string,
computeSessionState: (rawTrackingType?: string) => { trackingType: TrackingType; isTracked: boolean }
): Session<TrackingType> {
tryOldCookiesMigration(options)
const sessionStore = startSessionStore(options, productKey, computeSessionState)

sessionStore.expandOrRenewSession()
trackActivity(() => sessionStore.expandOrRenewSession())
trackVisibility(() => sessionStore.expandSession())

return {
getId: () => sessionStore.retrieveSession().id,
getTrackingType: () => sessionStore.retrieveSession()[productKey] as TrackingType | undefined,
renewObservable: sessionStore.renewObservable,
}
}

export function stopSessionManagement() {
stopCallbacks.forEach((e) => e())
stopCallbacks = []
}

let stopCallbacks: Array<() => void> = []

function trackActivity(expandOrRenewSession: () => void) {
const { stop } = utils.addEventListeners(
window,
[utils.DOM_EVENT.CLICK, utils.DOM_EVENT.TOUCH_START, utils.DOM_EVENT.KEY_DOWN, utils.DOM_EVENT.SCROLL],
expandOrRenewSession,
{ capture: true, passive: true }
)
stopCallbacks.push(stop)
}

function trackVisibility(expandSession: () => void) {
const expandSessionWhenVisible = monitor(() => {
if (document.visibilityState === 'visible') {
expandSession()
}
})

const { stop } = utils.addEventListener(document, utils.DOM_EVENT.VISIBILITY_CHANGE, expandSessionWhenVisible)
stopCallbacks.push(stop)

const visibilityCheckInterval = setInterval(expandSessionWhenVisible, VISIBILITY_CHECK_DELAY)
stopCallbacks.push(() => {
clearInterval(visibilityCheckInterval)
})
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,37 @@
import { cacheCookieAccess, COOKIE_ACCESS_DELAY, CookieCache, CookieOptions } from '../browser/cookie'
import { Observable } from '../tools/observable'
import * as utils from '../tools/utils'
import { monitor } from './internalMonitoring'
import { tryOldCookiesMigration } from './oldCookiesMigration'

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<T> {
import { CookieCache, CookieOptions, cacheCookieAccess, COOKIE_ACCESS_DELAY } from '../../browser/cookie'
import { Observable } from '../../tools/observable'
import * as utils from '../../tools/utils'
import { monitor } from '../internalMonitoring'

export interface SessionStore {
expandOrRenewSession: () => void
expandSession: () => void
retrieveSession: () => SessionState
renewObservable: Observable<void>
getId: () => string | undefined
getTrackingType: () => T | undefined
getInMemoryTrackingType: () => T | undefined
}

export interface SessionState {
id?: string
created?: string
expire?: string

[key: string]: string | undefined
}

/**
* Limit access to cookie to avoid performance issues
*/
export function startSessionManagement<TrackingType extends string>(
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

const SESSION_ENTRY_REGEXP = /^([a-z]+)=([a-z0-9-]+)$/
const SESSION_ENTRY_SEPARATOR = '&'

export function startSessionStore<TrackingType extends string>(
options: CookieOptions,
productKey: string,
computeSessionState: (rawTrackingType?: string) => { trackingType: TrackingType; isTracked: boolean }
): Session<TrackingType> {
const sessionCookie = cacheCookieAccess(SESSION_COOKIE_NAME, options)
tryOldCookiesMigration(sessionCookie)
): SessionStore {
const renewObservable = new Observable<void>()
const sessionCookie = cacheCookieAccess(SESSION_COOKIE_NAME, options)
let inMemorySession = retrieveActiveSession(sessionCookie)

const { throttled: expandOrRenewSession } = utils.throttle(
Expand All @@ -59,29 +57,25 @@ export function startSessionManagement<TrackingType extends string>(
COOKIE_ACCESS_DELAY
)

const expandSession = () => {
function expandSession() {
sessionCookie.clearCache()
const session = retrieveActiveSession(sessionCookie)
persistSession(session, sessionCookie)
}

expandOrRenewSession()
trackActivity(expandOrRenewSession)
trackVisibility(expandSession)
function retrieveSession() {
return retrieveActiveSession(sessionCookie)
}

return {
getId: () => retrieveActiveSession(sessionCookie).id,
getTrackingType: () => retrieveActiveSession(sessionCookie)[productKey] as TrackingType | undefined,
getInMemoryTrackingType: () => inMemorySession[productKey] as TrackingType | undefined,
expandOrRenewSession,
expandSession,
retrieveSession,
renewObservable,
}
}

const SESSION_ENTRY_REGEXP = /^([a-z]+)=([a-z0-9-]+)$/

const SESSION_ENTRY_SEPARATOR = '&'

export function isValidSessionString(sessionString: string | undefined): sessionString is string {
function isValidSessionString(sessionString: string | undefined): sessionString is string {
return (
sessionString !== undefined &&
(sessionString.indexOf(SESSION_ENTRY_SEPARATOR) !== -1 || SESSION_ENTRY_REGEXP.test(sessionString))
Expand Down Expand Up @@ -137,36 +131,3 @@ export function persistSession(session: SessionState, cookie: CookieCache) {
function clearSession(cookie: CookieCache) {
cookie.set('', 0)
}

export function stopSessionManagement() {
stopCallbacks.forEach((e) => e())
stopCallbacks = []
}

let stopCallbacks: Array<() => void> = []

export function trackActivity(expandOrRenewSession: () => void) {
const { stop } = utils.addEventListeners(
window,
[utils.DOM_EVENT.CLICK, utils.DOM_EVENT.TOUCH_START, utils.DOM_EVENT.KEY_DOWN, utils.DOM_EVENT.SCROLL],
expandOrRenewSession,
{ capture: true, passive: true }
)
stopCallbacks.push(stop)
}

function trackVisibility(expandSession: () => void) {
const expandSessionWhenVisible = monitor(() => {
if (document.visibilityState === 'visible') {
expandSession()
}
})

const { stop } = utils.addEventListener(document, utils.DOM_EVENT.VISIBILITY_CHANGE, expandSessionWhenVisible)
stopCallbacks.push(stop)

const visibilityCheckInterval = setInterval(expandSessionWhenVisible, VISIBILITY_CHECK_DELAY)
stopCallbacks.push(() => {
clearInterval(visibilityCheckInterval)
})
}
7 changes: 5 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@ export { Observable, Subscription } from './tools/observable'
export {
startSessionManagement,
Session,
// Exposed for tests
stopSessionManagement,
} from './domain/session/sessionManagement'
export {
SESSION_TIME_OUT_DELAY,
// Exposed for tests
SESSION_COOKIE_NAME,
stopSessionManagement,
} from './domain/sessionManagement'
} from './domain/session/sessionStore'
export { HttpRequest, Batch, canUseEventBridge, getEventBridge } from './transport'
export * from './tools/display'
export * from './tools/urlPolyfill'
Expand Down

0 comments on commit 9583f46

Please sign in to comment.