Compare commits

...

5 Commits

Author SHA1 Message Date
Benjamin Lu
c29c85afd1 fix: handle pre-init PostHog logout reset 2026-06-02 10:23:54 -07:00
Steven Tran
7abaf91e0b fix(telemetry): anchor PostHog reset to session state, drop trackLogout 2026-06-02 08:17:42 -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
2 changed files with 69 additions and 3 deletions

View File

@@ -7,20 +7,25 @@ const hoisted = vi.hoisted(() => {
const mockInit = vi.fn()
const mockIdentify = vi.fn()
const mockPeopleSet = vi.fn()
const mockReset = vi.fn()
const mockOnUserResolved = vi.fn()
const mockOnUserLogout = vi.fn()
return {
mockCapture,
mockInit,
mockIdentify,
mockPeopleSet,
mockReset,
mockOnUserResolved,
mockOnUserLogout,
mockPosthog: {
default: {
init: mockInit,
capture: mockCapture,
identify: mockIdentify,
people: { set: mockPeopleSet }
people: { set: mockPeopleSet },
reset: mockReset
}
}
}
@@ -36,7 +41,8 @@ vi.mock('vue', async () => {
vi.mock('@/composables/auth/useCurrentUser', () => ({
useCurrentUser: () => ({
onUserResolved: hoisted.mockOnUserResolved
onUserResolved: hoisted.mockOnUserResolved,
onUserLogout: hoisted.mockOnUserLogout
})
}))
@@ -236,6 +242,50 @@ describe('PostHogTelemetryProvider', () => {
})
})
describe('logout', () => {
it('registers onUserLogout watcher before init', () => {
createProvider()
expect(hoisted.mockOnUserLogout).toHaveBeenCalledOnce()
})
it('calls posthog.reset(true) when the watcher fires', async () => {
createProvider()
await vi.dynamicImportSettled()
const callback = hoisted.mockOnUserLogout.mock.calls[0][0]
callback()
expect(hoisted.mockReset).toHaveBeenCalledWith(true)
})
it('resets before flushing events queued after a pre-init logout', async () => {
const provider = createProvider()
const callback = hoisted.mockOnUserLogout.mock.calls[0][0]
provider.trackUserLoggedIn()
callback()
provider.trackAuth({ method: 'google' })
expect(hoisted.mockReset).not.toHaveBeenCalled()
await vi.dynamicImportSettled()
expect(hoisted.mockReset).toHaveBeenCalledWith(true)
expect(hoisted.mockCapture).not.toHaveBeenCalledWith(
TelemetryEvents.USER_LOGGED_IN,
expect.anything()
)
expect(hoisted.mockCapture).toHaveBeenCalledWith(
TelemetryEvents.USER_AUTH_COMPLETED,
{ method: 'google' }
)
expect(hoisted.mockReset.mock.invocationCallOrder[0]).toBeLessThan(
hoisted.mockCapture.mock.invocationCallOrder[0]
)
})
})
describe('page view', () => {
it('captures page view with page_name property', async () => {
const provider = createProvider()

View File

@@ -83,6 +83,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)
@@ -101,6 +102,17 @@ export class PostHogTelemetryProvider implements TelemetryProvider {
const apiKey = window.__CONFIG__?.posthog_project_token
if (apiKey) {
try {
const currentUser = useCurrentUser()
currentUser.onUserLogout(() => {
if (this.isInitialized && this.posthog) {
this.posthog.reset(true)
return
}
this.shouldResetOnInit = true
this.eventQueue = []
})
void import('posthog-js')
.then((posthogModule) => {
this.posthog = posthogModule.default
@@ -117,9 +129,13 @@ export class PostHogTelemetryProvider implements TelemetryProvider {
...serverConfig
})
this.isInitialized = true
if (this.shouldResetOnInit) {
this.posthog.reset(true)
this.shouldResetOnInit = false
}
this.flushEventQueue()
useCurrentUser().onUserResolved((user) => {
currentUser.onUserResolved((user) => {
if (this.posthog && user.id) {
this.posthog.identify(user.id)
this.setSubscriptionProperties()