From a125c33012c06ce0157a376a120fb25170a86394 Mon Sep 17 00:00:00 2001 From: Robbie Coomber Date: Fri, 3 Nov 2023 15:12:45 +0000 Subject: [PATCH] WIP add store migration --- src/posthog-core.ts | 2 +- src/posthog-persistence.ts | 79 +++++++++++++++++++++++++++++++++++--- src/storage.ts | 2 +- src/types.ts | 2 +- 4 files changed, 76 insertions(+), 9 deletions(-) diff --git a/src/posthog-core.ts b/src/posthog-core.ts index 5f45bb463..0f326d718 100644 --- a/src/posthog-core.ts +++ b/src/posthog-core.ts @@ -108,7 +108,7 @@ const defaultConfig = (): PostHogConfig => ({ autocapture: true, rageclick: true, cross_subdomain_cookie: isCrossDomainCookie(document?.location), - persistence: 'cookie', + persistence: 'default', persistence_name: '', cookie_name: '', loaded: __NOOP, diff --git a/src/posthog-persistence.ts b/src/posthog-persistence.ts index 7ccbec21f..7e67117d8 100644 --- a/src/posthog-persistence.ts +++ b/src/posthog-persistence.ts @@ -1,12 +1,19 @@ /* eslint camelcase: "off" */ import { _each, _extend, _include, _strip_empty_properties } from './utils' -import { cookieStore, localStore, localPlusCookieStore, memoryStore, sessionStore } from './storage' +import { + COOKIE_PERSISTED_PROPERTIES, + cookieStore, + localPlusCookieStore, + localStore, + memoryStore, + sessionStore, +} from './storage' import { PersistentStore, PostHogConfig, Properties } from './types' import { - PERSISTENCE_RESERVED_PROPERTIES, - EVENT_TIMERS_KEY, ENABLED_FEATURE_FLAGS, + EVENT_TIMERS_KEY, + PERSISTENCE_RESERVED_PROPERTIES, POSTHOG_QUOTA_LIMITED, USER_STATE, } from './constants' @@ -21,6 +28,7 @@ const CASE_INSENSITIVE_PERSISTENCE_TYPES: readonly 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 default') + config['persistence'] = 'default' } // We handle storage type in a case-insensitive way for backwards compatibility const storage_type = config['persistence'].toLowerCase() as Lowercase @@ -77,8 +85,11 @@ export class PostHogPersistence { this.storage = sessionStore } else if (storage_type === 'memory') { this.storage = memoryStore - } else { + } else if (storage_type === 'cookie') { this.storage = cookieStore + } else { + const { storage } = this._getAndMigrateToDefaultStore() + this.storage = storage } this.user_state = 'anonymous' @@ -314,4 +325,60 @@ export class PostHogPersistence { this.props[POSTHOG_QUOTA_LIMITED] = state this.save() } + + _getAndMigrateToDefaultStore = (): { storage: PersistentStore; wasPreviouslyDifferentStore: boolean } => { + // Check if there's any data in local/cookie/session storage, and if needed, + // migrate to cookie+localstorage + const defaultStore = localPlusCookieStore + const currentStore = this._getCurrentStore() + + if (currentStore && currentStore !== defaultStore) { + this._migrateStoreToDefaultStore(currentStore, defaultStore) + return { storage: defaultStore, wasPreviouslyDifferentStore: !!currentStore } + } else { + return { storage: defaultStore, wasPreviouslyDifferentStore: false } + } + } + + _getCurrentStore = () => { + // Test by picking a property that is stored in the cookie for local+cookie + const testKey = COOKIE_PERSISTED_PROPERTIES[0] + + // If that key exists in the local store, then we're definitely using + // local storage + const isLocalStore = localStore.is_supported() && localStore.get(this.name)?.[testKey] + if (isLocalStore) { + return localStore + } + // If not using pure localStore, we must be using localPlusCookie if + // there's data in both + const isLocalPlusCookie = + localPlusCookieStore.is_supported() && + localStore.is_supported() && + localStore.get(this.name) && + cookieStore.is_supported() && + cookieStore.get(this.name) + if (isLocalPlusCookie) { + return localPlusCookieStore + } + // If there's any data in cookieStore at this point then we're on cookieStore + const isCookie = cookieStore.is_supported() && cookieStore.get(this.name) + if (isCookie) { + return cookieStore + } + + const isSession = sessionStore.is_supported() && sessionStore.get(this.name) + if (isSession) { + return sessionStore + } + + // memory store, no existing store, or existing store no longer supported + return undefined + } + + _migrateStoreToDefaultStore(store: PersistentStore, defaultStore: PersistentStore) { + const values = store.get(this.name) + store.remove(this.name, this.cross_subdomain) + defaultStore.set(this.name, values, this.expire_days, this.cross_subdomain, this.secure) + } } diff --git a/src/storage.ts b/src/storage.ts index dd5f6f88e..03a07a762 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -170,7 +170,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] +export const COOKIE_PERSISTED_PROPERTIES = [DISTINCT_ID, SESSION_ID, SESSION_RECORDING_IS_SAMPLED] export const localPlusCookieStore: PersistentStore = { ...localStore, diff --git a/src/types.ts b/src/types.ts index ed607a8ab..22e0197c8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,7 +64,7 @@ export interface PostHogConfig { autocapture: boolean | AutocaptureConfig rageclick: boolean cross_subdomain_cookie: boolean - persistence: 'localStorage' | 'cookie' | 'memory' | 'localStorage+cookie' | 'sessionStorage' + persistence: 'localStorage' | 'cookie' | 'memory' | 'localStorage+cookie' | 'sessionStorage' | 'default' persistence_name: string cookie_name: string loaded: (posthog_instance: PostHog) => void