Compare commits

...

4 Commits

Author SHA1 Message Date
nav-tej
645472b2bd Merge branch 'main' into nav/posthog-qa-reset-on-logout 2026-06-01 17:12:58 -07:00
nav
f4653b7365 fix(telemetry): handle logout before PostHog finishes initializing
If trackLogout fires before the dynamic posthog-js import resolves,
this.posthog?.reset(true) is a no-op and the reset is never applied
later — leaving a path for identity/session bleed across users on
shared browsers. Defer the reset to the init callback via a flag,
and clear the pre-logout event queue at logout time so events
queued before logout don't surface under the post-logout anonymous
distinct_id.
2026-06-01 08:09:19 -07:00
nav-tej
d99f08e4ea Merge branch 'main' into nav/posthog-qa-reset-on-logout 2026-06-01 07:57:16 -07:00
Nav Singh
c957913c71 fix(telemetry): call posthog.reset(true) on logout to prevent session bleeding
Closes MAR-234.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 21:14:36 -07:00
5 changed files with 61 additions and 1 deletions

View File

@@ -81,6 +81,7 @@ export const useAuthActions = () => {
}
await authStore.logout()
useTelemetry()?.trackLogout()
toastStore.add({
severity: 'success',
summary: t('auth.signOut.success'),

View File

@@ -70,6 +70,10 @@ export class TelemetryRegistry implements TelemetryDispatcher {
this.dispatch((provider) => provider.trackUserLoggedIn?.())
}
trackLogout(): void {
this.dispatch((provider) => provider.trackLogout?.())
}
trackSubscription(
event: 'modal_opened' | 'subscribe_clicked',
metadata?: SubscriptionMetadata

View File

@@ -7,6 +7,7 @@ const hoisted = vi.hoisted(() => {
const mockInit = vi.fn()
const mockIdentify = vi.fn()
const mockPeopleSet = vi.fn()
const mockReset = vi.fn()
const mockOnUserResolved = vi.fn()
return {
@@ -14,13 +15,15 @@ const hoisted = vi.hoisted(() => {
mockInit,
mockIdentify,
mockPeopleSet,
mockReset,
mockOnUserResolved,
mockPosthog: {
default: {
init: mockInit,
capture: mockCapture,
identify: mockIdentify,
people: { set: mockPeopleSet }
people: { set: mockPeopleSet },
reset: mockReset
}
}
}
@@ -260,6 +263,42 @@ describe('PostHogTelemetryProvider', () => {
})
})
describe('logout', () => {
it('calls posthog.reset(true) when logout fires after init', async () => {
const provider = createProvider()
await vi.dynamicImportSettled()
provider.trackLogout()
expect(hoisted.mockReset).toHaveBeenCalledWith(true)
})
it('defers reset to init when logout fires before init resolves', async () => {
const provider = createProvider()
provider.trackLogout()
expect(hoisted.mockReset).not.toHaveBeenCalled()
await vi.dynamicImportSettled()
expect(hoisted.mockReset).toHaveBeenCalledWith(true)
})
it('drops pre-logout queued events when logout fires before init', async () => {
const provider = createProvider()
provider.trackUserLoggedIn()
provider.trackLogout()
await vi.dynamicImportSettled()
expect(hoisted.mockCapture).not.toHaveBeenCalledWith(
TelemetryEvents.USER_LOGGED_IN,
expect.anything()
)
})
})
describe('page view', () => {
it('captures page view with page_name property', async () => {
const provider = createProvider()

View File

@@ -85,6 +85,7 @@ export class PostHogTelemetryProvider implements TelemetryProvider {
private posthog: PostHog | null = null
private eventQueue: QueuedEvent[] = []
private isInitialized = false
private shouldResetOnInit = false
private lastTriggerSource: ExecutionTriggerSource | undefined
private disabledEvents = new Set<TelemetryEventName>(DEFAULT_DISABLED_EVENTS)
@@ -125,6 +126,11 @@ export class PostHogTelemetryProvider implements TelemetryProvider {
before_send: createPostHogBeforeSend()
})
this.isInitialized = true
if (this.shouldResetOnInit) {
this.posthog!.reset(true)
this.eventQueue = []
this.shouldResetOnInit = false
}
this.flushEventQueue()
useCurrentUser().onUserResolved((user) => {
@@ -248,6 +254,15 @@ export class PostHogTelemetryProvider implements TelemetryProvider {
this.trackEvent(TelemetryEvents.USER_LOGGED_IN)
}
trackLogout(): void {
if (!this.posthog) {
this.shouldResetOnInit = true
this.eventQueue = []
return
}
this.posthog.reset(true)
}
trackSubscription(
event: 'modal_opened' | 'subscribe_clicked',
metadata?: SubscriptionMetadata

View File

@@ -398,6 +398,7 @@ export interface TelemetryProvider {
trackSignupOpened?(): void
trackAuth?(metadata: AuthMetadata): void
trackUserLoggedIn?(): void
trackLogout?(): void
// Subscription flow events
trackSubscription?(