Skip to content

Commit 0ed2edd

Browse files
authored
fix: Sync consent with persistence immediately (#2082)
* Sync consent with persistence immediately * Sync consent with persistence immediately * Move test to persistence test file * Bump terser * Add another test * Reset between tests
1 parent 64f115e commit 0ed2edd

File tree

5 files changed

+91
-13
lines changed

5 files changed

+91
-13
lines changed

packages/browser/src/__tests__/posthog-persistence.test.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@
22
import { PostHogPersistence } from '../posthog-persistence'
33
import { INITIAL_PERSON_INFO, SESSION_ID, USER_STATE } from '../constants'
44
import { PostHogConfig } from '../types'
5-
import Mock = jest.Mock
65
import { PostHog } from '../posthog-core'
76
import { window } from '../utils/globals'
87
import { uuidv7 } from '../uuidv7'
8+
import {
9+
localPlusCookieStore,
10+
resetLocalStorageSupported,
11+
resetSessionStorageSupported,
12+
sessionStore,
13+
} from '../storage'
14+
import { defaultPostHog } from './helpers/posthog-instance'
15+
import Mock = jest.Mock
916

1017
let referrer = '' // No referrer by default
1118
Object.defineProperty(document, 'referrer', { get: () => referrer })
@@ -267,3 +274,56 @@ describe('persistence', () => {
267274
})
268275
})
269276
})
277+
278+
describe('posthog instance persistence', () => {
279+
beforeEach(() => {
280+
resetSessionStorageSupported()
281+
resetLocalStorageSupported()
282+
})
283+
it('should not write to storage if opt_out_persistence_by_default and opt_out_capturing_by_default is true', () => {
284+
const sessionSpy = jest.spyOn(sessionStore, '_set')
285+
const localPlusCookieSpy = jest.spyOn(localPlusCookieStore, '_set')
286+
287+
// init posthog while opting out
288+
defaultPostHog().init(
289+
uuidv7(),
290+
{
291+
opt_out_persistence_by_default: true,
292+
opt_out_capturing_by_default: true,
293+
persistence: 'localStorage+cookie',
294+
},
295+
uuidv7()
296+
)
297+
298+
// we do one call to check if session storage is supported, but don't actually store anything
299+
// the important thing is that we don't store the session id or window id, etc. This test was added alongside
300+
// a fix which prevented this
301+
const sessionCalls = sessionSpy.mock.calls.filter(([key]) => key !== '__support__')
302+
const localPlusCookieCalls = localPlusCookieSpy.mock.calls.filter(([key]) => key !== '__support__')
303+
304+
expect(sessionCalls).toEqual([])
305+
expect(localPlusCookieCalls).toEqual([])
306+
})
307+
308+
it('should write to storage if opt_out_persistence_by_default and opt_out_capturing_by_default is false', () => {
309+
const sessionSpy = jest.spyOn(sessionStore, '_set')
310+
const localPlusCookieSpy = jest.spyOn(localPlusCookieStore, '_set')
311+
312+
// init posthog while opting out
313+
defaultPostHog().init(
314+
uuidv7(),
315+
{
316+
opt_out_persistence_by_default: false,
317+
opt_out_capturing_by_default: false,
318+
persistence: 'localStorage+cookie',
319+
},
320+
uuidv7()
321+
)
322+
323+
const sessionCalls = sessionSpy.mock.calls.filter(([key]) => key !== '__support__')
324+
const localPlusCookieCalls = localPlusCookieSpy.mock.calls.filter(([key]) => key !== '__support__')
325+
326+
expect(sessionCalls.length).toBeGreaterThan(0)
327+
expect(localPlusCookieCalls.length).toBeGreaterThan(0)
328+
})
329+
})

packages/browser/src/posthog-core.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -486,11 +486,13 @@ export class PostHog {
486486

487487
this.compression = config.disable_compression ? undefined : Compression.GZipJS
488488

489-
this.persistence = new PostHogPersistence(this.config)
489+
const persistenceDisabled = this._is_persistence_disabled()
490+
491+
this.persistence = new PostHogPersistence(this.config, persistenceDisabled)
490492
this.sessionPersistence =
491493
this.config.persistence === 'sessionStorage' || this.config.persistence === 'memory'
492494
? this.persistence
493-
: new PostHogPersistence({ ...this.config, persistence: 'sessionStorage' })
495+
: new PostHogPersistence({ ...this.config, persistence: 'sessionStorage' }, persistenceDisabled)
494496

495497
// should I store the initial person profiles config in persistence?
496498
const initialPersistenceProps = { ...this.persistence.props }
@@ -555,8 +557,6 @@ export class PostHog {
555557
})
556558
}
557559

