From 9758bf8e97a0866ac28ec5a3c34b9f14c93265af Mon Sep 17 00:00:00 2001 From: Tim Yiu <137842098+tyiuhc@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:25:58 -0700 Subject: [PATCH 1/3] fix: delete all persisted data upon consent rejection --- packages/experiment-tag/src/experiment.ts | 25 +++++++++++------ packages/experiment-tag/src/index.ts | 2 ++ packages/experiment-tag/src/storage/keys.ts | 27 +++++++++++++++++++ .../experiment-tag/src/storage/storage.ts | 22 +++++++++++++++ packages/experiment-tag/src/util/messenger.ts | 5 ++-- packages/experiment-tag/src/util/url.ts | 5 ++-- 6 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 packages/experiment-tag/src/storage/keys.ts diff --git a/packages/experiment-tag/src/experiment.ts b/packages/experiment-tag/src/experiment.ts index 962031fc..03dd443b 100644 --- a/packages/experiment-tag/src/experiment.ts +++ b/packages/experiment-tag/src/experiment.ts @@ -18,6 +18,13 @@ import mutate, { MutationController } from 'dom-mutator'; import { MessageBus } from './message-bus'; import { showPreviewModeModal } from './preview/preview'; import { ConsentAwareStorage } from './storage/consent-aware-storage'; +import { + getExperimentStorageKey, + getPreviewModeSessionKey, + getRedirectStorageKey, + getVisualEditorSessionKey, +} from './storage/keys'; +import { deletePersistedData } from './storage/storage'; import { PageChangeEvent, SubscriptionManager } from './subscriptions'; import { ConsentOptions, @@ -37,7 +44,7 @@ import { } from './types'; import { applyAntiFlickerCss } from './util/anti-flicker'; import { getInjectUtils } from './util/inject-utils'; -import { VISUAL_EDITOR_SESSION_KEY, WindowMessenger } from './util/messenger'; +import { WindowMessenger } from './util/messenger'; import { patchRemoveChild } from './util/patch'; import { getUrlParams, @@ -53,7 +60,6 @@ const MUTATE_ACTION = 'mutate'; export const INJECT_ACTION = 'inject'; const REDIRECT_ACTION = 'redirect'; export const PREVIEW_MODE_PARAM = 'PREVIEW'; -export const PREVIEW_MODE_SESSION_KEY = 'amp-preview-mode'; const VISUAL_EDITOR_PARAM = 'VISUAL_EDITOR'; safeGlobal.Experiment = FeatureExperiment; @@ -182,7 +188,7 @@ export class DefaultWebExperimentClient implements WebExperimentClient { const urlParams = getUrlParams(); this.isVisualEditorMode = urlParams[VISUAL_EDITOR_PARAM] === 'true' || - this.storage.getItem('sessionStorage', VISUAL_EDITOR_SESSION_KEY) !== + this.storage.getItem('sessionStorage', getVisualEditorSessionKey()) !== null; this.subscriptionManager = new SubscriptionManager( this, @@ -216,7 +222,7 @@ export class DefaultWebExperimentClient implements WebExperimentClient { // fire url_change upon landing on page, set updateActivePagesOnly to not trigger variant actions this.messageBus.publish('url_change', { updateActivePages: true }); - const experimentStorageName = `EXP_${this.apiKey.slice(0, 10)}`; + const experimentStorageName = getExperimentStorageKey(this.apiKey); const user = this.storage.getItem( 'localStorage', @@ -535,6 +541,9 @@ export class DefaultWebExperimentClient implements WebExperimentClient { public setConsentStatus(consentStatus: ConsentStatus) { this.consentOptions.status = consentStatus; this.storage.setConsentStatus(consentStatus); + if (consentStatus === ConsentStatus.REJECTED) { + deletePersistedData(this.apiKey, this.config); + } } private async fetchRemoteFlags() { @@ -809,7 +818,7 @@ export class DefaultWebExperimentClient implements WebExperimentClient { variant: Variant, redirectUrl: string, ) { - const redirectStorageKey = `EXP_${this.apiKey.slice(0, 10)}_REDIRECT`; + const redirectStorageKey = getRedirectStorageKey(this.apiKey); // Store the current flag and variant for exposure tracking after redirect const storedRedirects = this.storage.getItem('sessionStorage', redirectStorageKey) || {}; @@ -819,7 +828,7 @@ export class DefaultWebExperimentClient implements WebExperimentClient { private fireStoredRedirectImpressions() { // Check for stored redirects and process them - const redirectStorageKey = `EXP_${this.apiKey.slice(0, 10)}_REDIRECT`; + const redirectStorageKey = getRedirectStorageKey(this.apiKey); const storedRedirects = this.storage.getItem('sessionStorage', redirectStorageKey) || {}; @@ -870,7 +879,7 @@ export class DefaultWebExperimentClient implements WebExperimentClient { } }); - this.storage.setItem('sessionStorage', PREVIEW_MODE_SESSION_KEY, { + this.storage.setItem('sessionStorage', getPreviewModeSessionKey(), { previewFlags: this.previewFlags, }); const previewParamsToRemove = [ @@ -890,7 +899,7 @@ export class DefaultWebExperimentClient implements WebExperimentClient { } else { const previewState: PreviewState | null = this.storage.getItem( 'sessionStorage', - PREVIEW_MODE_SESSION_KEY, + getPreviewModeSessionKey(), ); if (previewState) { this.previewFlags = previewState.previewFlags; diff --git a/packages/experiment-tag/src/index.ts b/packages/experiment-tag/src/index.ts index f58648d0..dd4dbe52 100644 --- a/packages/experiment-tag/src/index.ts +++ b/packages/experiment-tag/src/index.ts @@ -3,6 +3,7 @@ import { getGlobalScope } from '@amplitude/experiment-core'; import { DefaultWebExperimentClient } from './experiment'; import { HttpClient } from './preview/http'; import { SdkPreviewApi } from './preview/preview-api'; +import { deletePersistedData } from './storage/storage'; import { ConsentStatus, WebExperimentConfig } from './types'; import { applyAntiFlickerCss } from './util/anti-flicker'; import { isPreviewMode } from './util/url'; @@ -17,6 +18,7 @@ export const initialize = ( getGlobalScope()?.experimentConfig.consentOptions.status === ConsentStatus.REJECTED ) { + deletePersistedData(apiKey, config); return; } const shouldFetchConfigs = diff --git a/packages/experiment-tag/src/storage/keys.ts b/packages/experiment-tag/src/storage/keys.ts new file mode 100644 index 00000000..a5971aad --- /dev/null +++ b/packages/experiment-tag/src/storage/keys.ts @@ -0,0 +1,27 @@ +import { WebExperimentConfig } from '../types'; + +export const getExperimentStorageKey = (apiKey: string): string => { + return `EXP_${apiKey.slice(0, 10)}`; +}; + +export const getDefaultUserProviderStorageKey = (apiKey: string): string => { + return `EXP_${apiKey.slice(0, 10)}_DEFAULT_USER_PROVIDER`; +}; + +export const getUnsentEventsStorageKey = ( + config: WebExperimentConfig, +): string => { + return `EXP_unsent_${config.instanceName ?? 'default_instance'}`; +}; + +export const getRedirectStorageKey = (apiKey: string): string => { + return `EXP_${apiKey.slice(0, 10)}_REDIRECT`; +}; + +export const getPreviewModeSessionKey = (): string => { + return 'amp-preview-mode'; +}; + +export const getVisualEditorSessionKey = (): string => { + return 'visual-editor-state'; +}; diff --git a/packages/experiment-tag/src/storage/storage.ts b/packages/experiment-tag/src/storage/storage.ts index 29f19644..2bba0468 100644 --- a/packages/experiment-tag/src/storage/storage.ts +++ b/packages/experiment-tag/src/storage/storage.ts @@ -1,5 +1,13 @@ import { getGlobalScope } from '@amplitude/experiment-core'; +import { WebExperimentConfig } from '../types'; + +import { + getDefaultUserProviderStorageKey, + getExperimentStorageKey, + getUnsentEventsStorageKey, +} from './keys'; + export type StorageType = 'localStorage' | 'sessionStorage'; /** @@ -66,3 +74,17 @@ export const getStorage = (storageType: StorageType): Storage | null => { } return globalScope[storageType]; }; + +export const deletePersistedData = ( + apiKey: string, + config: WebExperimentConfig, +): void => { + const experimentStorageKey = getExperimentStorageKey(apiKey); + const defaultUserProviderStorageKey = + getDefaultUserProviderStorageKey(apiKey); + const unsentEventsStorageKey = getUnsentEventsStorageKey(config); + removeStorageItem('localStorage', experimentStorageKey); + removeStorageItem('localStorage', defaultUserProviderStorageKey); + removeStorageItem('sessionStorage', defaultUserProviderStorageKey); + removeStorageItem('localStorage', unsentEventsStorageKey); +}; diff --git a/packages/experiment-tag/src/util/messenger.ts b/packages/experiment-tag/src/util/messenger.ts index 1cf0bb19..25757ba4 100644 --- a/packages/experiment-tag/src/util/messenger.ts +++ b/packages/experiment-tag/src/util/messenger.ts @@ -1,5 +1,6 @@ import { getGlobalScope } from '@amplitude/experiment-core'; +import { getVisualEditorSessionKey } from '../storage/keys'; import { getStorageItem } from '../storage/storage'; interface VisualEditorSession { @@ -7,8 +8,6 @@ interface VisualEditorSession { amplitudeWindowUrl: string; } -export const VISUAL_EDITOR_SESSION_KEY = 'visual-editor-state'; - export class WindowMessenger { static setup() { let state: 'closed' | 'opening' | 'open' = 'closed'; @@ -75,7 +74,7 @@ export class WindowMessenger { private static getStoredSession(): VisualEditorSession | null { const sessionData = getStorageItem( 'sessionStorage', - VISUAL_EDITOR_SESSION_KEY, + getVisualEditorSessionKey(), ); if (!sessionData) { return null; diff --git a/packages/experiment-tag/src/util/url.ts b/packages/experiment-tag/src/util/url.ts index 12089f27..638efbb7 100644 --- a/packages/experiment-tag/src/util/url.ts +++ b/packages/experiment-tag/src/util/url.ts @@ -1,8 +1,9 @@ import { getGlobalScope } from '@amplitude/experiment-core'; -import { PREVIEW_MODE_PARAM, PREVIEW_MODE_SESSION_KEY } from '../experiment'; +import { PREVIEW_MODE_PARAM } from '../experiment'; import { getStorageItem } from '../storage/storage'; import { PreviewState } from '../types'; +import { getPreviewModeSessionKey } from '../storage/keys'; export const getUrlParams = (): Record => { const globalScope = getGlobalScope(); @@ -90,7 +91,7 @@ export const isPreviewMode = (): boolean => { } const previewState = getStorageItem( 'sessionStorage', - PREVIEW_MODE_SESSION_KEY, + getPreviewModeSessionKey(), ) as PreviewState; if ( previewState?.previewFlags && From c897aa7d090b827c65048ec3e146909d8d49d31a Mon Sep 17 00:00:00 2001 From: Tim Yiu <137842098+tyiuhc@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:28:54 -0700 Subject: [PATCH 2/3] fix lint --- packages/experiment-tag/src/util/url.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/experiment-tag/src/util/url.ts b/packages/experiment-tag/src/util/url.ts index 638efbb7..deb4af9a 100644 --- a/packages/experiment-tag/src/util/url.ts +++ b/packages/experiment-tag/src/util/url.ts @@ -1,9 +1,9 @@ import { getGlobalScope } from '@amplitude/experiment-core'; import { PREVIEW_MODE_PARAM } from '../experiment'; +import { getPreviewModeSessionKey } from '../storage/keys'; import { getStorageItem } from '../storage/storage'; import { PreviewState } from '../types'; -import { getPreviewModeSessionKey } from '../storage/keys'; export const getUrlParams = (): Record => { const globalScope = getGlobalScope(); From ea0787abe0c118823d33dfb8ff436389afc120f1 Mon Sep 17 00:00:00 2001 From: Tim Yiu <137842098+tyiuhc@users.noreply.github.com> Date: Fri, 17 Oct 2025 14:03:48 -0700 Subject: [PATCH 3/3] fix lint --- packages/experiment-tag/src/experiment.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/experiment-tag/src/experiment.ts b/packages/experiment-tag/src/experiment.ts index c5b5e1fe..1d519863 100644 --- a/packages/experiment-tag/src/experiment.ts +++ b/packages/experiment-tag/src/experiment.ts @@ -18,17 +18,17 @@ import mutate, { MutationController } from 'dom-mutator'; import { ConsentAwareExposureHandler } from './exposure/consent-aware-exposure-handler'; import { MessageBus } from './message-bus'; import { showPreviewModeModal } from './preview/preview'; +import { + ConsentAwareLocalStorage, + ConsentAwareSessionStorage, + ConsentAwareStorage, +} from './storage/consent-aware-storage'; import { getExperimentStorageKey, getPreviewModeSessionKey, getRedirectStorageKey, getVisualEditorSessionKey, } from './storage/keys'; -import { - ConsentAwareLocalStorage, - ConsentAwareSessionStorage, - ConsentAwareStorage, -} from './storage/consent-aware-storage'; import { deletePersistedData, getAndParseStorageItem,