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 <amp@ampcode.com>
This commit is contained in:
Alexander Brown
2026-01-26 22:17:01 -08:00
parent 2b2a72ffda
commit c117599bb6
5 changed files with 58 additions and 15 deletions

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)
}
})
}

View File

@@ -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<UserIdentity>()
/**
* Event hook for auth events (login, register, logout).
* Telemetry subscribes to track auth events without auth importing telemetry.
*/
export const authEventHook = createEventHook<AuthEvent>()

View File

@@ -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
})
}