[Refactor] Split authStore into authStore and authService (#3612)

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Chenlei Hu
2025-04-24 20:45:30 -04:00
committed by GitHub
parent 2a62f7ec7f
commit b05407ffdd
15 changed files with 217 additions and 129 deletions

View File

@@ -69,7 +69,7 @@ import { computed, watch } from 'vue'
import SearchBox from '@/components/common/SearchBox.vue'
import { useSettingSearch } from '@/composables/setting/useSettingSearch'
import { useSettingUI } from '@/composables/setting/useSettingUI'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
import { useFirebaseAuthService } from '@/services/firebaseAuthService'
import { SettingTreeNode } from '@/stores/settingStore'
import { ISettingGroup, SettingParams } from '@/types/settingTypes'
import { flattenTree } from '@/utils/treeUtil'
@@ -107,7 +107,7 @@ const {
getSearchResults
} = useSettingSearch()
const authStore = useFirebaseAuthStore()
const authService = useFirebaseAuthService()
// Sort groups for a category
const sortedGroups = (category: SettingTreeNode): ISettingGroup[] => {
@@ -140,7 +140,7 @@ watch(activeCategory, (_, oldValue) => {
activeCategory.value = oldValue
}
if (activeCategory.value?.key === 'credits') {
void authStore.fetchBalance()
void authService.fetchBalance()
}
})
</script>

View File

@@ -109,8 +109,9 @@ import ProgressSpinner from 'primevue/progressspinner'
import Tag from 'primevue/tag'
import { computed, onBeforeUnmount, ref } from 'vue'
import { useFirebaseAuthService } from '@/services/firebaseAuthService'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
import { formatMetronomeCurrency, usdToMicros } from '@/utils/formatUtil'
import { formatMetronomeCurrency } from '@/utils/formatUtil'
const {
isInsufficientCredits = false,
@@ -123,6 +124,7 @@ const {
}>()
const authStore = useFirebaseAuthStore()
const authService = useFirebaseAuthService()
const customAmount = ref<number>(100)
const didClickBuyNow = ref(false)
const loading = computed(() => authStore.loading)
@@ -133,29 +135,18 @@ const formattedBalance = computed(() => {
})
const handleSeeDetails = async () => {
const response = await authStore.accessBillingPortal()
if (!response?.billing_portal_url) return
window.open(response.billing_portal_url, '_blank')
await authService.accessBillingPortal()
}
const handleBuyNow = async (amount: number) => {
const response = await authStore.initiateCreditPurchase({
amount_micros: usdToMicros(amount),
currency: 'usd'
})
if (!response?.checkout_url) return
await authService.purchaseCredits(amount)
didClickBuyNow.value = true
// Go to Stripe checkout page
window.open(response.checkout_url, '_blank')
}
onBeforeUnmount(() => {
if (didClickBuyNow.value) {
// If clicked buy now, then returned back to the dialog and closed, fetch the balance
void authStore.fetchBalance()
void authService.fetchBalance()
}
})
</script>

View File

@@ -51,7 +51,7 @@
text
size="small"
severity="secondary"
@click="() => authStore.fetchBalance()"
@click="() => authService.fetchBalance()"
/>
</div>
</div>
@@ -128,6 +128,7 @@ import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useDialogService } from '@/services/dialogService'
import { useFirebaseAuthService } from '@/services/firebaseAuthService'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
import { formatMetronomeCurrency } from '@/utils/formatUtil'
@@ -141,6 +142,7 @@ interface CreditHistoryItemData {
const { t } = useI18n()
const dialogService = useDialogService()
const authStore = useFirebaseAuthStore()
const authService = useFirebaseAuthService()
const loading = computed(() => authStore.loading)
const balanceLoading = computed(() => authStore.isFetchingBalance)
const formattedBalance = computed(() => {
@@ -159,13 +161,7 @@ const handlePurchaseCreditsClick = () => {
}
const handleCreditsHistoryClick = async () => {
const response = await authStore.accessBillingPortal()
if (!response) return
const { billing_portal_url } = response
if (billing_portal_url) {
window.open(billing_portal_url, '_blank')
}
await authService.accessBillingPortal()
}
const handleMessageSupport = () => {

View File

@@ -81,14 +81,14 @@ import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { type SignInData, signInSchema } from '@/schemas/signInSchema'
import { useFirebaseAuthService } from '@/services/firebaseAuthService'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
import { useToastStore } from '@/stores/toastStore'
const authStore = useFirebaseAuthStore()
const firebaseAuthService = useFirebaseAuthService()
const loading = computed(() => authStore.loading)
const { t } = useI18n()
const toast = useToastStore()
const emit = defineEmits<{
submit: [values: SignInData]
@@ -102,19 +102,6 @@ const onSubmit = (event: FormSubmitEvent) => {
const handleForgotPassword = async (email: string) => {
if (!email) return
await authStore.sendPasswordReset(email)
if (authStore.error) {
toast.add({
severity: 'error',
summary: t('auth.login.forgotPasswordError'),
detail: authStore.error
})
} else {
toast.add({
severity: 'success',
summary: t('auth.login.passwordResetSent'),
detail: t('auth.login.passwordResetSentDetail')
})
}
await firebaseAuthService.sendPasswordReset(email)
}
</script>

View File

@@ -13,10 +13,10 @@ import { t } from '@/i18n'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { useDialogService } from '@/services/dialogService'
import { useFirebaseAuthService } from '@/services/firebaseAuthService'
import { useLitegraphService } from '@/services/litegraphService'
import { useWorkflowService } from '@/services/workflowService'
import type { ComfyCommand } from '@/stores/commandStore'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
import { useTitleEditorStore } from '@/stores/graphStore'
import { useQueueSettingsStore, useQueueStore } from '@/stores/queueStore'
import { useSettingStore } from '@/stores/settingStore'
@@ -32,7 +32,7 @@ export function useCoreCommands(): ComfyCommand[] {
const workflowStore = useWorkflowStore()
const dialogService = useDialogService()
const colorPaletteStore = useColorPaletteStore()
const authStore = useFirebaseAuthStore()
const firebaseAuthService = useFirebaseAuthService()
const toastStore = useToastStore()
const getTracker = () => workflowStore.activeWorkflow?.changeTracker
@@ -649,17 +649,7 @@ export function useCoreCommands(): ComfyCommand[] {
label: 'Sign Out',
versionAdded: '1.18.1',
function: async () => {
await authStore.logout()
if (authStore.error) {
toastStore.addAlert(authStore.error)
} else {
toastStore.add({
severity: 'success',
summary: t('auth.signOut.success'),
detail: t('auth.signOut.successDetail'),
life: 5000
})
}
await firebaseAuthService.logout()
}
}
]

View File

@@ -111,7 +111,8 @@
"updateAvailable": "Update Available",
"login": "Login",
"learnMore": "Learn more",
"amount": "Amount"
"amount": "Amount",
"unknownError": "Unknown error"
},
"manager": {
"title": "Custom Nodes Manager",
@@ -1077,7 +1078,14 @@
"errorCopyImage": "Error copying image: {error}",
"noTemplatesToExport": "No templates to export",
"failedToFetchLogs": "Failed to fetch server logs",
"migrateToLitegraphReroute": "Reroute nodes will be removed in future versions. Click to migrate to litegraph-native reroute."
"migrateToLitegraphReroute": "Reroute nodes will be removed in future versions. Click to migrate to litegraph-native reroute.",
"userNotAuthenticated": "User not authenticated",
"firebaseAuthNotInitialized": "Firebase Auth not initialized",
"failedToFetchBalance": "Failed to fetch balance: {error}",
"failedToCreateCustomer": "Failed to create customer: {error}",
"failedToInitiateCreditPurchase": "Failed to initiate credit purchase: {error}",
"failedToAccessBillingPortal": "Failed to access billing portal: {error}",
"failedToPurchaseCredits": "Failed to purchase credits: {error}"
},
"auth": {
"login": {
@@ -1104,8 +1112,7 @@
"andText": "and",
"privacyLink": "Privacy Policy",
"success": "Login successful",
"failed": "Login failed",
"genericErrorMessage": "Sorry, we've encountered an error. Please contact {supportEmail}."
"failed": "Login failed"
},
"signup": {
"title": "Create an account",

View File

@@ -18,7 +18,6 @@
"failed": "Inicio de sesión fallido",
"forgotPassword": "¿Olvidaste tu contraseña?",
"forgotPasswordError": "No se pudo enviar el correo electrónico para restablecer la contraseña",
"genericErrorMessage": "Lo sentimos, hemos encontrado un error. Por favor, contacta con {supportEmail}.",
"loginButton": "Iniciar sesión",
"loginWithGithub": "Iniciar sesión con Github",
"loginWithGoogle": "Iniciar sesión con Google",
@@ -297,6 +296,7 @@
"success": "Éxito",
"systemInfo": "Información del sistema",
"terminal": "Terminal",
"unknownError": "Error desconocido",
"update": "Actualizar",
"updateAvailable": "Actualización Disponible",
"updated": "Actualizado",
@@ -1108,12 +1108,18 @@
"errorCopyImage": "Error al copiar la imagen: {error}",
"errorLoadingModel": "Error al cargar el modelo",
"errorSaveSetting": "Error al guardar la configuración {id}: {err}",
"failedToAccessBillingPortal": "No se pudo acceder al portal de facturación: {error}",
"failedToApplyTexture": "Error al aplicar textura",
"failedToCreateCustomer": "No se pudo crear el cliente: {error}",
"failedToDownloadFile": "Error al descargar el archivo",
"failedToExportModel": "Error al exportar modelo como {format}",
"failedToFetchBalance": "No se pudo obtener el saldo: {error}",
"failedToFetchLogs": "Error al obtener los registros del servidor",
"failedToInitiateCreditPurchase": "No se pudo iniciar la compra de créditos: {error}",
"failedToPurchaseCredits": "No se pudo comprar créditos: {error}",
"fileLoadError": "No se puede encontrar el flujo de trabajo en {fileName}",
"fileUploadFailed": "Error al subir el archivo",
"firebaseAuthNotInitialized": "Firebase Auth no está inicializado",
"interrupted": "La ejecución ha sido interrumpida",
"migrateToLitegraphReroute": "Los nodos de reroute se eliminarán en futuras versiones. Haz clic para migrar a reroute nativo de litegraph.",
"no3dScene": "No hay escena 3D para aplicar textura",
@@ -1124,7 +1130,8 @@
"pendingTasksDeleted": "Tareas pendientes eliminadas",
"pleaseSelectNodesToGroup": "Por favor, seleccione los nodos (u otros grupos) para crear un grupo para",
"unableToGetModelFilePath": "No se puede obtener la ruta del archivo del modelo",
"updateRequested": "Actualización solicitada"
"updateRequested": "Actualización solicitada",
"userNotAuthenticated": "Usuario no autenticado"
},
"userSelect": {
"enterUsername": "Introduce un nombre de usuario",

View File

@@ -18,7 +18,6 @@
"failed": "Échec de la connexion",
"forgotPassword": "Mot de passe oublié?",
"forgotPasswordError": "Échec de l'envoi de l'e-mail de réinitialisation du mot de passe",
"genericErrorMessage": "Désolé, une erreur s'est produite. Veuillez contacter {supportEmail}.",
"loginButton": "Se connecter",
"loginWithGithub": "Se connecter avec Github",
"loginWithGoogle": "Se connecter avec Google",
@@ -297,6 +296,7 @@
"success": "Succès",
"systemInfo": "Informations système",
"terminal": "Terminal",
"unknownError": "Erreur inconnue",
"update": "Mettre à jour",
"updateAvailable": "Mise à jour disponible",
"updated": "Mis à jour",
@@ -1108,12 +1108,18 @@
"errorCopyImage": "Erreur lors de la copie de l'image: {error}",
"errorLoadingModel": "Erreur lors du chargement du modèle",
"errorSaveSetting": "Erreur lors de l'enregistrement du paramètre {id}: {err}",
"failedToAccessBillingPortal": "Échec de l'accès au portail de facturation : {error}",
"failedToApplyTexture": "Échec de l'application de la texture",
"failedToCreateCustomer": "Échec de la création du client : {error}",
"failedToDownloadFile": "Échec du téléchargement du fichier",
"failedToExportModel": "Échec de l'exportation du modèle en {format}",
"failedToFetchBalance": "Échec de la récupération du solde : {error}",
"failedToFetchLogs": "Échec de la récupération des journaux du serveur",
"failedToInitiateCreditPurchase": "Échec de l'initiation de l'achat de crédits : {error}",
"failedToPurchaseCredits": "Échec de l'achat de crédits : {error}",
"fileLoadError": "Impossible de trouver le flux de travail dans {fileName}",
"fileUploadFailed": "Échec du téléchargement du fichier",
"firebaseAuthNotInitialized": "Authentification Firebase non initialisée",
"interrupted": "L'exécution a été interrompue",
"migrateToLitegraphReroute": "Les nœuds de reroute seront supprimés dans les futures versions. Cliquez pour migrer vers le reroute natif de litegraph.",
"no3dScene": "Aucune scène 3D pour appliquer la texture",
@@ -1124,7 +1130,8 @@
"pendingTasksDeleted": "Tâches en attente supprimées",
"pleaseSelectNodesToGroup": "Veuillez sélectionner les nœuds (ou autres groupes) pour créer un groupe pour",
"unableToGetModelFilePath": "Impossible d'obtenir le chemin du fichier modèle",
"updateRequested": "Mise à jour demandée"
"updateRequested": "Mise à jour demandée",
"userNotAuthenticated": "Utilisateur non authentifié"
},
"userSelect": {
"enterUsername": "Entrez un nom d'utilisateur",

View File

@@ -18,7 +18,6 @@
"failed": "ログイン失敗",
"forgotPassword": "パスワードを忘れましたか?",
"forgotPasswordError": "パスワードリセット用メールの送信に失敗しました",
"genericErrorMessage": "申し訳ありませんが、エラーが発生しました。{supportEmail} までご連絡ください。",
"loginButton": "ログイン",
"loginWithGithub": "Githubでログイン",
"loginWithGoogle": "Googleでログイン",
@@ -297,6 +296,7 @@
"success": "成功",
"systemInfo": "システム情報",
"terminal": "ターミナル",
"unknownError": "不明なエラー",
"update": "更新",
"updateAvailable": "更新が利用可能",
"updated": "更新済み",
@@ -1108,12 +1108,18 @@
"errorCopyImage": "画像のコピーにエラーが発生しました: {error}",
"errorLoadingModel": "モデルの読み込みエラー",
"errorSaveSetting": "設定{id}の保存エラー: {err}",
"failedToAccessBillingPortal": "請求ポータルへのアクセスに失敗しました: {error}",
"failedToApplyTexture": "テクスチャの適用に失敗しました",
"failedToCreateCustomer": "顧客の作成に失敗しました: {error}",
"failedToDownloadFile": "ファイルのダウンロードに失敗しました",
"failedToExportModel": "{format}としてモデルのエクスポートに失敗しました",
"failedToFetchBalance": "残高の取得に失敗しました: {error}",
"failedToFetchLogs": "サーバーログの取得に失敗しました",
"failedToInitiateCreditPurchase": "クレジット購入の開始に失敗しました: {error}",
"failedToPurchaseCredits": "クレジットの購入に失敗しました: {error}",
"fileLoadError": "{fileName}でワークフローが見つかりません",
"fileUploadFailed": "ファイルのアップロードに失敗しました",
"firebaseAuthNotInitialized": "Firebase認証が初期化されていません",
"interrupted": "実行が中断されました",
"migrateToLitegraphReroute": "将来のバージョンではRerouteードが削除されます。litegraph-native rerouteに移行するにはクリックしてください。",
"no3dScene": "テクスチャを適用する3Dシーンがありません",
@@ -1124,7 +1130,8 @@
"pendingTasksDeleted": "保留中のタスクが削除されました",
"pleaseSelectNodesToGroup": "グループを作成するためのノード(または他のグループ)を選択してください",
"unableToGetModelFilePath": "モデルファイルのパスを取得できません",
"updateRequested": "更新が要求されました"
"updateRequested": "更新が要求されました",
"userNotAuthenticated": "ユーザーが認証されていません"
},
"userSelect": {
"enterUsername": "ユーザー名を入力してください",

View File

@@ -18,7 +18,6 @@
"failed": "로그인 실패",
"forgotPassword": "비밀번호를 잊으셨나요?",
"forgotPasswordError": "비밀번호 재설정 이메일 전송에 실패했습니다",
"genericErrorMessage": "죄송합니다. 오류가 발생했습니다. {supportEmail}로 문의해 주세요.",
"loginButton": "로그인",
"loginWithGithub": "Github로 로그인",
"loginWithGoogle": "구글로 로그인",
@@ -297,6 +296,7 @@
"success": "성공",
"systemInfo": "시스템 정보",
"terminal": "터미널",
"unknownError": "알 수 없는 오류",
"update": "업데이트",
"updateAvailable": "업데이트 가능",
"updated": "업데이트 됨",
@@ -1108,12 +1108,18 @@
"errorCopyImage": "이미지 복사 오류: {error}",
"errorLoadingModel": "모델 로딩 오류",
"errorSaveSetting": "설정 {id} 저장 오류: {err}",
"failedToAccessBillingPortal": "결제 포털에 접근하지 못했습니다: {error}",
"failedToApplyTexture": "텍스처 적용에 실패했습니다",
"failedToCreateCustomer": "고객 생성에 실패했습니다: {error}",
"failedToDownloadFile": "파일 다운로드에 실패했습니다",
"failedToExportModel": "{format} 형식으로 모델 내보내기에 실패했습니다",
"failedToFetchBalance": "잔액을 가져오지 못했습니다: {error}",
"failedToFetchLogs": "서버 로그를 가져오는 데 실패했습니다",
"failedToInitiateCreditPurchase": "크레딧 구매를 시작하지 못했습니다: {error}",
"failedToPurchaseCredits": "크레딧 구매에 실패했습니다: {error}",
"fileLoadError": "{fileName}에서 워크플로우를 찾을 수 없습니다",
"fileUploadFailed": "파일 업로드에 실패했습니다",
"firebaseAuthNotInitialized": "Firebase 인증이 초기화되지 않았습니다",
"interrupted": "실행이 중단되었습니다",
"migrateToLitegraphReroute": "향후 버전에서는 Reroute 노드가 제거됩니다. LiteGraph 에서 자체 제공하는 경유점으로 변환하려면 클릭하세요.",
"no3dScene": "텍스처를 적용할 3D 장면이 없습니다",
@@ -1124,7 +1130,8 @@
"pendingTasksDeleted": "보류 중인 작업이 삭제되었습니다",
"pleaseSelectNodesToGroup": "그룹을 만들기 위해 노드(또는 다른 그룹)를 선택해 주세요",
"unableToGetModelFilePath": "모델 파일 경로를 가져올 수 없습니다",
"updateRequested": "업데이트 요청됨"
"updateRequested": "업데이트 요청됨",
"userNotAuthenticated": "사용자가 인증되지 않았습니다"
},
"userSelect": {
"enterUsername": "사용자 이름 입력",

View File

@@ -18,7 +18,6 @@
"failed": "Вход не удался",
"forgotPassword": "Забыли пароль?",
"forgotPasswordError": "Не удалось отправить письмо для сброса пароля",
"genericErrorMessage": "Извините, произошла ошибка. Пожалуйста, свяжитесь с {supportEmail}.",
"loginButton": "Войти",
"loginWithGithub": "Войти через Github",
"loginWithGoogle": "Войти через Google",
@@ -297,6 +296,7 @@
"success": "Успех",
"systemInfo": "Информация о системе",
"terminal": "Терминал",
"unknownError": "Неизвестная ошибка",
"update": "Обновить",
"updateAvailable": "Доступно обновление",
"updated": "Обновлено",
@@ -1108,12 +1108,18 @@
"errorCopyImage": "Ошибка копирования изображения: {error}",
"errorLoadingModel": "Ошибка загрузки модели",
"errorSaveSetting": "Ошибка сохранения настройки {id}: {err}",
"failedToAccessBillingPortal": "Не удалось получить доступ к биллинговому порталу: {error}",
"failedToApplyTexture": "Не удалось применить текстуру",
"failedToCreateCustomer": "Не удалось создать клиента: {error}",
"failedToDownloadFile": "Не удалось скачать файл",
"failedToExportModel": "Не удалось экспортировать модель как {format}",
"failedToFetchBalance": "Не удалось получить баланс: {error}",
"failedToFetchLogs": "Не удалось получить серверные логи",
"failedToInitiateCreditPurchase": "Не удалось начать покупку кредитов: {error}",
"failedToPurchaseCredits": "Не удалось купить кредиты: {error}",
"fileLoadError": "Не удалось найти рабочий процесс в {fileName}",
"fileUploadFailed": "Не удалось загрузить файл",
"firebaseAuthNotInitialized": "Firebase Auth не инициализирован",
"interrupted": "Выполнение было прервано",
"migrateToLitegraphReroute": "Узлы перенаправления будут удалены в будущих версиях. Нажмите, чтобы перейти на litegraph-native reroute.",
"no3dScene": "Нет 3D сцены для применения текстуры",
@@ -1124,7 +1130,8 @@
"pendingTasksDeleted": "Ожидающие задачи удалены",
"pleaseSelectNodesToGroup": "Пожалуйста, выберите узлы (или другие группы) для создания группы",
"unableToGetModelFilePath": "Не удалось получить путь к файлу модели",
"updateRequested": "Запрошено обновление"
"updateRequested": "Запрошено обновление",
"userNotAuthenticated": "Пользователь не аутентифицирован"
},
"userSelect": {
"enterUsername": "Введите имя пользователя",

View File

@@ -18,7 +18,6 @@
"failed": "登录失败",
"forgotPassword": "忘记密码?",
"forgotPasswordError": "发送重置密码邮件失败",
"genericErrorMessage": "抱歉,我们遇到了一些错误。请联系 {supportEmail}。",
"loginButton": "登录",
"loginWithGithub": "使用Github登录",
"loginWithGoogle": "使用Google登录",
@@ -297,6 +296,7 @@
"success": "成功",
"systemInfo": "系统信息",
"terminal": "终端",
"unknownError": "未知错误",
"update": "更新",
"updateAvailable": "有更新可用",
"updated": "已更新",
@@ -1108,12 +1108,18 @@
"errorCopyImage": "复制图片出错:{error}",
"errorLoadingModel": "加载模型出错",
"errorSaveSetting": "保存设置 {id} 出错:{err}",
"failedToAccessBillingPortal": "访问账单门户失败:{error}",
"failedToApplyTexture": "应用纹理失败",
"failedToCreateCustomer": "创建客户失败:{error}",
"failedToDownloadFile": "文件下载失败",
"failedToExportModel": "无法将模型导出为 {format}",
"failedToFetchBalance": "获取余额失败:{error}",
"failedToFetchLogs": "无法获取服务器日志",
"failedToInitiateCreditPurchase": "发起积分购买失败:{error}",
"failedToPurchaseCredits": "购买积分失败:{error}",
"fileLoadError": "无法在 {fileName} 中找到工作流",
"fileUploadFailed": "文件上传失败",
"firebaseAuthNotInitialized": "Firebase 认证未初始化",
"interrupted": "执行已被中断",
"migrateToLitegraphReroute": "将来的版本中将删除重定向节点。点击以迁移到litegraph-native重定向。",
"no3dScene": "没有3D场景可以应用纹理",
@@ -1124,7 +1130,8 @@
"pendingTasksDeleted": "待处理任务已删除",
"pleaseSelectNodesToGroup": "请选取节点(或其他组)以创建分组",
"unableToGetModelFilePath": "无法获取模型文件路径",
"updateRequested": "已请求更新"
"updateRequested": "已请求更新",
"userNotAuthenticated": "用户未认证"
},
"userSelect": {
"enterUsername": "输入用户名",

View File

@@ -0,0 +1,90 @@
import { useErrorHandling } from '@/composables/useErrorHandling'
import { t } from '@/i18n'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
import { useToastStore } from '@/stores/toastStore'
import { usdToMicros } from '@/utils/formatUtil'
/**
* Service for Firebase Auth actions.
* All actions are wrapped with error handling.
* @returns {Object} - Object containing all Firebase Auth actions
*/
export const useFirebaseAuthService = () => {
const authStore = useFirebaseAuthStore()
const toastStore = useToastStore()
const { wrapWithErrorHandlingAsync } = useErrorHandling()
const reportError = (error: unknown) => {
toastStore.add({
severity: 'error',
summary: t('g.error'),
detail: error instanceof Error ? error.message : t('g.unknownError'),
life: 5000
})
}
const logout = wrapWithErrorHandlingAsync(async () => {
await authStore.logout()
toastStore.add({
severity: 'success',
summary: t('auth.signOut.success'),
detail: t('auth.signOut.successDetail'),
life: 5000
})
}, reportError)
const sendPasswordReset = wrapWithErrorHandlingAsync(
async (email: string) => {
await authStore.sendPasswordReset(email)
toastStore.add({
severity: 'success',
summary: t('auth.login.passwordResetSent'),
detail: t('auth.login.passwordResetSentDetail'),
life: 5000
})
},
reportError
)
const purchaseCredits = wrapWithErrorHandlingAsync(async (amount: number) => {
const response = await authStore.initiateCreditPurchase({
amount_micros: usdToMicros(amount),
currency: 'usd'
})
if (!response.checkout_url) {
throw new Error(
t('toastMessages.failedToPurchaseCredits', {
error: 'No checkout URL returned'
})
)
}
// Go to Stripe checkout page
window.open(response.checkout_url, '_blank')
}, reportError)
const accessBillingPortal = wrapWithErrorHandlingAsync(async () => {
const response = await authStore.accessBillingPortal()
if (!response.billing_portal_url) {
throw new Error(
t('toastMessages.failedToAccessBillingPortal', {
error: 'No billing portal URL returned'
})
)
}
window.open(response.billing_portal_url, '_blank')
}, reportError)
const fetchBalance = wrapWithErrorHandlingAsync(async () => {
await authStore.fetchBalance()
}, reportError)
return {
logout,
sendPasswordReset,
purchaseCredits,
accessBillingPortal,
fetchBalance
}
}

View File

@@ -21,8 +21,6 @@ import { COMFY_API_BASE_URL } from '@/config/comfyApi'
import { t } from '@/i18n'
import { operations } from '@/types/comfyRegistryTypes'
import { useToastStore } from './toastStore'
type CreditPurchaseResponse =
operations['InitiateCreditPurchase']['responses']['201']['content']['application/json']
type CreditPurchasePayload =
@@ -36,10 +34,16 @@ type AccessBillingPortalResponse =
type AccessBillingPortalReqBody =
operations['AccessBillingPortal']['requestBody']
export class FirebaseAuthStoreError extends Error {
constructor(message: string) {
super(message)
this.name = 'FirebaseAuthStoreError'
}
}
export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
// State
const loading = ref(false)
const error = ref<string | null>(null)
const currentUser = ref<User | null>(null)
const isInitialized = ref(false)
const customerCreated = ref(false)
@@ -75,16 +79,6 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
lastBalanceUpdateTime.value = null
})
const showAuthErrorToast = () => {
useToastStore().add({
summary: t('g.error'),
detail: t('auth.login.genericErrorMessage', {
supportEmail: 'support@comfy.org'
}),
severity: 'error'
})
}
const getIdToken = async (): Promise<string | null> => {
if (currentUser.value) {
return currentUser.value.getIdToken()
@@ -97,9 +91,10 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
try {
const token = await getIdToken()
if (!token) {
error.value = 'Cannot fetch balance: User not authenticated'
isFetchingBalance.value = false
return null
throw new FirebaseAuthStoreError(
t('toastMessages.userNotAuthenticated')
)
}
const response = await fetch(`${COMFY_API_BASE_URL}/customers/balance`, {
@@ -114,8 +109,11 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
return null
}
const errorData = await response.json()
error.value = `Failed to fetch balance: ${errorData.message}`
return null
throw new FirebaseAuthStoreError(
t('toastMessages.failedToFetchBalance', {
error: errorData.message
})
)
}
const balanceData = await response.json()
@@ -123,9 +121,6 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
lastBalanceUpdateTime.value = new Date()
balance.value = balanceData
return balanceData
} catch (e) {
error.value = `Failed to fetch balance: ${e}`
return null
} finally {
isFetchingBalance.value = false
}
@@ -142,15 +137,21 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
}
})
if (!createCustomerRes.ok) {
throw new Error(
`Failed to create customer: ${createCustomerRes.statusText}`
throw new FirebaseAuthStoreError(
t('toastMessages.failedToCreateCustomer', {
error: createCustomerRes.statusText
})
)
}
const createCustomerResJson: CreateCustomerResponse =
await createCustomerRes.json()
if (!createCustomerResJson?.id) {
throw new Error('Failed to create customer: No customer ID returned')
throw new FirebaseAuthStoreError(
t('toastMessages.failedToCreateCustomer', {
error: 'No customer ID returned'
})
)
}
return createCustomerResJson
@@ -162,10 +163,12 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
createCustomer?: boolean
} = {}
): Promise<T> => {
if (!auth) throw new Error('Firebase Auth not initialized')
if (!auth)
throw new FirebaseAuthStoreError(
t('toastMessages.firebaseAuthNotInitialized')
)
loading.value = true
error.value = null
try {
const result = await action(auth)
@@ -180,10 +183,6 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
}
return result
} catch (e: unknown) {
error.value = e instanceof Error ? e.message : 'Unknown error'
showAuthErrorToast()
throw e
} finally {
loading.value = false
}
@@ -232,11 +231,10 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
const addCredits = async (
requestBodyContent: CreditPurchasePayload
): Promise<CreditPurchaseResponse | null> => {
): Promise<CreditPurchaseResponse> => {
const token = await getIdToken()
if (!token) {
error.value = 'Cannot add credits: User not authenticated'
return null
throw new FirebaseAuthStoreError(t('toastMessages.userNotAuthenticated'))
}
// Ensure customer was created during login/registration
@@ -256,9 +254,11 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
if (!response.ok) {
const errorData = await response.json()
error.value = `Failed to initiate credit purchase: ${errorData.message}`
showAuthErrorToast()
return null
throw new FirebaseAuthStoreError(
t('toastMessages.failedToInitiateCreditPurchase', {
error: errorData.message
})
)
}
return response.json()
@@ -266,16 +266,15 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
const initiateCreditPurchase = async (
requestBodyContent: CreditPurchasePayload
): Promise<CreditPurchaseResponse | null> =>
): Promise<CreditPurchaseResponse> =>
executeAuthAction((_) => addCredits(requestBodyContent))
const accessBillingPortal = async (
requestBody?: AccessBillingPortalReqBody
): Promise<AccessBillingPortalResponse | null> => {
): Promise<AccessBillingPortalResponse> => {
const token = await getIdToken()
if (!token) {
error.value = 'Cannot access billing portal: User not authenticated'
return null
throw new FirebaseAuthStoreError(t('toastMessages.userNotAuthenticated'))
}
const response = await fetch(`${COMFY_API_BASE_URL}/customers/billing`, {
@@ -291,9 +290,11 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
if (!response.ok) {
const errorData = await response.json()
error.value = `Failed to access billing portal: ${errorData.message}`
showAuthErrorToast()
return null
throw new FirebaseAuthStoreError(
t('toastMessages.failedToAccessBillingPortal', {
error: errorData.message
})
)
}
return response.json()
@@ -302,7 +303,6 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
return {
// State
loading,
error,
currentUser,
isInitialized,
balance,

View File

@@ -135,7 +135,6 @@ describe('useFirebaseAuthStore', () => {
expect(store.userEmail).toBe('test@example.com')
expect(store.userId).toBe('test-user-id')
expect(store.loading).toBe(false)
expect(store.error).toBe(null)
})
it('should set persistence to local storage on initialization', () => {
@@ -158,17 +157,12 @@ describe('useFirebaseAuthStore', () => {
// Error expected
}
expect(store.error).toBe('Invalid password')
// Now, succeed on next attempt
vi.mocked(firebaseAuth.signInWithEmailAndPassword).mockResolvedValueOnce({
user: mockUser
} as any)
await store.login('test@example.com', 'correct-password')
// Error should be cleared
expect(store.error).toBe(null)
})
describe('login', () => {
@@ -187,7 +181,6 @@ describe('useFirebaseAuthStore', () => {
)
expect(result).toEqual(mockUserCredential)
expect(store.loading).toBe(false)
expect(store.error).toBe(null)
})
it('should handle login errors', async () => {
@@ -206,7 +199,6 @@ describe('useFirebaseAuthStore', () => {
'wrong-password'
)
expect(store.loading).toBe(false)
expect(store.error).toBe('Invalid password')
})
it('should handle concurrent login attempts correctly', async () => {
@@ -243,7 +235,6 @@ describe('useFirebaseAuthStore', () => {
)
expect(result).toEqual(mockUserCredential)
expect(store.loading).toBe(false)
expect(store.error).toBe(null)
})
it('should handle registration errors', async () => {
@@ -262,7 +253,6 @@ describe('useFirebaseAuthStore', () => {
'password'
)
expect(store.loading).toBe(false)
expect(store.error).toBe('Email already in use')
})
})
@@ -282,7 +272,6 @@ describe('useFirebaseAuthStore', () => {
await expect(store.logout()).rejects.toThrow('Network error')
expect(firebaseAuth.signOut).toHaveBeenCalledWith(mockAuth)
expect(store.error).toBe('Network error')
})
})
@@ -356,7 +345,6 @@ describe('useFirebaseAuthStore', () => {
)
expect(result).toEqual(mockUserCredential)
expect(store.loading).toBe(false)
expect(store.error).toBe(null)
})
it('should handle Google sign in errors', async () => {
@@ -372,7 +360,6 @@ describe('useFirebaseAuthStore', () => {
expect.any(firebaseAuth.GoogleAuthProvider)
)
expect(store.loading).toBe(false)
expect(store.error).toBe('Google authentication failed')
})
})
@@ -391,7 +378,6 @@ describe('useFirebaseAuthStore', () => {
)
expect(result).toEqual(mockUserCredential)
expect(store.loading).toBe(false)
expect(store.error).toBe(null)
})
it('should handle Github sign in errors', async () => {
@@ -407,7 +393,6 @@ describe('useFirebaseAuthStore', () => {
expect.any(firebaseAuth.GithubAuthProvider)
)
expect(store.loading).toBe(false)
expect(store.error).toBe('Github authentication failed')
})
})