import { useState, useEffect, useRef, type FC } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { toast } from "sonner"; import { Loader2 } from "lucide-react"; interface TCPingResult { seq: number; time: number; success: boolean; error?: string; } interface TCPingStats { sent: number; received: number; lost: number; min: number; max: number; avg: number; } const Tool: FC = () => { const [host, setHost] = useState(""); const [port, setPort] = useState("443"); const [running, setRunning] = useState(false); const [results, setResults] = useState([]); const [stats, setStats] = useState({ sent: 0, received: 0, lost: 0, min: 0, max: 0, avg: 0, }); const intervalRef = useRef(null); const seqRef = useRef(0); const resultsContainerRef = useRef(null); const handleHostBlur = () => { if (!host.trim()) return; let input = host.trim(); let cleanHost = input; let extractedPort: string | null = null; try { // Try to parse as URL const url = new URL(input.startsWith('http') ? input : `https://${input}`); cleanHost = url.hostname; // Extract port if specified in URL if (url.port) { extractedPort = url.port; } } catch { // If parsing fails, fallback to manual cleanup const withoutProtocol = input.replace(/^https?:\/\//, ""); const withoutPath = withoutProtocol.split("/")[0]; // Check for port in the format hostname:port const portMatch = withoutPath.match(/^(.+):(\d+)$/); if (portMatch) { cleanHost = portMatch[1]; extractedPort = portMatch[2]; } else { cleanHost = withoutPath; } } if (cleanHost !== input) { setHost(cleanHost); } if (extractedPort) { setPort(extractedPort); } }; const tcping = async () => { if (!host.trim()) { toast.error("Please enter a hostname or IP"); return; } const seq = ++seqRef.current; const portNum = parseInt(port) || 443; const targetHost = host.trim(); // Build test URL const protocol = portNum === 443 ? "https" : "http"; const url = `${protocol}://${targetHost}:${portNum}`; const startTime = performance.now(); try { // 使用 fetch 测试连接 await fetch(url, { method: "HEAD", mode: "no-cors", cache: "no-cache", }); const endTime = performance.now(); const time = endTime - startTime; const newResult: TCPingResult = { seq, time, success: true, }; setResults((prev) => [...prev, newResult]); updateStats(newResult); } catch (error: unknown) { const endTime = performance.now(); const time = endTime - startTime; const errorMessage = error instanceof Error ? error.message : "Connection failed"; const newResult: TCPingResult = { seq, time, success: false, error: errorMessage, }; setResults((prev) => [...prev, newResult]); updateStats(newResult); } }; const updateStats = (newResult: TCPingResult) => { setStats((prev) => { const sent = prev.sent + 1; const received = newResult.success ? prev.received + 1 : prev.received; const lost = sent - received; let min = prev.min; let max = prev.max; let avg = prev.avg; if (newResult.success) { if (received === 1) { min = newResult.time; max = newResult.time; avg = newResult.time; } else { min = Math.min(min, newResult.time); max = Math.max(max, newResult.time); avg = (prev.avg * (received - 1) + newResult.time) / received; } } return { sent, received, lost, min, max, avg }; }); }; const startTCPing = () => { if (!host.trim()) { toast.error("Please enter a hostname or IP"); return; } setRunning(true); setResults([]); setStats({ sent: 0, received: 0, lost: 0, min: 0, max: 0, avg: 0, }); seqRef.current = 0; // 立即执行第一次 tcping tcping(); // 然后每秒执行一次 intervalRef.current = window.setInterval(tcping, 1000); }; const stopTCPing = () => { setRunning(false); if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }; useEffect(() => { // 自动滚动到底部 if (resultsContainerRef.current) { resultsContainerRef.current.scrollTop = resultsContainerRef.current.scrollHeight; } }, [results]); useEffect(() => { // 清理定时器 return () => { if (intervalRef.current) { clearInterval(intervalRef.current); } }; }, []); const lossRate = stats.sent > 0 ? ((stats.lost / stats.sent) * 100).toFixed(1) : "0.0"; return (
setHost(e.target.value)} onBlur={handleHostBlur} disabled={running} />
setPort(e.target.value)} disabled={running} min="1" max="65535" />
{!running ? ( ) : ( )}
{stats.sent > 0 && (
Statistics
Sent:
{stats.sent} times
Success:
{stats.received} times
Failed:
{stats.lost} times ({lossRate}%)
{stats.received > 0 && ( <>
Min Latency:
{stats.min.toFixed(2)} ms
Max Latency:
{stats.max.toFixed(2)} ms
Avg Latency:
{stats.avg.toFixed(2)} ms
Port Status:
Open
)}
)} {results.length > 0 && (
TCPing Results:
{results.map((result) => (
{result.success ? ( <> seq={result.seq} port={port} time={result.time.toFixed(2)}ms ) : ( <> seq={result.seq} port={port} Connection failed {result.error && ` (${result.error})`} )}
))} {running && (
Running...
)}
)}
); }; export default Tool;