From bcdf2a6c24fc992d32b4ae883f2a34c8e64969e3 Mon Sep 17 00:00:00 2001 From: Draculabo Date: Sat, 11 Apr 2026 16:45:39 +0800 Subject: [PATCH] feat(cloud-oauth): support selectable google oauth clients --- src/components/CloudAccountList.tsx | 98 +++- src/hooks/useCloudAccounts.ts | 22 + src/ipc/cloud/handler.ts | 310 ++++++++++- src/ipc/cloud/router.ts | 79 ++- src/localization/i18n.ts | 15 + src/services/GoogleAPIService.ts | 621 ++++++++++++++++++---- src/tests/unit/google-api-timeout.test.ts | 337 ++++++++++++ src/utils/googleAuthSubmission.ts | 13 + 8 files changed, 1370 insertions(+), 125 deletions(-) create mode 100644 src/utils/googleAuthSubmission.ts diff --git a/src/components/CloudAccountList.tsx b/src/components/CloudAccountList.tsx index 966a6e7..68edc7d 100644 --- a/src/components/CloudAccountList.tsx +++ b/src/components/CloudAccountList.tsx @@ -8,6 +8,8 @@ import { useSetAutoSwitchEnabled, useForcePollCloudMonitor, useSyncLocalAccount, + useOAuthClients, + useSetActiveOAuthClient, startAuthFlow, useExportCloudAccounts, useImportCloudAccounts, @@ -77,6 +79,7 @@ import { getAccountSortValue, type QuotaStatus, } from '@/utils/quota-display'; +import { shouldAutoSubmitGoogleAuthCode } from '@/utils/googleAuthSubmission'; export type GridLayout = 'auto' | '2-col' | '3-col' | 'list' | 'compact'; @@ -113,9 +116,12 @@ export function CloudAccountList() { const { data: autoSwitchEnabled, isLoading: isSettingsLoading } = useAutoSwitchEnabled(); const setAutoSwitchMutation = useSetAutoSwitchEnabled(); const forcePollMutation = useForcePollCloudMonitor(); + const { data: oauthClients = [], isLoading: isOAuthClientsLoading } = useOAuthClients(); + const setActiveOAuthClientMutation = useSetActiveOAuthClient(); const { toast } = useToast(); const lastLoadErrorToastAtRef = useRef(0); + const lastSubmittedAuthCodeRef = useRef(null); const gridLayout: GridLayout = (config?.grid_layout as GridLayout) || 'auto'; @@ -195,6 +201,7 @@ export function CloudAccountList() { const [isAddDialogOpen, setIsAddDialogOpen] = useState(false); const [authCode, setAuthCode] = useState(''); + const [selectedOAuthClientKey, setSelectedOAuthClientKey] = useState(''); const [identityAccount, setIdentityAccount] = useState(null); const [isExportDialogOpen, setIsExportDialogOpen] = useState(false); const [isImportDialogOpen, setIsImportDialogOpen] = useState(false); @@ -219,12 +226,18 @@ export function CloudAccountList() { if (!codeToUse) { return; } + lastSubmittedAuthCodeRef.current = codeToUse; addMutation.mutate( - { authCode: codeToUse }, + { + authCode: codeToUse, + oauthClientKey: + selectedOAuthClientKey || oauthClients.find((client) => client.is_active)?.key, + }, { onSuccess: () => { setIsAddDialogOpen(false); setAuthCode(''); + lastSubmittedAuthCodeRef.current = null; toast({ title: t('cloud.toast.addSuccess') }); }, onError: (err) => { @@ -237,14 +250,25 @@ export function CloudAccountList() { }, ); }, - [addMutation, authCode, t, toast], + [addMutation, authCode, oauthClients, selectedOAuthClientKey, t, toast], ); + + useEffect(() => { + if (selectedOAuthClientKey !== '') { + return; + } + const activeClientKey = oauthClients.find((client) => client.is_active)?.key; + if (activeClientKey) { + setSelectedOAuthClientKey(activeClientKey); + } + }, [oauthClients, selectedOAuthClientKey]); // Listen for Google Auth Code useEffect(() => { if (window.electron?.onGoogleAuthCode) { console.log('[OAuth] Registering Google auth code IPC listener'); const cleanup = window.electron.onGoogleAuthCode((code) => { console.log('[OAuth] Received Google auth code via IPC:', code?.substring(0, 10) + '...'); + lastSubmittedAuthCodeRef.current = null; setAuthCode(code); }); return cleanup; @@ -253,7 +277,14 @@ export function CloudAccountList() { // Auto-submit when authCode is set and dialog is open useEffect(() => { - if (authCode && isAddDialogOpen && !addMutation.isPending) { + if ( + shouldAutoSubmitGoogleAuthCode({ + authCode, + isAddDialogOpen, + isPending: addMutation.isPending, + lastSubmittedAuthCode: lastSubmittedAuthCodeRef.current, + }) + ) { console.log('[OAuth] Auto-submitting Google auth code'); submitAuthCode(authCode); } @@ -399,7 +430,16 @@ export function CloudAccountList() { const openGoogleAuthSignIn = async () => { try { - await startAuthFlow(); + lastSubmittedAuthCodeRef.current = null; + const effectiveClientKey = + selectedOAuthClientKey || oauthClients.find((client) => client.is_active)?.key; + await startAuthFlow( + effectiveClientKey + ? { + oauthClientKey: effectiveClientKey, + } + : undefined, + ); } catch (e) { toast({ title: t('cloud.toast.startAuthFailed'), @@ -833,7 +873,16 @@ export function CloudAccountList() { - + { + setIsAddDialogOpen(open); + if (!open) { + setAuthCode(''); + lastSubmittedAuthCodeRef.current = null; + } + }} + >