import { useEffect, useRef, useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { useNotificationStore } from '@/stores'; import { oauthApi, type OAuthProvider, type IFlowCookieAuthResponse } from '@/services/api/oauth'; import { isLocalhost } from '@/utils/connection'; import styles from './OAuthPage.module.scss'; interface ProviderState { url?: string; state?: string; status?: 'idle' | 'waiting' | 'success' | 'error'; error?: string; polling?: boolean; } interface IFlowCookieState { cookie: string; loading: boolean; result?: IFlowCookieAuthResponse; error?: string; errorType?: 'error' | 'warning'; } const PROVIDERS: { id: OAuthProvider; titleKey: string; hintKey: string; urlLabelKey: string }[] = [ { id: 'codex', titleKey: 'auth_login.codex_oauth_title', hintKey: 'auth_login.codex_oauth_hint', urlLabelKey: 'auth_login.codex_oauth_url_label' }, { id: 'anthropic', titleKey: 'auth_login.anthropic_oauth_title', hintKey: 'auth_login.anthropic_oauth_hint', urlLabelKey: 'auth_login.anthropic_oauth_url_label' }, { id: 'antigravity', titleKey: 'auth_login.antigravity_oauth_title', hintKey: 'auth_login.antigravity_oauth_hint', urlLabelKey: 'auth_login.antigravity_oauth_url_label' }, { id: 'gemini-cli', titleKey: 'auth_login.gemini_cli_oauth_title', hintKey: 'auth_login.gemini_cli_oauth_hint', urlLabelKey: 'auth_login.gemini_cli_oauth_url_label' }, { id: 'qwen', titleKey: 'auth_login.qwen_oauth_title', hintKey: 'auth_login.qwen_oauth_hint', urlLabelKey: 'auth_login.qwen_oauth_url_label' }, { id: 'iflow', titleKey: 'auth_login.iflow_oauth_title', hintKey: 'auth_login.iflow_oauth_hint', urlLabelKey: 'auth_login.iflow_oauth_url_label' } ]; export function OAuthPage() { const { t } = useTranslation(); const { showNotification } = useNotificationStore(); const [states, setStates] = useState>({} as Record); const [iflowCookie, setIflowCookie] = useState({ cookie: '', loading: false }); const timers = useRef>({}); // 检测是否为本地访问 const isLocal = useMemo(() => isLocalhost(window.location.hostname), []); useEffect(() => { return () => { Object.values(timers.current).forEach((timer) => window.clearInterval(timer)); }; }, []); const startPolling = (provider: OAuthProvider, state: string) => { if (timers.current[provider]) { clearInterval(timers.current[provider]); } const timer = window.setInterval(async () => { try { const res = await oauthApi.getAuthStatus(state); if (res.status === 'ok') { setStates((prev) => ({ ...prev, [provider]: { ...prev[provider], status: 'success', polling: false } })); showNotification(t('auth_login.codex_oauth_status_success'), 'success'); window.clearInterval(timer); delete timers.current[provider]; } else if (res.status === 'error') { setStates((prev) => ({ ...prev, [provider]: { ...prev[provider], status: 'error', error: res.error, polling: false } })); showNotification(`${t('auth_login.codex_oauth_status_error')} ${res.error || ''}`, 'error'); window.clearInterval(timer); delete timers.current[provider]; } } catch (err: any) { setStates((prev) => ({ ...prev, [provider]: { ...prev[provider], status: 'error', error: err?.message, polling: false } })); window.clearInterval(timer); delete timers.current[provider]; } }, 3000); timers.current[provider] = timer; }; const startAuth = async (provider: OAuthProvider) => { setStates((prev) => ({ ...prev, [provider]: { ...prev[provider], status: 'waiting', polling: true, error: undefined } })); try { const res = await oauthApi.startAuth(provider); setStates((prev) => ({ ...prev, [provider]: { ...prev[provider], url: res.url, state: res.state, status: 'waiting', polling: true } })); if (res.state) { startPolling(provider, res.state); } } catch (err: any) { setStates((prev) => ({ ...prev, [provider]: { ...prev[provider], status: 'error', error: err?.message, polling: false } })); showNotification(`${t('auth_login.codex_oauth_start_error')} ${err?.message || ''}`, 'error'); } }; const copyLink = async (url?: string) => { if (!url) return; try { await navigator.clipboard.writeText(url); showNotification(t('notification.link_copied'), 'success'); } catch { showNotification('Copy failed', 'error'); } }; const submitIflowCookie = async () => { const cookie = iflowCookie.cookie.trim(); if (!cookie) { showNotification(t('auth_login.iflow_cookie_required'), 'warning'); return; } setIflowCookie((prev) => ({ ...prev, loading: true, error: undefined, errorType: undefined, result: undefined })); try { const res = await oauthApi.iflowCookieAuth(cookie); if (res.status === 'ok') { setIflowCookie((prev) => ({ ...prev, loading: false, result: res })); showNotification(t('auth_login.iflow_cookie_status_success'), 'success'); } else { setIflowCookie((prev) => ({ ...prev, loading: false, error: res.error, errorType: 'error' })); showNotification(`${t('auth_login.iflow_cookie_status_error')} ${res.error || ''}`, 'error'); } } catch (err: any) { if (err?.status === 409) { const message = t('auth_login.iflow_cookie_config_duplicate'); setIflowCookie((prev) => ({ ...prev, loading: false, error: message, errorType: 'warning' })); showNotification(message, 'warning'); return; } setIflowCookie((prev) => ({ ...prev, loading: false, error: err?.message, errorType: 'error' })); showNotification(`${t('auth_login.iflow_cookie_start_error')} ${err?.message || ''}`, 'error'); } }; return (

{t('nav.oauth', { defaultValue: 'OAuth' })}

{PROVIDERS.map((provider) => { const state = states[provider.id] || {}; // 非本地访问时禁用所有 OAuth 登录方式 const isDisabled = !isLocal; return (
startAuth(provider.id)} loading={state.polling} disabled={isDisabled} > {t('common.login')} } >
{t(provider.hintKey)}
{isDisabled && (
{t('auth_login.remote_access_disabled')}
)} {!isDisabled && state.url && (
{t(provider.urlLabelKey)}
{state.url}
)} {!isDisabled && state.status && state.status !== 'idle' && (
{state.status === 'success' ? t('auth_login.codex_oauth_status_success') : state.status === 'error' ? `${t('auth_login.codex_oauth_status_error')} ${state.error || ''}` : t('auth_login.codex_oauth_status_waiting')}
)}
); })} {/* iFlow Cookie 登录 */} {t('auth_login.iflow_cookie_button')} } >
{t('auth_login.iflow_cookie_hint')}
{t('auth_login.iflow_cookie_key_hint')}
setIflowCookie((prev) => ({ ...prev, cookie: e.target.value }))} placeholder={t('auth_login.iflow_cookie_placeholder')} />
{iflowCookie.error && (
{iflowCookie.errorType === 'warning' ? t('auth_login.iflow_cookie_status_duplicate') : t('auth_login.iflow_cookie_status_error')}{' '} {iflowCookie.error}
)} {iflowCookie.result && iflowCookie.result.status === 'ok' && (
{t('auth_login.iflow_cookie_result_title')}
{iflowCookie.result.email && (
{t('auth_login.iflow_cookie_result_email')} {iflowCookie.result.email}
)} {iflowCookie.result.expired && (
{t('auth_login.iflow_cookie_result_expired')} {iflowCookie.result.expired}
)} {iflowCookie.result.saved_path && (
{t('auth_login.iflow_cookie_result_path')} {iflowCookie.result.saved_path}
)} {iflowCookie.result.type && (
{t('auth_login.iflow_cookie_result_type')} {iflowCookie.result.type}
)}
)}
); }