/** * @license * Copyright 2025 Qwen * SPDX-License-Identifier: Apache-2.0 */ import type React from 'react'; import { useState, useEffect, useMemo } from 'react'; import { Box, Text } from 'ink'; import Spinner from 'ink-spinner'; import Link from 'ink-link'; import qrcode from 'qrcode-terminal'; import { Colors } from '../colors.js'; import type { DeviceAuthorizationData } from '@qwen-code/qwen-code-core'; import { useKeypress } from '../hooks/useKeypress.js'; import { t } from '../../i18n/index.js'; interface QwenOAuthProgressProps { onTimeout: () => void; onCancel: () => void; deviceAuth?: DeviceAuthorizationData; authStatus?: | 'idle' | 'polling' | 'success' | 'error' | 'timeout' | 'rate_limit'; authMessage?: string | null; } /** * Static QR Code Display Component * Renders the QR code and URL once and doesn't re-render unless the URL changes */ function QrCodeDisplay({ verificationUrl, qrCodeData, }: { verificationUrl: string; qrCodeData: string | null; }): React.JSX.Element | null { if (!qrCodeData) { return null; } return ( {t('Qwen OAuth Authentication')} {t('Please visit this URL to authorize:')} {verificationUrl} {t('Or scan the QR code below:')} {qrCodeData} ); } /** * Dynamic Status Display Component * Shows the loading spinner, timer, and status messages */ function StatusDisplay({ timeRemaining, dots, }: { timeRemaining: number; dots: string; }): React.JSX.Element { const formatTime = (seconds: number): string => { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; }; return ( {t('Waiting for authorization')} {dots} {t('Time remaining:')} {formatTime(timeRemaining)} {t('(Press ESC or CTRL+C to cancel)')} ); } export function QwenOAuthProgress({ onTimeout, onCancel, deviceAuth, authStatus, authMessage, }: QwenOAuthProgressProps): React.JSX.Element { const defaultTimeout = deviceAuth?.expires_in || 300; // Default 5 minutes const [timeRemaining, setTimeRemaining] = useState(defaultTimeout); const [dots, setDots] = useState(''); const [qrCodeData, setQrCodeData] = useState(null); useKeypress( (key) => { if (authStatus === 'timeout' || authStatus === 'error') { // Any key press in timeout or error state should trigger cancel to return to auth dialog onCancel(); } else if (key.name === 'escape' || (key.ctrl && key.name === 'c')) { onCancel(); } }, { isActive: true }, ); // Generate QR code once when device auth is available useEffect(() => { if (!deviceAuth?.verification_uri_complete) { return; } const generateQR = () => { try { qrcode.generate( deviceAuth.verification_uri_complete, { small: true }, (qrcode: string) => { setQrCodeData(qrcode); }, ); } catch (error) { console.error('Failed to generate QR code:', error); setQrCodeData(null); } }; generateQR(); }, [deviceAuth?.verification_uri_complete]); // Countdown timer useEffect(() => { const timer = setInterval(() => { setTimeRemaining((prev) => { if (prev <= 1) { onTimeout(); return 0; } return prev - 1; }); }, 1000); return () => clearInterval(timer); }, [onTimeout]); // Animated dots useEffect(() => { const dotsTimer = setInterval(() => { setDots((prev) => { if (prev.length >= 3) return ''; return prev + '.'; }); }, 500); return () => clearInterval(dotsTimer); }, []); // Memoize the QR code display to prevent unnecessary re-renders const qrCodeDisplay = useMemo(() => { if (!deviceAuth?.verification_uri_complete) return null; return ( ); }, [deviceAuth?.verification_uri_complete, qrCodeData]); // Handle timeout state if (authStatus === 'timeout') { return ( {t('Qwen OAuth Authentication Timeout')} {authMessage || t( 'OAuth token expired (over {{seconds}} seconds). Please select authentication method again.', { seconds: defaultTimeout.toString(), }, )} {t('Press any key to return to authentication type selection.')} ); } if (authStatus === 'error') { return ( Qwen OAuth Authentication Error {authMessage || 'An error occurred during authentication. Please try again.'} Press any key to return to authentication type selection. ); } // Show loading state when no device auth is available yet if (!deviceAuth) { return ( {t('Waiting for Qwen OAuth authentication...')} {t('Time remaining:')} {Math.floor(timeRemaining / 60)}: {(timeRemaining % 60).toString().padStart(2, '0')} {t('(Press ESC or CTRL+C to cancel)')} ); } return ( {/* Static QR Code Display */} {qrCodeDisplay} {/* Dynamic Status Display */} ); }