fix: await auth telemetry before auth flow navigation

This commit is contained in:
Benjamin Lu
2026-03-31 05:41:19 -07:00
parent b9d7bdf8f2
commit c679a38811
6 changed files with 93 additions and 58 deletions

View File

@@ -57,12 +57,26 @@ export class TelemetryRegistry implements TelemetryDispatcher {
})
}
private async dispatchAsync(
action: (provider: TelemetryProvider) => void | Promise<void>
): Promise<void> {
await Promise.all(
this.providers.map(async (provider) => {
try {
await action(provider)
} catch (error) {
console.error('[Telemetry] Provider dispatch failed', error)
}
})
)
}
trackSignupOpened(): void {
this.dispatch((provider) => provider.trackSignupOpened?.())
}
trackAuth(metadata: AuthMetadata): void {
this.dispatch((provider) => provider.trackAuth?.(metadata))
async trackAuth(metadata: AuthMetadata): Promise<void> {
await this.dispatchAsync((provider) => provider.trackAuth?.(metadata))
}
trackUserLoggedIn(): void {

View File

@@ -13,11 +13,6 @@ function lastDataLayerEntry(): Record<string, unknown> | undefined {
return dl?.[dl.length - 1] as Record<string, unknown> | undefined
}
async function flushAsyncWork() {
await Promise.resolve()
await Promise.resolve()
}
function toUint8Array(data: BufferSource): Uint8Array {
if (data instanceof ArrayBuffer) {
return new Uint8Array(data)
@@ -269,15 +264,13 @@ describe('GtmTelemetryProvider', () => {
}
})
provider.trackAuth({
await provider.trackAuth({
method: 'email',
is_new_user: true,
user_id: 'uid-123',
email: ' Test@Example.com '
})
await flushAsyncWork()
const dl = window.dataLayer as Record<string, unknown>[]
const authEvent = dl.find((entry) => entry.event === 'sign_up')
expect(authEvent).toMatchObject({
@@ -297,14 +290,12 @@ describe('GtmTelemetryProvider', () => {
it('omits user_data when email is absent', async () => {
const provider = createInitializedProvider()
provider.trackAuth({
await provider.trackAuth({
method: 'google',
is_new_user: false,
user_id: 'uid-456'
})
await flushAsyncWork()
expect(lastDataLayerEntry()).toMatchObject({
event: 'login',
method: 'google',
@@ -317,15 +308,13 @@ describe('GtmTelemetryProvider', () => {
const provider = createInitializedProvider()
vi.stubGlobal('crypto', undefined)
provider.trackAuth({
await provider.trackAuth({
method: 'github',
is_new_user: false,
user_id: 'uid-789',
email: 'user@example.com'
})
await flushAsyncWork()
expect(lastDataLayerEntry()).toMatchObject({
event: 'login',
method: 'github',

View File

@@ -135,12 +135,8 @@ export class GtmTelemetryProvider implements TelemetryProvider {
})
}
trackAuth(metadata: AuthMetadata): void {
async trackAuth(metadata: AuthMetadata): Promise<void> {
if (!this.initialized) return
void this.pushAuthEvent(metadata)
}
private async pushAuthEvent(metadata: AuthMetadata): Promise<void> {
const basePayload = {
method: metadata.method,
...(metadata.user_id ? { user_id: metadata.user_id } : {})

View File

@@ -351,7 +351,7 @@ export interface BeginCheckoutMetadata
export interface TelemetryProvider {
// Authentication flow events
trackSignupOpened?(): void
trackAuth?(metadata: AuthMetadata): void
trackAuth?(metadata: AuthMetadata): void | Promise<void>
trackUserLoggedIn?(): void
// Subscription flow events

View File

@@ -295,6 +295,46 @@ describe('useAuthStore', () => {
expect(store.loading).toBe(false)
})
it('should wait for auth telemetry before resolving login', async () => {
const mockUserCredential = { user: mockUser }
vi.mocked(firebaseAuth.signInWithEmailAndPassword).mockResolvedValue(
mockUserCredential as Partial<UserCredential> as UserCredential
)
let resolveTrackAuth: (() => void) | undefined
mockTrackAuth.mockImplementationOnce(
() =>
new Promise<void>((resolve) => {
resolveTrackAuth = resolve
})
)
let resolved = false
const loginPromise = store
.login('test@example.com', 'password')
.then((result) => {
resolved = true
return result
})
await vi.waitFor(() => {
expect(mockTrackAuth).toHaveBeenCalledWith(
expect.objectContaining({
method: 'email',
is_new_user: false,
user_id: mockUser.uid,
email: mockUser.email
})
)
})
expect(resolved).toBe(false)
resolveTrackAuth?.()
await expect(loginPromise).resolves.toEqual(mockUserCredential)
expect(resolved).toBe(true)
})
it('should handle login errors', async () => {
const mockError = new Error('Invalid password')
vi.mocked(firebaseAuth.signInWithEmailAndPassword).mockRejectedValue(

View File

@@ -25,6 +25,7 @@ import { t } from '@/i18n'
import { WORKSPACE_STORAGE_KEYS } from '@/platform/workspace/workspaceConstants'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import type { AuthMetadata } from '@/platform/telemetry/types'
import { useDialogService } from '@/services/dialogService'
import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore'
import type { AuthHeader } from '@/types/authTypes'
@@ -350,6 +351,11 @@ export const useAuthStore = defineStore('auth', () => {
}
}
const trackCloudAuth = async (metadata: AuthMetadata): Promise<void> => {
if (!isCloud) return
await useTelemetry()?.trackAuth(metadata)
}
const login = async (
email: string,
password: string
@@ -360,14 +366,12 @@ export const useAuthStore = defineStore('auth', () => {
{ createCustomer: true }
)
if (isCloud) {
useTelemetry()?.trackAuth({
method: 'email',
is_new_user: false,
user_id: result.user.uid,
email: result.user.email ?? undefined
})
}
await trackCloudAuth({
method: 'email',
is_new_user: false,
user_id: result.user.uid,
email: result.user.email ?? undefined
})
return result
}
@@ -382,14 +386,12 @@ export const useAuthStore = defineStore('auth', () => {
{ createCustomer: true }
)
if (isCloud) {
useTelemetry()?.trackAuth({
method: 'email',
is_new_user: true,
user_id: result.user.uid,
email: result.user.email ?? undefined
})
}
await trackCloudAuth({
method: 'email',
is_new_user: true,
user_id: result.user.uid,
email: result.user.email ?? undefined
})
return result
}
@@ -402,16 +404,13 @@ export const useAuthStore = defineStore('auth', () => {
{ createCustomer: true }
)
if (isCloud) {
const additionalUserInfo = getAdditionalUserInfo(result)
useTelemetry()?.trackAuth({
method: 'google',
is_new_user:
options?.isNewUser || additionalUserInfo?.isNewUser || false,
user_id: result.user.uid,
email: result.user.email ?? undefined
})
}
const additionalUserInfo = getAdditionalUserInfo(result)
await trackCloudAuth({
method: 'google',
is_new_user: options?.isNewUser || additionalUserInfo?.isNewUser || false,
user_id: result.user.uid,
email: result.user.email ?? undefined
})
return result
}
@@ -424,16 +423,13 @@ export const useAuthStore = defineStore('auth', () => {
{ createCustomer: true }
)
if (isCloud) {
const additionalUserInfo = getAdditionalUserInfo(result)
useTelemetry()?.trackAuth({
method: 'github',
is_new_user:
options?.isNewUser || additionalUserInfo?.isNewUser || false,
user_id: result.user.uid,
email: result.user.email ?? undefined
})
}
const additionalUserInfo = getAdditionalUserInfo(result)
await trackCloudAuth({
method: 'github',
is_new_user: options?.isNewUser || additionalUserInfo?.isNewUser || false,
user_id: result.user.uid,
email: result.user.email ?? undefined
})
return result
}