[🛠️ Fix] Add vcredist inst check & Improve Aikari lifecycle mgmt

1. [+] 添加了对于 VC Redist 是否安装的状态检查 & 引导
2. [*] 略微调整和改进了 Aikari 的生命周期管理, 避免彻底卡死等情况, 添加更多的错误说明
This commit is contained in:
Minoricew
2025-11-18 19:37:52 +08:00
parent a6c68782a0
commit cd946a60fb
7 changed files with 178 additions and 30 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "HugoAura",
"version": "0.2.0-rc1",
"version": "0.2.0-rc1-p1",
"description": "Aura for SeewoHugo",
"main": "app.asar/main.js",
"dependencies": {},

View File

@@ -696,13 +696,31 @@ const applyAikariIpcHandler = (ipcMain) => {
}
);
ipcMain.handle(
`${methodBase}.forceReloadKeepAliveWin`,
/**
*
* @param {import("electron").IpcMainInvokeEvent} _event
* @param {any} arg
*/
(_event, arg) => {
ipcMain.send(
"auraWsKeepAlive",
`${methodBase}.post.onForceReloadRequested`,
arg
);
return { success: true };
}
);
ipcMain.handle(
`${methodBase}.post.updateRetryStatus`,
/**
*
* @param {import("electron").IpcMainInvokeEvent} _event
* @param {{ success: boolean }} arg
* @param {{ success: boolean; message: string }} arg
*/
(_event, arg) => {
ipcMain.send("assistant", `${methodBase}.post.updateRetryStatus`, arg);

View File

@@ -0,0 +1,34 @@
const childProc = require("child_process");
const appRawCmds = {
checkVcRedistInst: async () => {
let deviceArch = process.env.PROCESSOR_ARCHITEW6432
? process.env.PROCESSOR_ARCHITEW6432
: process.env.PROCESSOR_ARCHITECTURE;
// @ts-expect-error
deviceArch = deviceArch.toLowerCase();
if (deviceArch === "amd64") {
deviceArch = "x64";
} else if (deviceArch === "arm64") {
// do nothing
}
const waitForCmd = new Promise((resolve) => {
childProc.exec(
`reg query "HKLM\\SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\${deviceArch}" /v Installed`,
(error, stdout, stderr) => {
if (error) {
console.warn(
`[HugoAura / UI / Composables / Raw CMD / APP] Detected VS Redist not installed: ${stderr}`
);
resolve({ installed: false, arch: deviceArch });
} else {
resolve({ installed: true, arch: deviceArch });
}
}
);
});
return waitForCmd;
},
};
module.exports = appRawCmds;

View File

@@ -38,9 +38,9 @@
let isErrorOccurred = false;
/** @type {number} */
let plsPort = 22077;
let aikariPort = 22077;
/** @type {"wss" | "ws"} */
let plsProtocol = "wss";
let aikariProtocol = "wss";
/** @type {boolean} */
let isRetrying = false;
@@ -49,9 +49,13 @@
let curSendListener = null;
let curSendGetListener = null;
const sendRetryStatusToMain = (/** @type {Boolean} */ status) => {
const sendRetryStatusToMain = (
/** @type {Boolean} */ status,
/** @type {string?} */ message = null
) => {
global.ipcRenderer.invoke(`${IPC_METHOD_BASE}.post.updateRetryStatus`, {
success: status,
message: message,
});
};
@@ -86,7 +90,7 @@
// @ts-expect-error
global.__HUGO_AURA__.aikariStats.authToken = authTokenRet.data;
} else {
sendRetryStatusToMain(false);
sendRetryStatusToMain(false, "E_AUTH_TOKEN_GET_FAILED");
return;
}
const portRet = await registryManager.readRegKey(
@@ -96,7 +100,7 @@
);
if (portRet.success) {
try {
plsPort = Number(portRet.data);
aikariPort = Number(portRet.data);
} catch {
console.warn(
`[HugoAura / UI / Aikari Conn Manager] Invalid Aikari port: ${portRet.data}`
@@ -104,7 +108,10 @@
}
}
// TODO: wsHost
createAikariConnection(updatedAikariStats.authToken, connectionResultCallback);
createAikariConnection(
updatedAikariStats.authToken,
connectionResultCallback
);
};
/**
@@ -116,18 +123,20 @@
const createAikariConnection = (authToken, callback) => {
if (failedCounter >= 3) {
console.error(
`[HugoAura / UI / Aikari Conn Manager / ERROR] Failed connecting to PLS WebSocket server, please check the status of PLS process.`
`[HugoAura / UI / Aikari Conn Manager / ERROR] Failed connecting to Aikari WebSocket server, please check the status of Aikari process.`
);
sendRetryStatusToMain(false);
sendRetryStatusToMain(false, "E_WS_CONN_FAILED_AFT_MULTIPLE_TRIES");
return;
}
/** @type {WebSocket} */
const aikariWs = new WebSocket(
`${plsProtocol}://aikari.hugoaura.local:${plsPort}/?auth=${authToken}`
`${aikariProtocol}://aikari.hugoaura.local:${aikariPort}/?auth=${authToken}`
);
aikariWs.onopen = () => {
failedCounter = 0;
isErrorOccurred = false;
callback(true, aikariWs);
};
@@ -142,7 +151,7 @@
if (global.__HUGO_AURA__.aikariStats) {
if (global.__HUGO_AURA__.aikariStats.status === "notReady") {
if (isRetrying) {
sendRetryStatusToMain(false);
sendRetryStatusToMain(false, "E_IS_LOADING");
return;
}
console.warn(
@@ -153,7 +162,7 @@
isRetrying = false;
startConnAikariProc(global.__HUGO_AURA__.aikariStats);
}, 10000);
sendRetryStatusToMain(false);
sendRetryStatusToMain(false, "E_START_WAIT_FOR_LOADING");
return;
}
@@ -234,7 +243,7 @@
global.__HUGO_AURA__.aikariStats
);
sendRetryStatusToMain(true);
sendRetryStatusToMain(true, "SUCCESS");
};
/**
@@ -275,21 +284,21 @@
if (!global.__HUGO_AURA__.aikariStats) return;
if (isRetrying) {
sendRetryStatusToMain(false);
sendRetryStatusToMain(false, "E_RETRY_PENDING");
return;
}
failedCounter = 0;
isErrorOccurred = false;
const curPlsStats = await global.ipcRenderer.invoke(
const curAikariStats = await global.ipcRenderer.invoke(
`${IPC_METHOD_BASE}.getAikariStatus`
);
let updatedAikariStats = {};
if (
(curPlsStats === null || !curPlsStats.success) &&
curPlsStats.status !== "downloading" &&
curPlsStats.status !== "installing"
(curAikariStats === null || !curAikariStats.success) &&
curAikariStats.status !== "downloading" &&
curAikariStats.status !== "installing"
) {
updatedAikariStats = {
installed: false,
@@ -301,7 +310,7 @@
authToken: "",
};
} else {
updatedAikariStats = curPlsStats.data;
updatedAikariStats = curAikariStats.data;
}
const isAikariBinExists = (
@@ -326,7 +335,7 @@
if (updatedAikariStats.installed || updatedAikariStats.detached) {
await startConnAikariProc(updatedAikariStats);
} else {
sendRetryStatusToMain(false);
sendRetryStatusToMain(false, "E_NOT_INSTALLED");
}
/*
@@ -355,6 +364,13 @@
}
);
global.ipcRenderer.on(
`${IPC_METHOD_BASE}.post.onForceReloadRequested`,
(_evt, _arg) => {
window.location.reload();
}
);
global.ipcRenderer.on(
`${IPC_METHOD_BASE}.post.aikariStopped`,
(_evt, _arg) => {

View File

@@ -8,6 +8,8 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig)
const REQUIRE_BASE = "../../aura/ui/pages/configSubPages/behaviourCtrl";
const IPC_METHOD_BASE = "$aura.aikari";
const appRawCmds = require(`${REQUIRE_BASE}/../../../composables/rawCmdExec/app`);
const lifecycleStatus = {
installed: false,
detached: false,
@@ -21,6 +23,30 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig)
};
global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.aikariStatus = {
openLink: async (link) => {
const childProc = require("child_process");
childProc.spawn("cmd.exe", [`/k start ${link}`]);
},
getRetryStatusDescByErrId: (errIdString) => {
switch (errIdString) {
case "E_AUTH_TOKEN_GET_FAILED":
return `<p>Aikari 注册表访问失败, 这是一个极为罕见的问题</p>
<p>请检查 HKEY_USERS\\.DEFAULT 是否存在, 并反馈至 GitHub Issues</p>`;
case "E_WS_CONN_FAILED_AFT_MULTIPLE_TRIES":
return `<p>在多次尝试连接后仍然失败, 请检查服务是否已启动</p>`;
case "E_IS_LOADING":
case "E_START_WAIT_FOR_LOADING":
return `<p>Aikari 正在加载中, 请稍作等待</p>`; // 此提示理论上在当前版本不会出现
case "E_RETRY_PENDING":
return `<p>正在尝试重连中, 请勿重复操作</p>`;
case "E_NOT_INSTALLED":
return `<p>Aikari 未安装, 请安装后继续</p>`;
default:
return null;
}
},
updateToast: async (
variant,
title,
@@ -583,22 +609,41 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig)
if (result.success && result.status === "Retrying") {
updateOperationBtnStatus("Refresh", true, "正在重连");
let isRefreshCompleted = false;
ipcRenderer.once(
`${IPC_METHOD_BASE}.post.updateRetryStatus`,
async (_evt, arg) => {
await global.__HUGO_AURA_GLOBAL__.utils.sleep(50);
updateOperationBtnStatus("Refresh", false, "刷新状态");
isRefreshCompleted = true;
global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.aikariStatus.updateToast(
arg.success ? "success" : "error",
arg.success ? "更新成功" : "连接失败",
null,
global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.aikariStatus.getRetryStatusDescByErrId(
arg.message
),
true,
true,
3000
5000
);
global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.aikariStatus.updateStatusContent();
}
);
setTimeout(() => {
if (!isRefreshCompleted) {
ipcRenderer.invoke(`${IPC_METHOD_BASE}.forceReloadKeepAliveWin`);
global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.aikariStatus.updateToast(
"error",
"连接控制器无响应, 强制重载中...",
null,
true,
true,
4000
);
}
}, 8000);
} else if (result.success && result.status === "Already") {
updateOperationBtnStatus("Refresh", false, "刷新状态");
global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.aikariStatus.updateToast(
@@ -690,8 +735,8 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig)
"准备开始下载...",
null,
true,
true,
2000
false,
undefined
);
GLOBAL_FUNCTIONS.updateOperationBtnStatus("Install", true);
} else {
@@ -700,8 +745,8 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig)
"正在恢复下载状态",
null,
true,
true,
2000
false,
undefined
);
}
@@ -711,7 +756,14 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig)
const callbackFn = (_evt, info) => {
switch (info.status) {
case "failed":
if (info.id !== "INSTALL_STAGE") {
if (info.id === "PRECHECK_STAGE") {
GLOBAL_FUNCTIONS.updatePBarStatus(
100,
info.message,
"danger",
false
);
} else if (info.id !== "INSTALL_STAGE") {
GLOBAL_FUNCTIONS.updateToast(
"error",
"下载失败",
@@ -879,6 +931,34 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig)
}
};
const isVcRedistInstalledPrecheck = await appRawCmds.checkVcRedistInst();
if (!isVcRedistInstalledPrecheck.installed) {
callbackFn(null, {
id: "PRECHECK_STAGE",
progress: 100,
status: "failed",
dlUrl: null,
savePath: null,
message: "Visual C++ Redist 2015~2022 未安装",
});
setTimeout(() => {
GLOBAL_FUNCTIONS.updateToast(
"error",
"发生错误",
`<p>Aikari 需要 Visual C++ Redist 2015~2022 运行时以启动</p>
<p>请访问
<a href="javascript:global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.aikariStatus.openLink('https://aka.ms/vc14/vc_redist.${isVcRedistInstalledPrecheck.arch}.exe')">此链接</a>
以下载并安装运行时</p>`,
true,
false,
undefined
);
}, 500);
return;
}
ipcRenderer.on(CUR_CHANNEL, callbackFn);
ipcRenderer.invoke(`${IPC_METHOD_BASE}.downloadAndInstallAikari`, {

View File

@@ -13,7 +13,7 @@
<path fill="currentColor" d="M20.59 22L15 16.41V7h2v8.58l5 5.01z" />
</svg>
<p>请稍候...</p>
<p>无数据可用, 检查 Aikari 连接</p>
</div>
<div class="acs-bc-dsc-fop-on-req-error" auraIf="false">

View File

@@ -1,6 +1,6 @@
// @ts-check
const __AURA_VERSION__ = "0.2.0-rc1";
const __AURA_VERSION__ = "0.2.0-rc1-p1";
(() => {
if (require.main) return; // 如果只是导入 Aura Version, 不运行闭包逻辑