From ca08df92fd9177e375f292671b849b90d18936fa Mon Sep 17 00:00:00 2001 From: jarvis2f <137974272+jarvis2f@users.noreply.github.com> Date: Tue, 21 Jan 2025 14:09:42 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20Support=20network=20testing?= =?UTF-8?q?=20when=20adding=20an=20account,=20optimize=20error=20tip.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/telegram/files/HttpVerticle.java | 16 +++++ web/src/components/about.tsx | 2 - web/src/components/account-dialog.tsx | 20 +++--- web/src/components/debug-telegram-method.tsx | 2 +- web/src/components/empty-state.tsx | 1 - web/src/components/file-phase-statistics.tsx | 2 - web/src/components/file-statistics.tsx | 6 +- web/src/components/proxy-ping.tsx | 2 +- web/src/components/proxys-dialog.tsx | 63 +++++++++++++++---- web/src/components/swr-provider.tsx | 34 +++++++++- web/src/components/ui/tooltip.tsx | 25 +++++++- web/src/lib/api.ts | 55 +++++++++------- 12 files changed, 170 insertions(+), 58 deletions(-) diff --git a/api/src/main/java/telegram/files/HttpVerticle.java b/api/src/main/java/telegram/files/HttpVerticle.java index 5138b5b..8cf4970 100644 --- a/api/src/main/java/telegram/files/HttpVerticle.java +++ b/api/src/main/java/telegram/files/HttpVerticle.java @@ -144,6 +144,7 @@ public class HttpVerticle extends AbstractVerticle { router.post("/telegrams/change").handler(this::handleTelegramChange); router.post("/telegram/:telegramId/toggle-proxy").handler(this::handleTelegramToggleProxy); router.get("/telegram/:telegramId/ping").handler(this::handleTelegramPing); + router.get("/telegram/:telegramId/test-network").handler(this::handleTelegramTestNetwork); router.get("/file/preview").handler(this::handleFilePreview); router.post("/file/start-download").handler(this::handleFileStartDownload); @@ -493,6 +494,21 @@ public class HttpVerticle extends AbstractVerticle { ); } + private void handleTelegramTestNetwork(RoutingContext ctx) { + String telegramId = ctx.pathParam("telegramId"); + if (StrUtil.isBlank(telegramId)) { + ctx.fail(400); + return; + } + getTelegramVerticle(telegramId) + .ifPresentOrElse(telegramVerticle -> + telegramVerticle.client.execute(new TdApi.TestNetwork()) + .onComplete(r -> + ctx.json(JsonObject.of("success", r.succeeded()))), + () -> ctx.fail(404) + ); + } + private void handleTelegramApiMethods(RoutingContext ctx) { Map>> functions = TdApiHelp.getFunctions(); ctx.json(JsonObject.of("methods", functions.keySet())); diff --git a/web/src/components/about.tsx b/web/src/components/about.tsx index e78a77e..3b03430 100644 --- a/web/src/components/about.tsx +++ b/web/src/components/about.tsx @@ -8,7 +8,6 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import { request } from "@/lib/api"; import Link from "next/link"; import TGDuck16HeyOut from "@/components/animations/tg-duck16_hey_out.json"; import dynamic from "next/dynamic"; @@ -27,7 +26,6 @@ const Lottie = dynamic(() => import("lottie-react"), { ssr: false }); export default function About() { const { data: apiData, error: apiError } = useSWR( "/version", - request, ); const { data: githubData, error: githubError } = useSWR< GitHubReleaseData, diff --git a/web/src/components/account-dialog.tsx b/web/src/components/account-dialog.tsx index ea8aa54..0bdd205 100644 --- a/web/src/components/account-dialog.tsx +++ b/web/src/components/account-dialog.tsx @@ -38,7 +38,7 @@ export function AccountDialog({ {children} @@ -50,20 +50,22 @@ export function AccountDialog({ )} -
+
setOpen(false)} /> - +
+ +
diff --git a/web/src/components/debug-telegram-method.tsx b/web/src/components/debug-telegram-method.tsx index 7e5ba31..2683f75 100644 --- a/web/src/components/debug-telegram-method.tsx +++ b/web/src/components/debug-telegram-method.tsx @@ -59,7 +59,7 @@ export default function DebugTelegramMethod() { const { data: methodData, isLoading: isMethodsLoading } = useSWR< TelegramMethodData, Error - >("/telegram/api/methods", request, { + >("/telegram/api/methods", { // 添加缓存以提升性能 revalidateOnFocus: false, revalidateOnReconnect: false, diff --git a/web/src/components/empty-state.tsx b/web/src/components/empty-state.tsx index a1be7e4..a2881c0 100644 --- a/web/src/components/empty-state.tsx +++ b/web/src/components/empty-state.tsx @@ -6,7 +6,6 @@ import { AccountDialog } from "@/components/account-dialog"; import React from "react"; import { Button } from "@/components/ui/button"; import { BorderBeam } from "@/components/ui/border-beam"; -import ProxysDialog from "@/components/proxys-dialog"; interface EmptyStateProps { isLoadingAccount?: boolean; diff --git a/web/src/components/file-phase-statistics.tsx b/web/src/components/file-phase-statistics.tsx index eddd2c6..a6bff90 100644 --- a/web/src/components/file-phase-statistics.tsx +++ b/web/src/components/file-phase-statistics.tsx @@ -20,7 +20,6 @@ import { XAxis, YAxis, } from "recharts"; -import { request } from "@/lib/api"; import prettyBytes from "pretty-bytes"; // Type definitions @@ -92,7 +91,6 @@ const TelegramStats: React.FC = ({ telegramId }) => { const { data, error, isLoading } = useSWR( `/telegram/${telegramId}/download-statistics?type=phase&timeRange=${timeRange}`, - request, ); if (error) { diff --git a/web/src/components/file-statistics.tsx b/web/src/components/file-statistics.tsx index 59429fe..2094c58 100644 --- a/web/src/components/file-statistics.tsx +++ b/web/src/components/file-statistics.tsx @@ -19,7 +19,7 @@ import { Upload, Video, } from "lucide-react"; -import { request, telegramApi, type TelegramApiArg } from "@/lib/api"; +import { telegramApi, type TelegramApiArg } from "@/lib/api"; import { formatDistanceToNow } from "date-fns"; import { Button } from "@/components/ui/button"; import useSWRMutation from "swr/mutation"; @@ -60,7 +60,6 @@ const FileStatistics: React.FC = ({ telegramId }) => { // Use SWR for data fetching and caching const { data, error, mutate } = useSWR( `/telegram/${telegramId}/download-statistics`, - request, ); const { trigger: triggerReset, isMutating: isResetMutating } = useSWRMutation< @@ -248,8 +247,7 @@ const FileStatistics: React.FC = ({ telegramId }) => { {avgStatFields.map((stat, index) => (
diff --git a/web/src/components/proxy-ping.tsx b/web/src/components/proxy-ping.tsx index 8a19924..71efccb 100644 --- a/web/src/components/proxy-ping.tsx +++ b/web/src/components/proxy-ping.tsx @@ -32,7 +32,7 @@ export default function ProxyPing({ accountId }: { accountId: string }) { {isLoading && (
)} - {!isLoading && error && Connection error} + {!isLoading && error && Failed} {!isLoading && data && (
diff --git a/web/src/components/proxys-dialog.tsx b/web/src/components/proxys-dialog.tsx index e32b623..6613dc7 100644 --- a/web/src/components/proxys-dialog.tsx +++ b/web/src/components/proxys-dialog.tsx @@ -8,8 +8,10 @@ import { Button } from "@/components/ui/button"; import { EarthLock } from "lucide-react"; import Proxys, { type ProxysProps } from "@/components/proxys"; import { VisuallyHidden } from "@radix-ui/react-visually-hidden"; -import React from "react"; +import React, { useMemo } from "react"; import { cn } from "@/lib/utils"; +import useSWR from "swr"; +import { TooltipWrapper } from "@/components/ui/tooltip"; type ProxysDialogProps = { className?: string; @@ -17,21 +19,56 @@ type ProxysDialogProps = { export default function ProxysDialog({ className, + telegramId, ...proxyProps }: ProxysDialogProps) { + const { data, isLoading } = useSWR<{ + success: boolean; + }>(telegramId ? `/telegram/${telegramId}/test-network` : undefined); + + const triggerTooltipContent = useMemo(() => { + if (!telegramId) { + return "You can add proxys for telegram connection."; + } + return ( + <> + {isLoading && "Testing network..."} + {!isLoading && data?.success && "Successfully connect to telegram"} + {!isLoading && !data?.success && "Failed to connect to telegram"} + + ); + }, [data, isLoading, telegramId]); + + const triggerClassName = telegramId + ? cn( + isLoading && + "animate-pulse border-4 border-blue-400 text-blue-400 dark:border-blue-500 dark:text-blue-500", + !isLoading && + data?.success && + "border-4 border-green-400 text-green-400 dark:border-green-500 dark:text-green-500", + !isLoading && + !data?.success && + "border-4 border-red-400 text-red-400 dark:border-red-500 dark:text-red-500", + ) + : ""; + return ( - - - + + + + + Proxys
- +
diff --git a/web/src/components/swr-provider.tsx b/web/src/components/swr-provider.tsx index e452fc9..c653708 100644 --- a/web/src/components/swr-provider.tsx +++ b/web/src/components/swr-provider.tsx @@ -2,7 +2,7 @@ import { SWRConfig } from "swr"; import React from "react"; import { useToast } from "@/hooks/use-toast"; -import { request } from "@/lib/api"; +import { request, RequestParsedError } from "@/lib/api"; export const SWRProvider = ({ children }: { children: React.ReactNode }) => { const { toast } = useToast(); @@ -13,8 +13,36 @@ export const SWRProvider = ({ children }: { children: React.ReactNode }) => { refreshInterval: 0, errorRetryCount: 1, fetcher: request, - onError: (err: Error) => { - toast({ title: "Error", description: err.message }); + onError: (err: Error, key: string) => { + let message; + if (err instanceof RequestParsedError) { + const responseText = err.responseText; + if (/<\/?[a-z][\s\S]*>/i.test(responseText)) { + message = ( +
+ ); + } else { + message = responseText; + } + } else { + message = err.message; + } + + toast({ + description: ( +
+
+ + Request Failed + +

+ {key} +

+
+
{message}
+
+ ), + }); }, }} > diff --git a/web/src/components/ui/tooltip.tsx b/web/src/components/ui/tooltip.tsx index 3e3f0be..6fd058c 100644 --- a/web/src/components/ui/tooltip.tsx +++ b/web/src/components/ui/tooltip.tsx @@ -29,4 +29,27 @@ const TooltipContent = React.forwardRef< )); TooltipContent.displayName = TooltipPrimitive.Content.displayName; -export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; +const TooltipWrapper = ({ + content, + children, +}: { + content: React.ReactNode; + children: React.ReactNode; +}) => { + return ( + + + {children} + {content} + + + ); +}; + +export { + Tooltip, + TooltipTrigger, + TooltipContent, + TooltipProvider, + TooltipWrapper, +}; diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index fa7d644..8b6c34e 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -26,33 +26,46 @@ export function getWsUrl(): string { } /* eslint-disable */ -export async function request( +export async function request( api: string, requestInit?: RequestInit, -): Promise { - return fetch(`${getApiUrl()}${api}`, { - credentials: "include", +): Promise { + const defaultHeaders = { + 'Content-Type': 'application/json', + }; + + const response = await fetch(`${getApiUrl()}${api}`, { + credentials: 'include', headers: { - "Content-Type": "application/json", + ...defaultHeaders, + ...requestInit?.headers, }, ...requestInit, - }).then(async (res) => { - let responseText = "Failed to fetch"; - try { - responseText = await res.text(); - if (!responseText || responseText === "") { - return; - } - const data = JSON.parse(responseText); - if (res.ok) { - return data; - } - throw new Error(data.error ?? responseText); - } catch (err: Error | any) { - toast({ title: "Error", description: err.message }); - throw err; - } }); + const responseText = await response.text(); + if (!responseText) { + return undefined as T; + } + let data; + try { + data = JSON.parse(responseText); + } catch (e) { + throw new RequestParsedError(responseText); + } + if (!response.ok) { + throw new Error(data.error ?? `Request failed with status ${response.status}`); + } + + return data as T; +} + +export class RequestParsedError extends Error { + responseText: string; + + constructor(responseText: string) { + super('Parse JSON Error'); + this.responseText = responseText; + } } export function localStorageProvider() {