feat: enhance API key normalization and improve Space OAuth callback handling

This commit is contained in:
fdc310
2026-05-11 15:03:30 +08:00
parent ea13ef87f2
commit 6713b57d01
9 changed files with 153 additions and 14 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect, useState, useCallback, Suspense } from 'react';
import { useEffect, useState, useCallback, Suspense, useRef } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { httpClient } from '@/app/infra/http/HttpClient';
import { toast } from 'sonner';
@@ -20,10 +20,39 @@ import { Button } from '@/components/ui/button';
import { LoadingSpinner } from '@/components/ui/loading-spinner';
import langbotIcon from '@/app/assets/langbot-logo.webp';
type SpaceOAuthLoginResult = {
token: string;
user: string;
};
const pendingSpaceOAuthLogins = new Map<
string,
Promise<SpaceOAuthLoginResult>
>();
function getOrCreateSpaceOAuthLoginPromise(
authCode: string,
): Promise<SpaceOAuthLoginResult> {
const pendingRequest = pendingSpaceOAuthLogins.get(authCode);
if (pendingRequest) {
return pendingRequest;
}
const requestPromise = httpClient
.exchangeSpaceOAuthCode(authCode)
.finally(() => {
pendingSpaceOAuthLogins.delete(authCode);
});
pendingSpaceOAuthLogins.set(authCode, requestPromise);
return requestPromise;
}
function SpaceOAuthCallbackContent() {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const { t } = useTranslation();
const isMountedRef = useRef(true);
const [status, setStatus] = useState<
'loading' | 'confirm' | 'success' | 'error'
@@ -37,7 +66,11 @@ function SpaceOAuthCallbackContent() {
const handleOAuthCallback = useCallback(
async (authCode: string) => {
try {
const response = await httpClient.exchangeSpaceOAuthCode(authCode);
const response = await getOrCreateSpaceOAuthLoginPromise(authCode);
if (!isMountedRef.current) {
return;
}
localStorage.setItem('token', response.token);
if (response.user) {
localStorage.setItem('userEmail', response.user);
@@ -52,6 +85,10 @@ function SpaceOAuthCallbackContent() {
navigate(redirectTo);
}, 1000);
} catch (err) {
if (!isMountedRef.current) {
return;
}
setStatus('error');
const errorObj = err as { msg?: string };
const errMsg = (errorObj?.msg || '').toLowerCase();
@@ -72,6 +109,10 @@ function SpaceOAuthCallbackContent() {
setIsProcessing(true);
try {
const response = await httpClient.bindSpaceAccount(authCode, state);
if (!isMountedRef.current) {
return;
}
localStorage.setItem('token', response.token);
if (response.user) {
localStorage.setItem('userEmail', response.user);
@@ -82,6 +123,10 @@ function SpaceOAuthCallbackContent() {
navigate('/home');
}, 1000);
} catch (err) {
if (!isMountedRef.current) {
return;
}
setStatus('error');
const errorObj = err as { msg?: string };
const errMsg = (errorObj?.msg || '').toLowerCase();
@@ -91,13 +136,17 @@ function SpaceOAuthCallbackContent() {
setErrorMessage(t('account.bindSpaceFailed'));
}
} finally {
setIsProcessing(false);
if (isMountedRef.current) {
setIsProcessing(false);
}
}
},
[navigate, t],
);
useEffect(() => {
isMountedRef.current = true;
const authCode = searchParams.get('code');
const error = searchParams.get('error');
const errorDescription = searchParams.get('error_description');
@@ -135,6 +184,9 @@ function SpaceOAuthCallbackContent() {
// Normal login/register mode
handleOAuthCallback(authCode);
}
return () => {
isMountedRef.current = false;
};
}, [searchParams, handleOAuthCallback, t]);
const handleConfirmBind = () => {