paw-gui/src/hooks/useStartupCheck.ts
2025-04-21 14:40:24 +08:00

321 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import { commands, ProxyNodeInfo } from "@/bindings";
import { app } from "@tauri-apps/api";
import { createCircuit } from "@/store/circuitSlice";
import { setServiceStatus, getProxy, getVersion } from "@/store/serviceSlice";
import { setNodes, setNodesError, clearNodes } from "@/store/nodesSlice";
import { setProxiesList1 } from "@/store/web3Slice";
// import { clearCircuitState } from '@/store/circuitSlice';
import { AppDispatch, RootState } from "@/store";
interface StartupCheckResult {
success: boolean;
error?: string;
data?: ProxyNodeInfo[];
}
// 重试函数:最多重试指定次数,每次等待指定时间
async function retry<T>(
operation: () => Promise<T>,
maxAttempts: number,
delayMs: number,
validate: (result: T) => boolean
): Promise<T> {
let lastError: Error | undefined;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const result = await operation();
if (validate(result)) {
return result;
}
lastError = new Error(`Validation failed on attempt ${attempt}`);
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
}
if (attempt < maxAttempts) {
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
throw lastError || new Error("Operation failed after all attempts");
}
// 正常来说windows下和linux下使用包管理器更新时安装/卸载服务是足够的只有macOS下依赖启动检查来安装/卸载服务
// !FIXME实现一个更好的安装/卸载服务的逻辑当前中间只是简单的等待5秒因为没有合适的方法判断卸载是否完成
async function checkAndInstallService(): Promise<StartupCheckResult> {
try {
// 获取当前服务版本和期望版本
const versionResult = await commands.getServiceVersion();
const expectedVersion = await app.getVersion();
// 需要安装/重装的两种情况,分别是
// 1. 服务不存在
// 2. 服务存在但版本不匹配
const serviceNotExists = versionResult.status === "error";
const versionMismatch =
!serviceNotExists && versionResult.data !== expectedVersion;
if (serviceNotExists) {
console.log("Service not found, installing...");
const installResult = await commands.installService();
if (installResult.status === "error") {
return {
success: false,
error: "Failed to install service",
};
}
} else if (versionMismatch) {
console.log("Service version mismatch, uninstalling old service...");
const uninstallResult = await commands.uninstallService();
if (uninstallResult.status === "error") {
return {
success: false,
error: "Failed to uninstall old service",
};
}
await new Promise((resolve) => setTimeout(resolve, 5000));
console.log("Installing new service...");
const installResult = await commands.installService();
if (installResult.status === "error") {
return {
success: false,
error: "Failed to install service",
};
}
}
// 如果进行了安装操作,验证安装结果
if (serviceNotExists || versionMismatch) {
// 等待并验证服务安装结果
// 最多等待10秒每秒检查一次
try {
await retry(
() => commands.getServiceVersion(),
10,
1000,
(result) => result.status === "ok" && result.data === expectedVersion
);
} catch (error) {
return {
success: false,
error: "Service installation verification failed after retries",
};
}
}
return { success: true };
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: "Unknown error during service check",
};
}
}
async function checkAndStartCore(): Promise<StartupCheckResult> {
try {
// 检查核心状态
let coreStatus = await commands.getNodes();
// 如果核心未运行,尝试启动
if (coreStatus.status === "error") {
console.log("Starting core...");
const startResult = await commands.restartCore();
if (startResult.status === "error") {
return {
success: false,
error: "Failed to start core",
};
}
// 等待并验证核心是否成功启动
// 最多等待5秒每500ms检查一次
try {
coreStatus = await retry(
() => commands.getNodes(),
10,
500,
(result) => result.status === "ok"
);
} catch (error) {
return {
success: false,
error: "Core start verification failed after retries",
};
}
}
return {
success: true,
data: coreStatus.status === "ok" ? coreStatus.data : undefined,
};
} catch (error) {
return {
success: false,
error:
error instanceof Error
? error.message
: "Unknown error during core check",
};
}
}
export function useStartupCheck() {
const dispatch: AppDispatch = useDispatch();
const { fallbackCircuit } = useSelector(
(state: RootState) => state.circuitReducer
);
// 检查服务和核心状态,同时更新节点信息
const checkStatus = async () => {
try {
const [versionResult, nodesResult] = await Promise.all([
commands.getServiceVersion(),
commands.getNodes(),
]);
const suitableServiceVersion = await app.getVersion();
const isServiceInstalled =
versionResult.status === "ok" &&
versionResult.data === suitableServiceVersion;
const isCoreRunning = nodesResult.status === "ok";
// 更新服务状态
dispatch(
setServiceStatus({
isServiceInstalled,
isCoreRunning,
})
);
// 更新节点信息
// 只有当核心正在运行时才更新节点
if (isCoreRunning) {
if (nodesResult.status === "ok") {
const proxies:any[] = [];
nodesResult.data.forEach((node,index) => {
const { country_code } = node
if(nodesResult.data.length-1 === index){
return
}
proxies.push({ country_code: country_code,ingress_country_code: nodesResult.data[index + 1].country_code })
})
// console.log(proxies,'proxiesproxiesproxies')
dispatch(setProxiesList1(proxies))
dispatch(setNodes(nodesResult.data));
} else {
dispatch(setNodesError("Failed to fetch nodes"));
}
} else {
dispatch(clearNodes());
// dispatch(clearCircuitState()); // 当核心未运行时清空链路状态
}
} catch (error) {
console.error("Status check failed:", error);
dispatch(
setServiceStatus({
isServiceInstalled: false,
isCoreRunning: false,
})
);
dispatch(clearNodes());
}
};
useEffect(() => {
async function performStartupCheck() {
try {
// 步骤1检查并安装服务
const serviceResult = await checkAndInstallService();
if (!serviceResult.success) {
console.error("Service check failed:", serviceResult.error);
await dispatch(
setServiceStatus({
isServiceInstalled: false,
isCoreRunning: false,
})
);
await dispatch(clearNodes());
return;
}
// 步骤2检查并启动核心
const coreResult = await checkAndStartCore();
if (!coreResult.success) {
console.error("Core check failed:", coreResult.error);
await dispatch(
setServiceStatus({
isServiceInstalled: true,
isCoreRunning: false,
})
);
await dispatch(clearNodes());
return;
}
// 步骤3检查当前核心版本
await dispatch(getVersion());
// 步骤4检查当前是否开启了代理
await dispatch(getProxy());
// 步骤5检查当前是否有默认链路并且保证状态是正常否则重新创建一个默认链路
if (!fallbackCircuit || fallbackCircuit?.state === "failed") {
let inbound = "";
let outbound = "";
if (coreResult && coreResult.data && coreResult.data.length > 0) {
inbound = coreResult.data[0].country_code || "";
outbound =
coreResult.data
.filter((item) => item.exit && item.country_code !== inbound)
?.slice(-1)?.[0].country_code || "";
// 如果没有这两个那么就跳过,证明核心运行了但是节点是空的,如果只有其中一个,那么证明总共节点就一个无法创建链路
if (inbound && outbound) {
await dispatch(
createCircuit({
uid: uuidv4(),
name: "系统默认链路",
inbound: inbound,
outbound: outbound,
multi_hop: 3,
fallback: true,
rule_path: null,
is_prefix: false,
})
).unwrap();
}
}
}
//
// 执行一次完整的状态检查
await checkStatus();
} catch (error) {
console.error("Startup check failed:", error);
dispatch(
setServiceStatus({
isServiceInstalled: false,
isCoreRunning: false,
})
);
dispatch(clearNodes());
}
}
// 执行启动自检
performStartupCheck();
// 每5秒检查一次状态
const intervalId = setInterval(checkStatus, 5000);
return () => clearInterval(intervalId);
}, []);
}