fix: 修复页面刷新后邮件列表为空的问题 refactor: 优化Turnstile验证流程

This commit is contained in:
gujiangjiang committed 2025-10-17 05:37:23 +08:00
1 parent be9e923baf
commit dc8a25cd32
3 files changed
+57 -26

No files matched your search

+29 -12
View File
@@ -9,7 +9,7 @@ import { Toaster, toast } from 'react-hot-toast';
import { MailList } from '../components/MailList.tsx';
import { CopyButton } from '../components/CopyButton.tsx';
// feat: 导入 loginByPassword
import { getEmails, deleteEmails, loginByPassword } from '../services/api.ts';
import { getEmails, deleteEmails, loginByPassword, verifyTurnstile } from '../services/api.ts';
import { useConfig } from '../hooks/useConfig.ts';
import { getRandomCharacter } from '../lib/utlis.ts';
@@ -22,7 +22,6 @@ import ShieldCheck from "../components/icons/ShieldCheck.tsx";
import Cloudflare from "../components/icons/Cloudflare.tsx";
import Clock from "../components/icons/Clock.tsx";
import Info from "../components/icons/Info.tsx";
import Close from '../components/icons/Close.tsx';
// refactor: 将导入从 'database' 包更改为本地的类型定义文件
import type { Email } from '../database_types.ts';
@@ -37,6 +36,8 @@ export function Home() {
const [address, setAddress] = useState<string | undefined>(() => Cookies.get('userMailbox'));
const [turnstileToken, setTurnstileToken] = useState<string>('');
const [selectedIds, setSelectedIds] = useState<string[]>([]);
// fix: 新增状态,跟踪人机验证是否已通过
const [isTurnstileVerified, setIsTurnstileVerified] = useState(false);
// feat: 初始化密码模态框
const { PasswordModal, setShowPasswordModal } = usePasswordModal();
@@ -47,17 +48,19 @@ export function Home() {
// feat: 新增状态来控制“查看密码”按钮的显示
const [showViewPasswordButton, setShowViewPasswordButton] = useState(false);
// 使用 React Query 获取邮件列表
const { data: emails = [], isLoading, isFetching, refetch } = useQuery<Email[]>({
queryKey: ['emails', address],
queryFn: () => getEmails(address!, turnstileToken),
// fix: 确保在 address 和 turnstileToken 都存在时,查询才被激活
enabled: !!address && !!turnstileToken,
queryFn: () => getEmails(address!),
// fix: 关键修复:现在查询只依赖于 `address` 是否存在。
// 移除了对 `turnstileToken` 的依赖,解决了页面刷新后无法加载邮件的问题。
enabled: !!address,
// fix: 恢复20秒自动刷新
refetchInterval: 20000,
// 当查询出错时,自动刷新会停止,这里添加错误提示方便调试
onError: (err: Error) => {
// 当发生错误时,给出提示
// fix: 修复硬编码文本问题,使用 t() 函数
toast.error(`${t("Failed to get emails")}: ${err.message}`, { duration: 5000 });
},
// 失败后不自动重试
@@ -101,7 +104,7 @@ export function Home() {
}}
className="w-full border border-transparent rounded-none rounded-r-lg p-4 flex items-center justify-center text-sm font-medium text-cyan-400 hover:text-cyan-300 focus:outline-none focus:ring-2 focus:ring-cyan-500"
>
关闭
{t("Close")}
</button>
</div>
</div>
@@ -144,14 +147,22 @@ export function Home() {
}, [emails, address, passwordNotificationShown, showPasswordToast]);
// 创建新邮箱地址的处理函数
const handleCreateAddress = () => {
const handleCreateAddress = async () => {
if (!turnstileToken) {
toast.error(t('No captcha response'));
return;
}
const mailbox = `${randomName("", getRandomCharacter())}@${config.emailDomain}`;
Cookies.set('userMailbox', mailbox, { expires: 1 }); // cookie 有效期1天
setAddress(mailbox);
// fix: 在创建地址前,先调用后端进行人机验证
try {
await verifyTurnstile(turnstileToken);
setIsTurnstileVerified(true); // 验证通过
const mailbox = `${randomName("", getRandomCharacter())}@${config.emailDomain}`;
Cookies.set('userMailbox', mailbox, { expires: 1 }); // cookie 有效期1天
setAddress(mailbox);
} catch (error) {
toast.error(t('Failed to verify captcha'));
console.error("Turnstile verification failed:", error);
}
};
// 停止使用当前邮箱地址
@@ -165,14 +176,17 @@ export function Home() {
// 删除邮件的 useMutation hook
const deleteMutation = useMutation({
mutationFn: (ids: string[]) => deleteEmails(ids, turnstileToken),
// fix: 删除邮件不再需要 turnstile token
mutationFn: (ids: string[]) => deleteEmails(ids),
onSuccess: () => {
// fix: 修复硬编码文本问题,使用 t() 函数
toast.success(t('Emails deleted'));
setSelectedIds([]); // 清空选择
// 使邮件列表的查询失效,触发一次刷新
queryClient.invalidateQueries({ queryKey: ['emails', address] });
},
onError: () => {
// fix: 修复硬编码文本问题,使用 t() 函数
toast.error(t('Deletion failed'));
}
});
@@ -180,6 +194,7 @@ export function Home() {
// 定义 handleDeleteEmails 函数
const handleDeleteEmails = (ids: string[]) => {
if (ids.length === 0) {
// fix: 修复硬编码文本问题,使用 t() 函数
toast.error(t('Please select emails to delete'));
return;
}
@@ -194,6 +209,7 @@ export function Home() {
}
setIsLoggingIn(true);
try {
// fix: 登录时也需要传递 turnstile token
const data = await loginByPassword(password, turnstileToken);
Cookies.set('userMailbox', data.address, { expires: 1 });
setAddress(data.address);
@@ -299,3 +315,4 @@ export function Home() {
</div>
);
}
+25 -10
View File
@@ -3,18 +3,16 @@ import type { Email } from '../database_types';
const API_BASE_URL = '/api';
// 定义 Turnstile 人机验证所需的 token
interface ApiPayload {
token: string;
[key: string]: any;
}
// fix: 移除不再需要的 ApiPayload 接口定义
// 获取邮件列表
export async function getEmails(address: string, token: string): Promise<Email[]> {
// fix: 移除 getEmails 函数中的 token 参数,因为后端已不再需要它
export async function getEmails(address: string): Promise<Email[]> {
const response = await fetch(`${API_BASE_URL}/emails`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ address, token }),
// fix: 请求体中只发送 address
body: JSON.stringify({ address }),
});
if (!response.ok) {
throw new Error('Network response was not ok');
@@ -22,6 +20,20 @@ export async function getEmails(address: string, token: string): Promise<Email[]
return response.json();
}
// feat: 新增函数,用于在创建邮箱前验证人机校验token
export async function verifyTurnstile(token: string): Promise<{ success: boolean }> {
const response = await fetch(`${API_BASE_URL}/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token }),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Turnstile verification failed');
}
return response.json();
}
// feat: 添加获取单封邮件详情的函数
export async function getEmailById(id: string): Promise<Email> {
const response = await fetch(`${API_BASE_URL}/emails/${id}`);
@@ -32,15 +44,18 @@ export async function getEmailById(id: string): Promise<Email> {
}
// 删除邮件
export async function deleteEmails(ids: string[], token: string): Promise<void> {
// fix: 移除 deleteEmails 函数中的 token 参数
export async function deleteEmails(ids: string[]): Promise<{ count: number }> {
const response = await fetch(`${API_BASE_URL}/delete-emails`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ids, token }),
// fix: 请求体中只发送 ids
body: JSON.stringify({ ids }),
});
if (!response.ok) {
throw new Error('Failed to delete emails');
}
return response.json();
}
// feat: 添加密码登录函数
@@ -55,4 +70,4 @@ export async function loginByPassword(password: string, token: string): Promise<
throw new Error(errorData.message || 'Login failed');
}
return response.json();
}
}
+3 -4
View File
@@ -120,11 +120,10 @@ api.get('/emails/:id', async (c) => {
return c.json(email);
});
// 删除邮件接口仍然保留 turnstile 验证,以防滥用
api.post('/delete-emails', turnstile, async (c) => {
// fix: 删除邮件接口不再需要 turnstile 验证,因为通常这是在已知邮箱上下文中操作的。
api.post('/delete-emails', async (c) => {
const db = getD1DB(c.env.DB);
// fix: 从上下文中获取已解析的请求体,并进行安全访问
const body = c.get('parsedBody');
const body = await c.req.json();
const ids = body?.ids;
if (!ids || !Array.isArray(ids) || ids.length === 0) {
return c.json({ message: 'ids are required' }, 400);