5 Commits

Author SHA1 Message Date
Minoricew
c84aaef994 [ Feat] Add settings for Aikari Telemetry 2025-11-22 19:21:52 +08:00
Minoricew
8656fd0334 [🛠️ Fix] Unexpected infinite reconn for ACM 2025-11-21 02:00:30 +08:00
Minoricew
80354cc310 [🛠️ Fix] Add auto clean for prev Py-PLS installation 2025-11-18 20:12:00 +08:00
Minoricew
cd946a60fb [🛠️ Fix] Add vcredist inst check & Improve Aikari lifecycle mgmt
1. [+] 添加了对于 VC Redist 是否安装的状态检查 & 引导
2. [*] 略微调整和改进了 Aikari 的生命周期管理, 避免彻底卡死等情况, 添加更多的错误说明
2025-11-18 19:37:52 +08:00
Minorice
a6c68782a0 [🔄 Chore] Add rickroll to README (bushi 2025-11-18 02:19:14 +08:00
12 changed files with 409 additions and 37 deletions

View File

@@ -14,6 +14,13 @@
> [!IMPORTANT]
> 已经过测试的希沃管家版本: v1.5.5.3878
> [!NOTE]
> **社群信息**
>
> - [QQ 群 (用户自治)](https://qm.qq.com/q/buo7m9oHBK)
> - [Telegram 群组](https://www.bilibili.com/video/BV1GJ411x7h7/) (暂时还没有...)
> - [STCN 论坛讨论版块](https://www.bilibili.com/video/BV1GJ411x7h7/) (暂时还没有...)
![Repobeats](https://repobeats.axiom.co/api/embed/69b5be5daacef624b8f5e4b8966a0b5898439a22.svg "Repobeats analytics image")
## ✨ 概览

View File

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

View File

@@ -2,7 +2,7 @@
const __SCOPE = "main";
const { exec } = require("child_process");
const { exec, execSync } = require("child_process");
const fs = require("fs");
const path = require("path");
const nodeHttps = require("https");
@@ -301,6 +301,14 @@ const applyAikariIpcHandler = (ipcMain) => {
"Aikari-Installer.exe"
);
// Prev PLS Cfg
const OLD_PLS_INSTALL_DIR = path.join(
"C:\\Program Files",
"HugoAura PLS",
"bin"
);
const OLD_PLS_SVC_NAME = "HugoAuraPLS";
const isAikariDetached = process.argv.includes("--aikari-detach");
global.__HUGO_AURA__.aikariStats = {
@@ -651,6 +659,19 @@ const applyAikariIpcHandler = (ipcMain) => {
} else {
fs.mkdirSync(AIKARI_TEMP_DL_DIR);
}
if (fs.existsSync(OLD_PLS_INSTALL_DIR)) {
try {
execSync(`sc stop ${OLD_PLS_SVC_NAME}`);
execSync(`sc delete ${OLD_PLS_SVC_NAME}`);
} catch (err) {
// ...
}
try {
fs.unlinkSync(OLD_PLS_INSTALL_DIR);
} catch (err) {
// ...
}
}
const channel = arg.channel ? arg.channel : "stable";
const reportWin = arg.reportTo ? arg.reportTo : "assistant";
functions.handleAikariDlAndInstall(
@@ -696,13 +717,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

@@ -40,19 +40,36 @@ const actions = {
data: {},
})
);
const promise = new Promise((resolve) => {
const promiseForConfig = new Promise((resolve) => {
wsGetCallbacks.set(eventId, resolve);
});
const data = await promise;
const data = await promiseForConfig;
if (data.success) {
console.debug(
"[HugoAura / UI / Aikari OCMS] Received Aikari launcher config: ",
data
);
return data.data;
} else {
return null;
}
wsObj.send(
JSON.stringify({
module: "launcher",
eventId,
method: "config.actions.getTelemetryIsEnabled",
data: {},
})
);
const promiseForTelemetry = new Promise((resolve) => {
wsGetCallbacks.set(eventId, resolve);
});
const telemetryConfig = await promiseForTelemetry;
if (telemetryConfig.success) {
data.data.telemetryEnabled = telemetryConfig.data.isEnabled;
} else {
data.data.telemetryEnabled = false;
}
return data.data;
},
getAikariPLSRules: async (wsObj) => {
const eventId = genRandomHex();

View File

@@ -80,6 +80,46 @@ const updateAikariConfigToRemote = async (
});
};
/**
*
* @param {boolean} newValue
*/
const updateAikariTelemetryConfigToRemote = async (newValue) => {
const aikariConfigUpdateEvent = new CustomEvent("onAikariConfigUpdate", {
detail: {
path: ["telemetry"],
value: newValue,
},
});
document.dispatchEvent(aikariConfigUpdateEvent);
const settingsEntries = document.getElementsByClassName(
"aura-settings-entry"
);
if (settingsEntries.length > 0) {
Array.from(settingsEntries).forEach((entry) => {
entry.dispatchEvent(aikariConfigUpdateEvent);
});
}
/**
* @type {ClientAikariRequest}
*/
const data = {
method: "config.actions.setTelemetryIsEnabled",
data: {
isEnabled: newValue,
},
eventId: genRandomHex(),
module: "launcher",
};
global.ipcRenderer.invoke(`${IPC_METHOD_BASE}.ws.sendWsMessage`, data);
global.ipcRenderer.invoke(`${IPC_METHOD_BASE}.syncAikariConfig`, {
basic: global.__HUGO_AURA__.aikariSettings,
rules: global.__HUGO_AURA__.aikariRules,
});
};
// [!] Will be deprecated
const updateAikariPLSRulesToRemote = async (
configKey,
@@ -131,6 +171,7 @@ module.exports = {
updateAikariRulesFromLocal,
updateAikariStatusFromLocal,
updateAikariSettingsFromLocal,
updateAikariTelemetryConfigToRemote,
updateAikariConfigToRemote,
updateAikariPLSRulesToRemote,
};

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

@@ -380,7 +380,7 @@ const renderNormalSettingsItem = (entry, formEl) => {
}
if (entry.aikariRequired) {
if (!global.__HUGO_AURA__.aikariStats.connected) {
if (!global.__HUGO_AURA__.aikariStats.connected && !entry.alwaysEnable) {
setDisableStatus(entryOperationArea, true, "连接至 Aikari 以继续");
}

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,15 +123,15 @@
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 = () => {
@@ -142,7 +149,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 +160,7 @@
isRetrying = false;
startConnAikariProc(global.__HUGO_AURA__.aikariStats);
}, 10000);
sendRetryStatusToMain(false);
sendRetryStatusToMain(false, "E_START_WAIT_FOR_LOADING");
return;
}
@@ -234,7 +241,7 @@
global.__HUGO_AURA__.aikariStats
);
sendRetryStatusToMain(true);
sendRetryStatusToMain(true, "SUCCESS");
};
/**
@@ -275,21 +282,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 +308,7 @@
authToken: "",
};
} else {
updatedAikariStats = curPlsStats.data;
updatedAikariStats = curAikariStats.data;
}
const isAikariBinExists = (
@@ -326,7 +333,7 @@
if (updatedAikariStats.installed || updatedAikariStats.detached) {
await startConnAikariProc(updatedAikariStats);
} else {
sendRetryStatusToMain(false);
sendRetryStatusToMain(false, "E_NOT_INSTALLED");
}
/*
@@ -355,6 +362,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,
@@ -303,14 +329,14 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig)
if (!isHostNotInit) {
global.__HUGO_AURA_UI_FUNCTIONS__.subConfig.aikariStatus.updateToast(
"success",
"Aikari 已启动",
"Aikari 已启动, 将在 2 秒后尝试连接",
null,
true,
true,
2000
);
}
await global.__HUGO_AURA_GLOBAL__.utils.sleep(100);
await global.__HUGO_AURA_GLOBAL__.utils.sleep(2000);
await ipcRenderer.invoke(
`${IPC_METHOD_BASE}.retryAikariConnect`
);
@@ -580,25 +606,52 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig)
const result = await ipcRenderer.invoke(
`${IPC_METHOD_BASE}.retryAikariConnect`
);
if (
!global.__HUGO_AURA__.aikariSettings ||
(global.__HUGO_AURA__.aikariStats.connected === false &&
global.__HUGO_AURA__.aikariStats.launched === true)
) {
ipcRenderer.invoke(`${IPC_METHOD_BASE}.forceReloadKeepAliveWin`);
}
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 +743,8 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig)
"准备开始下载...",
null,
true,
true,
2000
false,
undefined
);
GLOBAL_FUNCTIONS.updateOperationBtnStatus("Install", true);
} else {
@@ -700,8 +753,8 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig)
"正在恢复下载状态",
null,
true,
true,
2000
false,
undefined
);
}
@@ -711,7 +764,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 +939,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

@@ -1,7 +1,12 @@
const REQUIRE_BASE = ".";
const path = require("path");
const AIKARI_ROOT_DIR = path.join("C:\\ProgramData", "HugoAura", "Aikari");
const {
updateAikariConfigToRemote,
updateAikariTelemetryConfigToRemote,
} = require(`${REQUIRE_BASE}/../../../../composables/aikariConfigManager`);
const basicSettings = [
@@ -80,6 +85,133 @@ const basicSettings = [
},
],
},
{
id: 1,
categoryName: "分析",
child: [
{
index: 0,
id: "aikariTelemetryCtrl",
type: "switch",
name: "启用错误收集与分析",
description: "启用后, Aikari 将在发生错误时上报自托管 Sentry",
reactive: true,
reactiveVal: ["root.settings"],
restart: false,
reload: false,
aikariRequired: false,
restartAikari: false,
warning: true,
warningContent:
"我们不会收集您的设备用户名、管家内的学校名等信息, 也不会保存您的 IP 地址, 所有上传的数据仅供调试使用, 不会与任何第三方共享",
associateVal: null,
auraIf: () => true,
defaultValue: false,
valueGetter: () => {
if (
!global.__HUGO_AURA__.aikariSettings ||
!global.__HUGO_AURA__.aikariStats.connected
) {
const fs = require("fs");
return fs.existsSync(
path.join(AIKARI_ROOT_DIR, ".telemetryEnabled")
);
} else {
return global.__HUGO_AURA__.aikariSettings.telemetryEnabled;
}
},
callbackFn: (newVal) => {
if (
!global.__HUGO_AURA__.aikariSettings ||
!global.__HUGO_AURA__.aikariStats.connected
) {
if (newVal) {
const fs = require("fs");
fs.appendFile(
path.join(AIKARI_ROOT_DIR, ".telemetryEnabled"),
"",
(err) => {
if (err) console.warn(err);
}
);
return true;
} else {
const fs = require("fs");
try {
fs.unlinkSync(path.join(AIKARI_ROOT_DIR, ".telemetryEnabled"));
return true;
} catch (err) {
console.error("Error removing telemetry flag: ", err);
}
}
} else {
global.__HUGO_AURA__.aikariSettings.telemetryEnabled = newVal;
updateAikariTelemetryConfigToRemote(newVal);
return true;
}
},
},
{
index: 1,
id: "aikariTelemetryId",
type: "button",
style: "outline",
name: "Aikari Telemetry ID",
reactive: true,
reactiveVal: ["telemetry"],
restart: false,
reload: false,
aikariRequired: true,
restartAikari: false,
warning: true,
warningContent: "此标识符完全在初始化时随机生成, 与设备特征无关",
associateVal: ["telemetry"],
auraIf: () => true,
alwaysEnable: true,
buttonContent: "复制",
valueGetter: async () => {
if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig.behaviourCtrl)
global.__HUGO_AURA_UI_REACTIVES__.subConfig.behaviourCtrl = {};
const getIdPromise = new Promise((resolve) => {
setTimeout(() => {
const fs = require("fs");
const telemetryIdPath = path.join(
AIKARI_ROOT_DIR,
".telemetryId"
);
if (fs.existsSync(telemetryIdPath)) {
const fileContent = fs
.readFileSync(telemetryIdPath, { encoding: "utf-8" })
.trim();
global.__HUGO_AURA_UI_REACTIVES__.subConfig.behaviourCtrl.telemetryId =
fileContent;
resolve("标识符: " + fileContent);
}
global.__HUGO_AURA_UI_REACTIVES__.subConfig.behaviourCtrl.telemetryId =
null;
resolve("未能获取标识符, Aikari 未安装或未初始化");
}, 1000);
});
return await getIdPromise;
},
callbackFn: async (event) => {
if (
global.__HUGO_AURA_UI_REACTIVES__.subConfig.behaviourCtrl
.telemetryId
) {
await navigator.clipboard.writeText(
global.__HUGO_AURA_UI_REACTIVES__.subConfig.behaviourCtrl
.telemetryId
);
event.target.textContent = "已复制";
} else {
event.target.textContent = "复制失败";
}
},
},
],
},
];
module.exports = { basicSettings };

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-p2";
(() => {
if (require.main) return; // 如果只是导入 Aura Version, 不运行闭包逻辑