558-
this._sync_opt_out_with_persistence()
559-
560560
// isUndefined doesn't provide typehint here so wouldn't reduce bundle as we'd need to assign
561561
// eslint-disable-next-line posthog-js/no-direct-undefined-check
562562
if (config.bootstrap?.distinctID !== undefined) {
@@ -2100,11 +2100,12 @@ export class PostHog {
21002100
if (isObject(config)) {
21012101
extend(this.config, configRenames(config))
21022102

2103-
this.persistence?.update_config(this.config, oldConfig)
2103+
const isPersistenceDisabled = this._is_persistence_disabled()
2104+
this.persistence?.update_config(this.config, oldConfig, isPersistenceDisabled)
21042105
this.sessionPersistence =
21052106
this.config.persistence === 'sessionStorage' || this.config.persistence === 'memory'
21062107
? this.persistence
2107-
: new PostHogPersistence({ ...this.config, persistence: 'sessionStorage' })
2108+
: new PostHogPersistence({ ...this.config, persistence: 'sessionStorage' }, isPersistenceDisabled)
21082109

21092110
if (localStore._is_supported() && localStore._get('ph_debug') === 'true') {
21102111
this.config.debug = true
@@ -2326,19 +2327,24 @@ export class PostHog {
23262327
return true
23272328
}
23282329

2329-
private _sync_opt_out_with_persistence(): void {
2330+
private _is_persistence_disabled(): boolean {
23302331
const isOptedOut = this.consent.isOptedOut()
23312332
const defaultPersistenceDisabled = this.config.opt_out_persistence_by_default
23322333

23332334
// TRICKY: We want a deterministic state for persistence so that a new pageload has the same persistence
2334-
const persistenceDisabled = this.config.disable_persistence || (isOptedOut && !!defaultPersistenceDisabled)
2335+
return this.config.disable_persistence || (isOptedOut && !!defaultPersistenceDisabled)
2336+
}
2337+
2338+
private _sync_opt_out_with_persistence(): boolean {
2339+
const persistenceDisabled = this._is_persistence_disabled()
23352340

23362341
if (this.persistence?._disabled !== persistenceDisabled) {
23372342
this.persistence?.set_disabled(persistenceDisabled)
23382343
}
23392344
if (this.sessionPersistence?._disabled !== persistenceDisabled) {
23402345
this.sessionPersistence?.set_disabled(persistenceDisabled)
23412346
}
2347+
return persistenceDisabled
23422348
}
23432349

23442350
/**

packages/browser/src/posthog-persistence.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ export class PostHogPersistence {
6060
private _default_expiry: number | undefined
6161
private _cross_subdomain: boolean | undefined
6262

63-
constructor(config: PostHogConfig) {
63+
/**
64+
* @param {PostHogConfig} config initial PostHog configuration
65+
* @param {boolean=} isDisabled should persistence be disabled (e.g. because of consent management)
66+
*/
67+
constructor(config: PostHogConfig, isDisabled?: boolean) {
6468
this._config = config
6569
this.props = {}
6670
this._campaign_params_saved = false
@@ -70,10 +74,14 @@ export class PostHogPersistence {
7074
if (config.debug) {
7175
logger.info('Persistence loaded', config['persistence'], { ...this.props })
7276
}
73-
this.update_config(config, config)
77+
this.update_config(config, config, isDisabled)
7478
this.save()
7579
}
7680

81+
public isDisabled(): boolean {
82+
return !!this._disabled
83+
}
84+
7785
private _buildStorage(config: PostHogConfig) {
7886
if (
7987
CASE_INSENSITIVE_PERSISTENCE_TYPES.indexOf(
@@ -308,9 +316,9 @@ export class PostHogPersistence {
308316
return props
309317
}
310318

311-
update_config(config: PostHogConfig, oldConfig: PostHogConfig): void {
319+
update_config(config: PostHogConfig, oldConfig: PostHogConfig, isDisabled?: boolean): void {
312320
this._default_expiry = this._expire_days = config['cookie_expiration']
313-
this.set_disabled(config['disable_persistence'])
321+
this.set_disabled(config['disable_persistence'] || !!isDisabled)
314322
this.set_cross_subdomain(config['cross_subdomain_cookie'])
315323
this.set_secure(config['secure_cookie'])
316324

packages/browser/src/storage.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,9 @@ export const cookieStore: PersistentStore = {
179179
}
180180

181181
let _localStorage_supported: boolean | null = null
182+
export const resetLocalStorageSupported = () => {
183+
_localStorage_supported = null
184+
}
182185

183186
export const localStore: PersistentStore = {
184187
_is_supported: function () {

packages/browser/terser-mangled-names.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@
135135
"_isSurveyConditionMatched",
136136
"_isSurveyFeatureFlagEnabled",
137137
"_isSurveysEnabled",
138+
"_is_persistence_disabled",
138139
"_is_supported",
139140
"_lastActivityTimestamp",
140141
"_lastHref",

0 commit comments

Comments
 (0)