diff --git a/src/platform/telemetry/authTracking.ts b/src/platform/telemetry/authTracking.ts new file mode 100644 index 000000000..63ee70bf8 --- /dev/null +++ b/src/platform/telemetry/authTracking.ts @@ -0,0 +1,26 @@ +import { authEventHook, userResolvedHook } from '@/stores/authEventBus' + +import type { TelemetryProvider } from './types' + +export function initAuthTracking( + getTelemetry: () => TelemetryProvider | null +): void { + authEventHook.on((event) => { + const telemetry = getTelemetry() + if (!telemetry) return + + if (event.type === 'login' || event.type === 'register') { + telemetry.trackAuth({ + method: event.method, + is_new_user: event.is_new_user + }) + } + }) + + userResolvedHook.on((event) => { + const telemetry = getTelemetry() + if (!telemetry) return + + telemetry.identify?.(event.userId) + }) +} diff --git a/src/platform/telemetry/index.ts b/src/platform/telemetry/index.ts index 83d7f2c9f..4e26a1d90 100644 --- a/src/platform/telemetry/index.ts +++ b/src/platform/telemetry/index.ts @@ -16,11 +16,13 @@ */ import { isCloud } from '@/platform/distribution/types' +import { initAuthTracking } from './authTracking' import { MixpanelTelemetryProvider } from './providers/cloud/MixpanelTelemetryProvider' import type { TelemetryProvider } from './types' // Singleton instance let _telemetryProvider: TelemetryProvider | null = null +let _authTrackingInitialized = false /** * Telemetry factory - conditionally creates provider based on distribution @@ -34,6 +36,11 @@ export function useTelemetry(): TelemetryProvider | null { // Use distribution check for tree-shaking if (isCloud) { _telemetryProvider = new MixpanelTelemetryProvider() + + if (!_authTrackingInitialized) { + initAuthTracking(() => _telemetryProvider) + _authTrackingInitialized = true + } } // For OSS builds, _telemetryProvider stays null } diff --git a/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts b/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts index 6f8c0ccbb..baf5242e6 100644 --- a/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts +++ b/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts @@ -1,7 +1,6 @@ import type { OverridedMixpanel } from 'mixpanel-browser' import { watch } from 'vue' -import { useCurrentUser } from '@/composables/auth/useCurrentUser' import { checkForCompletedTopup as checkTopupUtil, clearTopupTracking as clearTopupUtil, @@ -120,11 +119,6 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { loaded: () => { this.isInitialized = true this.flushEventQueue() // flush events that were queued while initializing - useCurrentUser().onUserResolved((user) => { - if (this.mixpanel && user.id) { - this.mixpanel.identify(user.id) - } - }) } }) }) @@ -205,6 +199,12 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { ) } + identify(userId: string): void { + if (this.mixpanel) { + this.mixpanel.identify(userId) + } + } + trackSignupOpened(): void { this.trackEvent(TelemetryEvents.USER_SIGN_UP_OPENED) } diff --git a/src/platform/telemetry/types.ts b/src/platform/telemetry/types.ts index 2ce9c7f0f..1bab7e39c 100644 --- a/src/platform/telemetry/types.ts +++ b/src/platform/telemetry/types.ts @@ -272,6 +272,9 @@ export interface WorkflowCreatedMetadata { * Core telemetry provider interface */ export interface TelemetryProvider { + // User identification (called by auth event hooks) + identify?(userId: string): void + // Authentication flow events trackSignupOpened(): void trackAuth(metadata: AuthMetadata): void diff --git a/src/stores/authEventBus.ts b/src/stores/authEventBus.ts new file mode 100644 index 000000000..5e51a5854 --- /dev/null +++ b/src/stores/authEventBus.ts @@ -0,0 +1,16 @@ +import { createEventHook } from '@vueuse/core' + +import type { AuthMetadata } from '@/platform/telemetry/types' + +export interface AuthEvent extends AuthMetadata { + type: 'login' | 'register' | 'logout' | 'password_reset' +} + +export interface UserResolvedEvent { + userId: string + email?: string | null + displayName?: string | null +} + +export const authEventHook = createEventHook() +export const userResolvedHook = createEventHook() diff --git a/src/stores/firebaseAuthStore.ts b/src/stores/firebaseAuthStore.ts index 6073a4304..76ee0359e 100644 --- a/src/stores/firebaseAuthStore.ts +++ b/src/stores/firebaseAuthStore.ts @@ -25,8 +25,8 @@ import { getComfyApiBaseUrl } from '@/config/comfyApi' import { t } from '@/i18n' import { WORKSPACE_STORAGE_KEYS } from '@/platform/auth/workspace/workspaceConstants' import { isCloud } from '@/platform/distribution/types' -import { useTelemetry } from '@/platform/telemetry' import { useDialogService } from '@/services/dialogService' +import { authEventHook, userResolvedHook } from '@/stores/authEventBus' import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore' import type { AuthHeader } from '@/types/authTypes' import type { operations } from '@/types/comfyRegistryTypes' @@ -323,10 +323,16 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { ) if (isCloud) { - useTelemetry()?.trackAuth({ + authEventHook.trigger({ + type: 'login', method: 'email', is_new_user: false }) + userResolvedHook.trigger({ + userId: result.user.uid, + email: result.user.email, + displayName: result.user.displayName + }) } return result @@ -343,10 +349,16 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { ) if (isCloud) { - useTelemetry()?.trackAuth({ + authEventHook.trigger({ + type: 'register', method: 'email', is_new_user: true }) + userResolvedHook.trigger({ + userId: result.user.uid, + email: result.user.email, + displayName: result.user.displayName + }) } return result @@ -361,10 +373,16 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { if (isCloud) { const additionalUserInfo = getAdditionalUserInfo(result) const isNewUser = additionalUserInfo?.isNewUser ?? false - useTelemetry()?.trackAuth({ + authEventHook.trigger({ + type: isNewUser ? 'register' : 'login', method: 'google', is_new_user: isNewUser }) + userResolvedHook.trigger({ + userId: result.user.uid, + email: result.user.email, + displayName: result.user.displayName + }) } return result @@ -379,10 +397,16 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { if (isCloud) { const additionalUserInfo = getAdditionalUserInfo(result) const isNewUser = additionalUserInfo?.isNewUser ?? false - useTelemetry()?.trackAuth({ + authEventHook.trigger({ + type: isNewUser ? 'register' : 'login', method: 'github', is_new_user: isNewUser }) + userResolvedHook.trigger({ + userId: result.user.uid, + email: result.user.email, + displayName: result.user.displayName + }) } return result