diff --git a/src/__tests__/posthog-persistence.test.ts b/src/__tests__/posthog-persistence.test.ts index 4f956459a..e80fb4614 100644 --- a/src/__tests__/posthog-persistence.test.ts +++ b/src/__tests__/posthog-persistence.test.ts @@ -1,6 +1,6 @@ /// import { PostHogPersistence } from '../posthog-persistence' -import { SESSION_ID, USER_STATE } from '../constants' +import { CLIENT_SESSION_PROPS, SESSION_ID, USER_STATE } from '../constants' import { PostHogConfig } from '../types' import Mock = jest.Mock @@ -18,7 +18,7 @@ describe('persistence', () => { let library: PostHogPersistence afterEach(() => { - library.clear() + library?.clear() document.cookie = '' referrer = '' }) @@ -139,6 +139,7 @@ describe('persistence', () => { distinct_id: 'test', })}` ) + expect(document.cookie).not.toContain('test_prop') lib.register({ otherProp: 'prop' }) expect(document.cookie).toContain( @@ -155,6 +156,29 @@ describe('persistence', () => { })}` ) + lib.register({ + [CLIENT_SESSION_PROPS]: { + sessionId: 'sid', + props: { + initialPathName: '/some/pathname', + referringDomain: '$direct', + }, + }, + }) + expect(document.cookie).toContain( + `ph__posthog=${encode({ + distinct_id: 'test', + $sesid: [1000, 'sid', 2000], + $client_session_props: { + sessionId: 'sid', + props: { + initialPathName: '/some/pathname', + referringDomain: '$direct', + }, + }, + })}` + ) + // Clear localstorage to simulate being on a different domain localStorage.clear() @@ -163,6 +187,13 @@ describe('persistence', () => { expect(newLib.props).toEqual({ distinct_id: 'test', $sesid: [1000, 'sid', 2000], + $client_session_props: { + sessionId: 'sid', + props: { + initialPathName: '/some/pathname', + referringDomain: '$direct', + }, + }, }) }) }) diff --git a/src/posthog-core.ts b/src/posthog-core.ts index b3eb68e90..55ea3f212 100644 --- a/src/posthog-core.ts +++ b/src/posthog-core.ts @@ -108,7 +108,7 @@ export const defaultConfig = (): PostHogConfig => ({ autocapture: true, rageclick: true, cross_subdomain_cookie: isCrossDomainCookie(document?.location), - persistence: 'cookie', + persistence: 'localStorage+cookie', // up to 1.92.0 this was 'cookie'. It's easy to migrate as 'localStorage+cookie' will migrate data from cookie storage persistence_name: '', cookie_name: '', loaded: __NOOP, diff --git a/src/posthog-persistence.ts b/src/posthog-persistence.ts index 7ccbec21f..71017e659 100644 --- a/src/posthog-persistence.ts +++ b/src/posthog-persistence.ts @@ -64,8 +64,10 @@ export class PostHogPersistence { config['persistence'].toLowerCase() as Lowercase ) === -1 ) { - logger.critical('Unknown persistence type ' + config['persistence'] + '; falling back to cookie') - config['persistence'] = 'cookie' + logger.critical( + 'Unknown persistence type ' + config['persistence'] + '; falling back to localStorage+cookie' + ) + config['persistence'] = 'localStorage+cookie' } // We handle storage type in a case-insensitive way for backwards compatibility const storage_type = config['persistence'].toLowerCase() as Lowercase @@ -77,8 +79,15 @@ export class PostHogPersistence { this.storage = sessionStore } else if (storage_type === 'memory') { this.storage = memoryStore - } else { + } else if (storage_type === 'cookie') { this.storage = cookieStore + } else { + // selected storage type wasn't supported, fallback to 'localstorage+cookie' if possible + if (localPlusCookieStore.is_supported()) { + this.storage = localPlusCookieStore + } else { + this.storage = cookieStore + } } this.user_state = 'anonymous' diff --git a/src/session-props.ts b/src/session-props.ts index 79021ae80..48bced994 100644 --- a/src/session-props.ts +++ b/src/session-props.ts @@ -12,7 +12,7 @@ import { SessionIdManager } from './sessionid' import { PostHogPersistence } from './posthog-persistence' import { CLIENT_SESSION_PROPS } from './constants' -interface SessionSourceProps { +export interface SessionSourceProps { initialPathName: string referringDomain: string // Is actually host, but named domain for internal consistency. Should contain a port if there is one. utm_medium?: string @@ -22,7 +22,7 @@ interface SessionSourceProps { utm_term?: string } -interface StoredSessionSourceProps { +export interface StoredSessionSourceProps { sessionId: string props: SessionSourceProps } diff --git a/src/storage.ts b/src/storage.ts index c559b379c..b090e5892 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -1,6 +1,6 @@ import { _extend } from './utils' import { PersistentStore, Properties } from './types' -import { DISTINCT_ID, SESSION_ID, SESSION_RECORDING_IS_SAMPLED } from './constants' +import { CLIENT_SESSION_PROPS, DISTINCT_ID, SESSION_ID, SESSION_RECORDING_IS_SAMPLED } from './constants' import { _isNull, _isUndefined } from './utils/type-utils' import { logger } from './utils/logger' @@ -140,6 +140,12 @@ export const cookieStore: PersistentStore = { '; SameSite=Lax; path=/' + cdomain + secure + + // 4096 bytes is the size at which some browsers (e.g. firefox) will not store a cookie, warn slightly before that + if (new_cookie_val.length > 4096 * 0.9) { + logger.warn('cookieStore warning: large cookie, len=' + new_cookie_val.length) + } + document.cookie = new_cookie_val return new_cookie_val } catch (err) { @@ -230,7 +236,7 @@ export const localStore: PersistentStore = { // Use localstorage for most data but still use cookie for COOKIE_PERSISTED_PROPERTIES // This solves issues with cookies having too much data in them causing headers too large // Also makes sure we don't have to send a ton of data to the server -const COOKIE_PERSISTED_PROPERTIES = [DISTINCT_ID, SESSION_ID, SESSION_RECORDING_IS_SAMPLED] +const COOKIE_PERSISTED_PROPERTIES = [DISTINCT_ID, SESSION_ID, SESSION_RECORDING_IS_SAMPLED, CLIENT_SESSION_PROPS] export const localPlusCookieStore: PersistentStore = { ...localStore,