From c117599bb63f95e30500f3188c465e85e873374e Mon Sep 17 00:00:00 2001 From: Alexander Brown <448862+DrJKL@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:17:01 -0800 Subject: [PATCH] refactor: create userIdentityBus to decouple auth and telemetry - Create src/platform/telemetry/userIdentityBus.ts with event hooks - MixpanelTelemetryProvider subscribes to userIdentityHook instead of importing useCurrentUser - firebaseAuthStore emits authEventHook instead of calling useTelemetry - telemetry/index.ts subscribes to authEventHook for tracking Part of Phase 1 circular dependency fixes. Amp-Thread-ID: https://ampcode.com/threads/T-019bfe05-7da5-736f-bff0-34743c003b34 Co-authored-by: Amp --- src/composables/auth/useCurrentUser.ts | 10 +++++++- src/platform/telemetry/index.ts | 8 +++++++ .../cloud/MixpanelTelemetryProvider.ts | 10 ++++---- src/platform/telemetry/userIdentityBus.ts | 23 +++++++++++++++++++ src/stores/firebaseAuthStore.ts | 22 ++++++++++-------- 5 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 src/platform/telemetry/userIdentityBus.ts diff --git a/src/composables/auth/useCurrentUser.ts b/src/composables/auth/useCurrentUser.ts index b43191fbb..6efa0b707 100644 --- a/src/composables/auth/useCurrentUser.ts +++ b/src/composables/auth/useCurrentUser.ts @@ -3,6 +3,7 @@ import { computed, watch } from 'vue' import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions' import { t } from '@/i18n' +import { userIdentityHook } from '@/platform/telemetry/userIdentityBus' import { useDialogService } from '@/services/dialogService' import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore' import { useCommandStore } from '@/stores/commandStore' @@ -35,7 +36,14 @@ export const useCurrentUser = () => { }) const onUserResolved = (callback: (user: AuthUserInfo) => void) => - whenever(resolvedUserInfo, callback, { immediate: true }) + whenever( + resolvedUserInfo, + (user) => { + void userIdentityHook.trigger({ userId: user.id }) + callback(user) + }, + { immediate: true } + ) const onTokenRefreshed = (callback: () => void) => whenever(() => authStore.tokenRefreshTrigger, callback) diff --git a/src/platform/telemetry/index.ts b/src/platform/telemetry/index.ts index 83d7f2c9f..297e87cbc 100644 --- a/src/platform/telemetry/index.ts +++ b/src/platform/telemetry/index.ts @@ -18,6 +18,7 @@ import { isCloud } from '@/platform/distribution/types' import { MixpanelTelemetryProvider } from './providers/cloud/MixpanelTelemetryProvider' import type { TelemetryProvider } from './types' +import { authEventHook } from './userIdentityBus' // Singleton instance let _telemetryProvider: TelemetryProvider | null = null @@ -34,6 +35,13 @@ export function useTelemetry(): TelemetryProvider | null { // Use distribution check for tree-shaking if (isCloud) { _telemetryProvider = new MixpanelTelemetryProvider() + + authEventHook.on(({ method, isNewUser }) => { + _telemetryProvider?.trackAuth({ + method, + is_new_user: isNewUser + }) + }) } // 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..4be41647d 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, @@ -46,6 +45,7 @@ import type { import { remoteConfig } from '@/platform/remoteConfig/remoteConfig' import type { RemoteConfig } from '@/platform/remoteConfig/types' import { TelemetryEvents } from '../../types' +import { userIdentityHook } from '../../userIdentityBus' import { normalizeSurveyResponses } from '../../utils/surveyNormalization' const DEFAULT_DISABLED_EVENTS = [ @@ -119,10 +119,10 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { persistence: 'cookie', 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) + this.flushEventQueue() + userIdentityHook.on(({ userId }) => { + if (this.mixpanel && userId) { + this.mixpanel.identify(userId) } }) } diff --git a/src/platform/telemetry/userIdentityBus.ts b/src/platform/telemetry/userIdentityBus.ts new file mode 100644 index 000000000..f61edc0ac --- /dev/null +++ b/src/platform/telemetry/userIdentityBus.ts @@ -0,0 +1,23 @@ +import { createEventHook } from '@vueuse/core' + +export interface UserIdentity { + userId: string +} + +export interface AuthEvent { + event: 'login' | 'register' | 'logout' + method: 'email' | 'google' | 'github' + isNewUser: boolean +} + +/** + * Event hook for user identity changes. + * Telemetry subscribes to this instead of importing useCurrentUser directly. + */ +export const userIdentityHook = createEventHook() + +/** + * Event hook for auth events (login, register, logout). + * Telemetry subscribes to track auth events without auth importing telemetry. + */ +export const authEventHook = createEventHook() diff --git a/src/stores/firebaseAuthStore.ts b/src/stores/firebaseAuthStore.ts index 6073a4304..aa7025929 100644 --- a/src/stores/firebaseAuthStore.ts +++ b/src/stores/firebaseAuthStore.ts @@ -25,7 +25,7 @@ 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 { authEventHook } from '@/platform/telemetry/userIdentityBus' import { useDialogService } from '@/services/dialogService' import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore' import type { AuthHeader } from '@/types/authTypes' @@ -323,9 +323,10 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { ) if (isCloud) { - useTelemetry()?.trackAuth({ + void authEventHook.trigger({ + event: 'login', method: 'email', - is_new_user: false + isNewUser: false }) } @@ -343,9 +344,10 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { ) if (isCloud) { - useTelemetry()?.trackAuth({ + void authEventHook.trigger({ + event: 'register', method: 'email', - is_new_user: true + isNewUser: true }) } @@ -361,9 +363,10 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { if (isCloud) { const additionalUserInfo = getAdditionalUserInfo(result) const isNewUser = additionalUserInfo?.isNewUser ?? false - useTelemetry()?.trackAuth({ + void authEventHook.trigger({ + event: isNewUser ? 'register' : 'login', method: 'google', - is_new_user: isNewUser + isNewUser }) } @@ -379,9 +382,10 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { if (isCloud) { const additionalUserInfo = getAdditionalUserInfo(result) const isNewUser = additionalUserInfo?.isNewUser ?? false - useTelemetry()?.trackAuth({ + void authEventHook.trigger({ + event: isNewUser ? 'register' : 'login', method: 'github', - is_new_user: isNewUser + isNewUser }) }