321 lines
9.9 KiB
TypeScript
321 lines
9.9 KiB
TypeScript
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);
|
||
}, []);
|
||
}
|