4 Commits

Author SHA1 Message Date
Minoricew
a86d13431b [Feat] Settings UI Auth (#3) & Prepare for v0.1.1-rel 2025-06-02 00:23:00 +08:00
Minoricew
1320f5397a [Chore] Minor README update 2025-05-28 18:50:07 +08:00
Minoricew
fece9a8805 [Chore] Add Issue templates 2025-05-26 00:00:58 +08:00
Minoricew
12f1040884 [Feat] Early support for PLS & Use JSDoc (Partially) 2025-05-25 22:43:01 +08:00
77 changed files with 2181 additions and 217 deletions

67
.github/ISSUE_TEMPLATE/bugReport.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: 反馈异常 / Bug Report
description: 反馈应用中的问题, 或预期外的行为
labels: ["Bug"]
title: "[Bug] "
body:
- type: textarea
attributes:
label: 复现步骤 / Reproduce steps
placeholder: |
1. 首先, 我...
2. 然后, 出现了...
3. 最终...
validations:
required: true
- type: textarea
attributes:
label: 预期行为 / Expected behaviour
placeholder: |
1. 应用本来应该...
validations:
required: true
- type: textarea
attributes:
label: 实际行为 / Actual behaviour
placeholder: |
1. 事实上, 应用...
validations:
required: true
- type: input
attributes:
label: HugoAura 版本 / HugoAura version
validations:
required: true
- type: input
attributes:
label: 希沃管家版本 / Seewo Services version
validations:
required: true
- type: checkboxes
id: confirms
attributes:
label: 提交前请确认 / Confirm before submit
options:
- label: 我已经在 [HugoAura Issues](https://github.com/HugoAura/Seewo-HugoAura/issues) 中查询过相关关键词, 并确定此 Issue 是唯一的。
required: true
- label: 我已经确认, 我运行的 HugoAura 版本与希沃管家客户端版本相兼容
required: true
- type: textarea
attributes:
label: 日志 / Logs
description: 请前往 `用户文件夹/Documents/HugoAura/logs` 取得日志
placeholder: |
[LOG] Logger initialized. Log file: C:\...
[LOG] [HugoAura / Loaded] Aura is loaded!
validations:
required: false
- type: textarea
id: extraDesc
attributes:
label: 其他信息 (可选) / Additional info (Optional)

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View File

@@ -0,0 +1,53 @@
name: 功能请求 / Feature Request
description: 提交你想要的新功能
labels: ["Feature"]
title: "[Feature Request] "
body:
- type: textarea
attributes:
label: 概述 / Overview
description: 简述你想要的新功能, 需要它做什么
placeholder:
validations:
required: true
- type: textarea
attributes:
label: 背景 (可选) / Background info (Optional)
description: 有哪些因素让你想到了这个新功能? 它为什么对用户体验至关重要?
placeholder:
validations:
required: false
- type: textarea
attributes:
label: 具体需求 / Detail Requirements
description: 请详细描述具体希望实现哪些目标, 以及提出你认为可能的实现思路
placeholder: |
我希望能够实现...
具体来讲, 包含:
1. 实现 XXX
2. XXXXXX
3. XXXXXX
validations:
required: true
- type: input
attributes:
label: HugoAura 版本 / HugoAura version
description: 创建此 Issue 时的最新 HugoAura 版本
validations:
required: true
- type: checkboxes
id: confirms
attributes:
label: 提交前请确认 / Confirm before submit
options:
- label: 我已经在 [HugoAura Issues](https://github.com/HugoAura/Seewo-HugoAura/issues) 中查询过相关关键词, 并确定此功能请求是唯一的。
required: true
- type: textarea
id: extraDesc
attributes:
label: 其他信息 (可选) / Additional info (Optional)

20
.github/ISSUE_TEMPLATE/other.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: 其他问题 / Any Other
description: 其他任何类型的问题
labels: []
title: "[Question] "
body:
- type: textarea
attributes:
label: 你的问题 / Your question
placeholder: |
开发者能不能 v 我 50
validations:
required: true
- type: checkboxes
id: confirms
attributes:
label: 提交前请确认 / Confirm before submit
options:
- label: 请勿利用此 Issue 类型进行灌水 / 闲聊, 如果您有相关需求, 可留意后续 HugoAura 的论坛 / 用户讨论群开放情况
required: true

0
LICENSE Normal file → Executable file
View File

3
README.md Normal file → Executable file
View File

@@ -13,6 +13,9 @@
> [!TIP]
> HugoAura 的首个预览版已发布! [查看安装教程](https://github.com/HugoAura/HugoAura/wiki)
> [!IMPORTANT]
> 已经过测试的希沃管家版本: v1.5.4.3822
## ✨ 概览
[天下](https://www.bilibili.com/video/BV1UN4y1k7bA) [苦希沃管家](https://www.bilibili.com/video/BV18Z421j7Lf) [久矣](https://github.com/255doesnotexist/SeewoAssistantPasswordRecovery), 如此~~好用~~的一款集控软件, 让广大电教委员对它~~爱不释手~~。

9
jsconfig.json Executable file
View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"module": "CommonJS",
"target": "ES2015",
"allowImportingTsExtensions": false,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}

0
scripts/cd.bat Normal file → Executable file
View File

0
scripts/kad.bat Normal file → Executable file
View File

0
scripts/kar.bat Normal file → Executable file
View File

56
src/aura/init/main/ipcHandler.js Normal file → Executable file
View File

@@ -1,10 +1,64 @@
// @ts-check
const __SCOPE = "main";
/**
*
* @param {import("electron")} electron
*/
const buildIpcMain = (electron) => {
const { app, ipcMain } = electron;
const { app } = electron;
/**
* @type {import("../../types/main/electron").AuraIPCMain}
*/
// @ts-ignore
const ipcMain = electron.ipcMain;
/**
*
* @param {string} windowKey
* @param {string} channel
* @param {any} data
* @param {import("electron").WebContents?} grep
*/
ipcMain.send = (windowKey, channel, data, grep = null) => {
/**
*
* @param {string} key
* @param {string} chan
* @param {any} targetData
*/
const sendDataToWebContents = (key, chan, targetData) => {
const webContents =
global.__HUGO_AURA__.hookedWindows.get(key).webContents;
if (grep !== webContents) webContents.send(chan, targetData);
};
if (windowKey === "*") {
for (const perWindow of global.__HUGO_AURA__.hookedWindows.keys()) {
sendDataToWebContents(perWindow, channel, data);
}
} else {
const isWindowValid = global.__HUGO_AURA__.hookedWindows.has(windowKey);
if (!isWindowValid) {
throw new Error(
`[HugoAura / Main / IPC / ERROR] Unknown windowKey: ${windowKey}`
);
}
sendDataToWebContents(windowKey, channel, data);
}
};
const { applyPlsIpcHandler } = require("./ipcModules/plsIpcHandler");
ipcMain.handle("$aura.base.restartApplication", async () => {
app.relaunch();
app.exit(0);
});
applyPlsIpcHandler(ipcMain);
};
module.exports = { buildIpcMain };

View File

@@ -0,0 +1,190 @@
// @ts-check
const __SCOPE = "main";
const fs = require("fs");
const path = require("path");
/**
*
* @param {import("../../../types/main/electron").AuraIPCMain} ipcMain
*/
const applyPlsIpcHandler = (ipcMain) => {
const methodBase = "$aura.pls";
const isPlsDetached = process.argv.includes("--pls-detach");
global.__HUGO_AURA__.plsStats = {
installed: false,
launched: false,
detached: isPlsDetached,
connected: false,
version: "未知",
status: "dead",
authToken: global.__HUGO_AURA_CONFIG__.plsToken,
};
ipcMain.handle(
`${methodBase}.getPlsFolderExists`,
/**
*
* @returns {{ success: boolean; data: { isExists: boolean }; error?: Error }}
*/
(_event, _arg) => {
const plsFolderPath = path.join(__dirname, "../../../proxy");
try {
const result = fs.existsSync(plsFolderPath);
return {
success: true,
data: { isExists: result },
};
} catch (e) {
return {
success: false,
data: { isExists: false },
error: e,
};
}
}
);
ipcMain.handle(
`${methodBase}.getPlsStats`,
/**
*
* @returns {{ success: boolean; data: PLSStatus; }}
*/
(_event, _arg) => {
return {
success: true,
data: global.__HUGO_AURA__.plsStats,
};
}
);
ipcMain.handle(
`${methodBase}.updatePlsStats`,
/**
*
* @param {import("electron").IpcMainInvokeEvent} _event
* @param {PLSStatus} arg
* @returns
*/
(_event, arg) => {
global.__HUGO_AURA__.plsStats = arg;
ipcMain.send("assistant", `${methodBase}.post.onPlsStatsUpdate`, arg);
return {
success: true,
};
}
);
ipcMain.handle(
`${methodBase}.getPlsSettings`,
/**
*
* @returns {{ success: boolean; data: Record<any, any> }}
*/
(_event, _arg) => {
return {
success: true,
data: global.__HUGO_AURA__.plsSettings,
};
}
);
ipcMain.handle(
`${methodBase}.updatePlsSettings`,
/**
*
* @param {import("electron").IpcMainInvokeEvent} _event
* @param {Record<any, any>} arg
* @returns
*/
(_event, arg) => {
global.__HUGO_AURA__.plsSettings = arg;
return {
success: true,
};
}
);
ipcMain.handle(
`${methodBase}.getPlsRules`,
/**
*
* @returns {{ success: boolean; data: Record<any, any> }}
*/
(_event, _arg) => {
return {
success: true,
data: global.__HUGO_AURA__.plsRules,
};
}
);
ipcMain.handle(
`${methodBase}.updatePlsRules`,
/**
*
* @param {import("electron").IpcMainInvokeEvent} _event
* @param {Record<any, any>} arg
* @returns
*/
(_event, arg) => {
global.__HUGO_AURA__.plsRules = arg;
return {
success: true,
};
}
);
ipcMain.on(
`${methodBase}.ws.broadcastMessageRecv`,
/**
*
* @param {import("electron").IpcMainEvent} _event
* @param {PLSResponse} arg
*/
(_event, arg) => {
ipcMain.send("assistant", `${methodBase}.ws.post.onNewMsgRecv`, arg);
}
);
ipcMain.handle(
`${methodBase}.ws.sendWsMessage`,
/**
*
* @param {import("electron").IpcMainInvokeEvent} _event
* @param {ClientPLSRequest} arg
*/
(_event, arg) => {
ipcMain.send(
"desktopAssistant",
`${methodBase}.ws.post.onReqSendMsg`,
arg
);
}
);
ipcMain.handle(
`${methodBase}.syncPlsConfig`,
/**
*
* @param {import("electron").IpcMainInvokeEvent} event
* @param {{ basic: Record<any, any>, rules: Record<any, any> }} arg
*/
(event, arg) => {
global.__HUGO_AURA__.plsRules = arg.rules;
global.__HUGO_AURA__.plsSettings = arg.basic;
ipcMain.send("*", `${methodBase}.syncPlsConfig`, arg, event.sender);
return {
success: true,
};
}
);
};
module.exports = { applyPlsIpcHandler };

0
src/aura/init/preload/webpackHook.js Normal file → Executable file
View File

32
src/aura/init/rendererHook/hooksManager.js Normal file → Executable file
View File

@@ -1,13 +1,20 @@
// @ts-check
const fs = require("fs");
const path = require("path");
class HooksManager {
loadHooks() {
if (global.__HUGO_AURA__.hooks) {
if (
global.__HUGO_AURA__.hooks &&
Object.keys(global.__HUGO_AURA__.hooks).length !== 0
) {
return global.__HUGO_AURA__.hooks;
}
const hooksPath = path.join(__dirname, "../../../aura/ui/hooks");
const hooksPath = path.join(__dirname, "../../../aura/ui/hookDefinitions");
/** @type {import("../../types/main/core").HooksMap} */
const hooks = new Map();
try {
@@ -17,6 +24,7 @@ class HooksManager {
try {
const hook = require(path.join(hooksPath, file));
/** @type {import("../../types/main/core").WindowName} */
const targetWindow = hook.windowName || path.basename(file, ".js");
hooks.set(targetWindow, hook);
console.log(
@@ -40,13 +48,19 @@ class HooksManager {
return hooks;
}
cleanupWindow(windowKey, listeners) {
/**
*
* @param {import("../../types/main/core").WindowName} windowKey
* @param {import("../../types/main/core").HookedWindow} hookedWindowProps
*/
cleanupWindow(windowKey, hookedWindowProps) {
console.log(
`[HugoAura / Cleanup / ${windowKey}] Window destroyed, cleaning up...`
);
if (listeners) {
const { webContents, domReadyListener, destroyedListener } = listeners;
if (hookedWindowProps) {
const { webContents, domReadyListener, destroyedListener } =
hookedWindowProps;
webContents.removeListener("dom-ready", domReadyListener);
webContents.removeListener("destroyed", destroyedListener);
}
@@ -54,9 +68,17 @@ class HooksManager {
global.__HUGO_AURA__.hookedWindows.delete(windowKey);
}
/**
*
* @param {import("electron").WebContents} webContents
* @param {import("../../types/render/uiHook").UIHookConfigFin} hookConfig
* @param {import("../../types/main/core").WindowName} windowName
* @returns
*/
handleWindowHook(webContents, hookConfig, windowName) {
if (!hookConfig) return;
/** @type {import("../../types/main/core").WindowName} */
const windowKey = `${hookConfig.windowName || windowName}`;
if (global.__HUGO_AURA__.hookedWindows.has(windowKey)) {
console.log(

0
src/aura/init/rendererHook/injection.js Normal file → Executable file
View File

0
src/aura/init/rendererHook/networkHook.js Normal file → Executable file
View File

230
src/aura/init/shared/configManager.js Normal file → Executable file
View File

@@ -1,11 +1,34 @@
const fs = require('fs');
const path = require('path');
const os = require('os');
// @ts-check
const fs = require("fs");
const path = require("path");
const os = require("os");
const crypto = require("crypto");
const childProc = require("child_process");
// Constants
const CRYPTO_SETTINGS_AES = {
mode: "aes-256-gcm",
keyLength: 32,
keyIter: 100000,
ivLength: 12,
tagLength: 16,
saltLength: 16,
obfuscateStr: "eCybsseK",
hash: "sha256",
};
/**
*
* @param {Record<any, any>} target
* @param {Record<any, any>} source
* @returns
*/
const deepMerge = (target, source) => {
const result = JSON.parse(JSON.stringify(target));
if (!source || typeof source !== 'object') {
if (!source || typeof source !== "object") {
return {};
}
@@ -15,9 +38,9 @@ const deepMerge = (target, source) => {
if (!(key in source)) {
keysToDelete.push(key);
} else if (
typeof result[key] === 'object' &&
typeof result[key] === "object" &&
result[key] !== null &&
typeof source[key] === 'object' &&
typeof source[key] === "object" &&
source[key] !== null
) {
result[key] = deepMerge(result[key], source[key]);
@@ -42,13 +65,11 @@ const deepMerge = (target, source) => {
class ConfigManager {
constructor() {
this.configPath = path.join(
os.homedir(),
'Documents',
'HugoAura',
'config.json'
);
this.defaultConfigPath = path.join(__dirname, 'default.json');
this.configDir = path.join(os.homedir(), "Documents", "HugoAura");
this.configPath = path.join(this.configDir, "config.json");
this.encConfigPath = path.join(this.configDir, ".cache_2eafc8d0.dat"); // (雾
/* ↑ 不使用 .tmp 扩展名, 不然容易真被清理了 */
this.defaultConfigPath = path.join(__dirname, "default.json");
}
getHugoAuraConfigPath() {
@@ -61,10 +82,10 @@ class ConfigManager {
getDefaultConfig() {
try {
return JSON.parse(fs.readFileSync(this.defaultConfigPath, 'utf8'));
return JSON.parse(fs.readFileSync(this.defaultConfigPath, "utf8"));
} catch (err) {
console.warn(
'[HugoAura / Config] No default config found, using empty config'
"[HugoAura / Config] No default config found, using empty config"
);
return { rewrite: {} };
}
@@ -74,12 +95,12 @@ class ConfigManager {
if (global.__HUGO_AURA__.configInit) return;
const hugoAuraPath = this.getHugoAuraConfigPath();
if (!fs.existsSync(hugoAuraPath)) {
console.log('[HugoAura / Config] Creating HugoAura directory');
console.log("[HugoAura / Config] Creating HugoAura directory");
fs.mkdirSync(hugoAuraPath, { recursive: true });
}
if (!fs.existsSync(this.configPath)) {
console.log('[HugoAura / Config] Creating default config file');
console.log("[HugoAura / Config] Creating default config file");
const defaultConfig = this.getDefaultConfig();
this.writeConfig(defaultConfig);
}
@@ -87,25 +108,30 @@ class ConfigManager {
readConfig() {
try {
const config = JSON.parse(fs.readFileSync(this.configPath, 'utf8'));
console.log('[HugoAura / Config] Successfully loaded config:', config);
const config = JSON.parse(fs.readFileSync(this.configPath, "utf8"));
console.log("[HugoAura / Config] Successfully loaded config:", config);
return config;
} catch (err) {
console.error('[HugoAura / Config] Failed to read config:', err);
console.error("[HugoAura / Config] Failed to read config:", err);
return this.getDefaultConfig();
}
}
/**
*
* @param {Record<any, any>} config
* @returns
*/
writeConfig(config) {
try {
fs.writeFileSync(
this.configPath,
JSON.stringify(config, null, 2),
'utf8'
"utf8"
);
return true;
} catch (err) {
console.error('[HugoAura / Config] Failed to write config:', err);
console.error("[HugoAura / Config] Failed to write config:", err);
return false;
}
}
@@ -115,23 +141,177 @@ class ConfigManager {
let config = {};
try {
if (fs.existsSync(this.configPath)) {
const userConfig = JSON.parse(fs.readFileSync(this.configPath, 'utf8'));
const userConfig = JSON.parse(fs.readFileSync(this.configPath, "utf8"));
if (global.__HUGO_AURA__.configInit) {
config = userConfig;
return userConfig;
} else {
config = deepMerge(userConfig, defaultConfig);
console.log('[HugoAura / Config] Merged with user config');
console.log("[HugoAura / Config] Merged with user config");
this.writeConfig(config);
}
}
} catch (err) {
console.error('[HugoAura / Config] Failed to load user config:', err);
console.error("[HugoAura / Config] Failed to load user config:", err);
config = defaultConfig;
}
return config;
}
/**
*
* @param {Record<any, any>} configData
* @param {string} passwd
*/
encryptConfig(configData, passwd) {
const salt = crypto.randomBytes(CRYPTO_SETTINGS_AES.saltLength);
const key = crypto.pbkdf2Sync(
passwd,
salt,
CRYPTO_SETTINGS_AES.keyIter,
CRYPTO_SETTINGS_AES.keyLength,
CRYPTO_SETTINGS_AES.hash
);
const iv = crypto.randomBytes(CRYPTO_SETTINGS_AES.ivLength);
const cipherIns = crypto.createCipheriv(CRYPTO_SETTINGS_AES.mode, key, iv, {
// @ts-expect-error
authTagLength: CRYPTO_SETTINGS_AES.tagLength,
});
const stringifyConfigData = JSON.stringify(configData);
let encryptedCfg = cipherIns.update(stringifyConfigData, "utf-8", "hex");
encryptedCfg += cipherIns.final("hex");
const authTag = cipherIns.getAuthTag();
/** @type {EncryptedConfig} */
const encConfigFinal = {};
encConfigFinal.content = encryptedCfg;
encConfigFinal.authTag = authTag.toString("base64");
encConfigFinal.salt = salt.toString("base64");
encConfigFinal.iv = iv.toString("base64");
const base64EncConfig =
CRYPTO_SETTINGS_AES.obfuscateStr +
Buffer.from(JSON.stringify(encConfigFinal)).toString("base64");
try {
fs.writeFileSync(this.encConfigPath, base64EncConfig, "utf-8");
// fs.rmSync(this.configPath);
const _hideFileProc = childProc.spawnSync(
"cmd.exe",
["/c", "attrib", "+h", this.encConfigPath],
{
stdio: "inherit",
}
);
return true;
} catch (err) {
console.error(
"[HugoAura / Config] Failed to write encrypted config:",
err
);
console.error(
"[HugoAura / Config] Pending config data:",
base64EncConfig
);
return false;
}
}
/**
*
* @param {string} passwd
* @returns
*/
decryptConfig(passwd) {
const FAILED_RET = {
success: false,
data: {},
};
let base64EncConfig = null;
try {
if (!fs.existsSync(this.encConfigPath)) {
return FAILED_RET;
}
base64EncConfig = fs.readFileSync(this.encConfigPath, "utf-8");
} catch (err) {
console.error(
"[HugoAura / Config] Failed to read encrypted config:",
err
);
return FAILED_RET;
}
if (base64EncConfig) {
const strip64EncCfg = base64EncConfig.split(
CRYPTO_SETTINGS_AES.obfuscateStr
)[1];
const encryptCfg = Buffer.from(strip64EncCfg, "base64").toString("utf-8");
/** @type {null | EncryptedConfig} */
let parsedEncCfg = null;
try {
parsedEncCfg = JSON.parse(encryptCfg);
} catch (err) {
console.error(
"[HugoAura / Config] Failed to parse encrypted config:",
err
);
console.error("[HugoAura / Config] Pending data:", encryptCfg);
}
if (parsedEncCfg === null) return FAILED_RET;
const salt = Buffer.from(parsedEncCfg.salt, "base64");
const iv = Buffer.from(parsedEncCfg.iv, "base64");
const authTag = Buffer.from(parsedEncCfg.authTag, "base64");
const key = crypto.pbkdf2Sync(
passwd,
salt,
CRYPTO_SETTINGS_AES.keyIter,
CRYPTO_SETTINGS_AES.keyLength,
CRYPTO_SETTINGS_AES.hash
);
const decipherIns = crypto.createDecipheriv(
CRYPTO_SETTINGS_AES.mode,
key,
iv,
{
// @ts-expect-error
authTagLength: CRYPTO_SETTINGS_AES.tagLength,
}
);
decipherIns.setAuthTag(authTag);
let stringifyDecCfg = Buffer.concat([
decipherIns.update(parsedEncCfg.content, "hex"),
decipherIns.final(),
]).toString();
/** @type {null | Record<any, any>} */
let decConfig = null;
try {
decConfig = JSON.parse(stringifyDecCfg);
} catch (err) {
console.error(
"[HugoAura / Config] Failed to parse decrypted config:",
err
);
console.error("[HugoAura / Config] Pending data:", decConfig);
return FAILED_RET;
}
if (decConfig === null) return FAILED_RET;
console.debug(decConfig);
}
}
}
module.exports = new ConfigManager();

8
src/aura/init/shared/default.json Normal file → Executable file
View File

@@ -18,5 +18,13 @@
"enabled": true
}
},
"plsToken": "66ccff0d000721114514191981023333",
"auraSettings": {
"settingsPasswordEnabled": false,
"settingsPasswordWithSalt": "32703D292460CC9A3B867494D6AD9A8E4A3ADF0FAA4D6867BC4D412CC3927D02E47C6D0B1763BB53E57B2241C6193433561CDA09D7C48CA03983072B876F0965",
"appearance": {
"enablePasswdDialogBlur": true
}
},
"devTools": false
}

0
src/aura/init/zeron/hookWS.js Normal file → Executable file
View File

0
src/aura/jsRewrite/network/disableBehaviorAudit.js Normal file → Executable file
View File

0
src/aura/jsRewrite/network/disableFriday.js Normal file → Executable file
View File

0
src/aura/jsRewrite/vendor/passwordValidation.js vendored Normal file → Executable file
View File

33
src/aura/types/main/core.d.ts vendored Executable file
View File

@@ -0,0 +1,33 @@
import { WebContents } from "electron";
type SeewoHugoCentralLambda = any;
type SeewoHugoGlobalConfig = Record<any, any>;
type WindowName = string;
interface LauncherArgs {
central: SeewoHugoCentralLambda;
windowName: WindowName;
config: SeewoHugoGlobalConfig;
}
interface HookedWindow {
webContents: WebContents;
domReadyListener: any;
destroyedListener: any;
}
type HookedWindowsMap = Map<WindowName, HookedWindow>;
type HookRequire = any;
type HooksMap = Map<WindowName, HookRequire>;
interface MainProcessGlobal {
hookedWindows: HookedWindowsMap;
hooks: HooksMap;
configInit: boolean;
plsStats: PLSStatus | null;
plsSettings: Record<any, any> | null;
plsRules: Record<any, any> | null;
}

10
src/aura/types/main/electron.d.ts vendored Executable file
View File

@@ -0,0 +1,10 @@
import { IpcMain, WebContents } from "electron";
interface AuraIPCMain extends IpcMain {
send: (
windowKey: string,
channel: string,
data: any,
grep?: WebContents
) => void;
}

14
src/aura/types/render/global.d.ts vendored Executable file
View File

@@ -0,0 +1,14 @@
interface HugoAuraGlobal {
utils: Record<any, any>;
}
interface AssistantHugoAuraGlobal extends HugoAuraGlobal {
plsStatus: PLSStatus;
plsRules: Record<any, any>;
plsSettings: Record<any, any>;
}
interface DesktopAssistantHugoAuraGlobal extends HugoAuraGlobal {
plsWs: WebSocket | null;
plsStats: PLSStatus;
}

25
src/aura/types/render/uiHook.d.ts vendored Executable file
View File

@@ -0,0 +1,25 @@
import { WindowName } from "../main/core";
interface UIHookTarget {
active: boolean;
pageURI: string;
pageScript: string;
pageSelector: string;
selectorMode: "insertAfter" | "insertBefore" | "appendChild";
pageCSS: string;
revive?: boolean;
}
type AuraElementUID = string;
type OnLoadedEvalJS = string;
interface UIHookConfig {
targets: Record<AuraElementUID, UIHookTarget>;
globalStyles: string[];
globalJS: string[];
onLoaded: OnLoadedEvalJS;
}
interface UIHookConfigFin extends UIHookConfig {
windowName: WindowName;
}

9
src/aura/types/shared/config.d.ts vendored Executable file
View File

@@ -0,0 +1,9 @@
type AES256EncryptedConfig = string;
type Base64String = string;
interface EncryptedConfig {
content: AES256EncryptedConfig;
authTag: Base64String;
salt: Base64String;
iv: Base64String;
}

9
src/aura/types/shared/pls/status.d.ts vendored Executable file
View File

@@ -0,0 +1,9 @@
interface PLSStatus {
installed: boolean;
detached: boolean;
connected: boolean;
launched: boolean;
status: string;
version: string;
authToken: string;
}

18
src/aura/types/shared/pls/websocket.d.ts vendored Executable file
View File

@@ -0,0 +1,18 @@
interface ClientPLSRequest {
method: string;
data: Record<any, any>;
eventId: string;
}
interface PLSResponse {
success: boolean;
code: number;
data: Record<any, any>;
eventId: string;
}
interface PLSPush {
success: boolean;
type: string;
data: Record<any, any>;
}

0
src/aura/ui/bootstrap/bootstrap.bundle.min.js vendored Normal file → Executable file
View File

0
src/aura/ui/bootstrap/bootstrap.min.css vendored Normal file → Executable file
View File

View File

@@ -0,0 +1,93 @@
// @ts-check
const __SCOPE = "assistant / rendererCommon";
const IPC_METHOD_BASE = "$aura.pls";
const updatePlsStatusFromLocal = async () => {
const plsStatus = (
await global.ipcRenderer.invoke(`${IPC_METHOD_BASE}.getPlsStats`)
).data;
global.__HUGO_AURA_GLOBAL__.plsStatus = plsStatus;
return plsStatus;
};
const updatePlsSettingsFromLocal = async () => {
const plsSettings = (
await global.ipcRenderer.invoke(`${IPC_METHOD_BASE}.getPlsSettings`)
).data;
global.__HUGO_AURA_GLOBAL__.plsSettings = plsSettings;
return plsSettings;
};
const updatePlsRulesFromLocal = async () => {
const plsRules = (
await global.ipcRenderer.invoke(`${IPC_METHOD_BASE}.getPlsRules`)
).data;
global.__HUGO_AURA_GLOBAL__.plsRules = plsRules;
return plsRules;
};
/**
*
* @returns {string}
*/
const genRandomHex = () => {
let result = "";
for (let i = 0; i < 8; i++) {
const randomNum = Math.floor(Math.random() * 0x10000);
result += randomNum.toString(16).padStart(4, "0");
}
return result;
};
/**
*
* @param {string} configKey
* @param {any} configValue
*/
const updatePlsConfigToRemote = async (configKey, configValue) => {
const configLevels = configKey.split(".");
/** @type {Record<any, any>} */
let localUpdateTarget =
configLevels[0] === "ruleSettings"
? global.__HUGO_AURA_GLOBAL__.plsRules
: global.__HUGO_AURA_GLOBAL__.plsSettings;
for (const level of configLevels.slice(0, -1)) {
localUpdateTarget = localUpdateTarget[level];
}
localUpdateTarget[configLevels.slice(-1)[0]] = configValue;
const plsConfigUpdateEvent = new CustomEvent("onPLSConfigUpdate", {
detail: {
path: configKey,
value: configValue,
},
});
document.dispatchEvent(plsConfigUpdateEvent);
/**
* @type {ClientPLSRequest}
*/
const data = {
method: "config.action.updateConfig",
data: {
key: configKey,
value: configValue,
},
eventId: genRandomHex(), // 不用 crypto, 因为会带来不必要的性能开销
};
global.ipcRenderer.invoke(`${IPC_METHOD_BASE}.ws.sendWsMessage`, data);
global.ipcRenderer.invoke(`${IPC_METHOD_BASE}.syncPlsConfig`, {
basic: global.__HUGO_AURA_GLOBAL__.plsSettings,
rules: global.__HUGO_AURA_GLOBAL__.plsRules,
});
};
module.exports = {
updatePlsRulesFromLocal,
updatePlsStatusFromLocal,
updatePlsSettingsFromLocal,
updatePlsConfigToRemote,
};

49
src/aura/ui/composables/settingsRenderer.js Normal file → Executable file
View File

@@ -18,15 +18,30 @@ const showRelaunchToast = () => {
if (!toastBs.isShown()) toastBs.show();
};
const showRelaunchPLSToast = () => {
const toast = document.getElementById("relaunchPlsNotifyToast");
const toastBs = bootstrap.Toast.getOrCreateInstance(toast);
if (global.__HUGO_AURA_GLOBAL__.plsStatus.detached) {
const relaunchBtn = document.getElementById("plsRelaunchBtn");
relaunchBtn.disabled = true;
relaunchBtn.textContent = "分离模式下无法执行"
}
if (!toastBs.isShown()) toastBs.show();
};
const showToast = (entry) => {
if (entry.reload) {
showReloadToast();
} else if (entry.restart) {
showRelaunchToast();
} else if (entry.restartPLS) {
showRelaunchPLSToast();
}
};
const settingsRenderer = (pendingEl, settingsObj) => {
const settingsRenderer = (pendingEl, settingsObj, isPls = false) => {
const formEl = document.createElement("form");
formEl.classList.add("aura-settings-form");
for (const category of settingsObj) {
@@ -56,6 +71,18 @@ const settingsRenderer = (pendingEl, settingsObj) => {
powerIcon.setAttribute("data-bs-title", "需要重启 Electron 进程");
entryTitle.appendChild(powerIcon);
}
if (entry.restartPLS) {
const plsIcon = document.createElement("i");
plsIcon.classList.add(
"layui-icon",
"layui-icon-logout",
"aura-settings-entry-property-icon"
);
plsIcon.setAttribute("data-bs-toggle", "tooltip");
plsIcon.setAttribute("data-bs-placement", "top");
plsIcon.setAttribute("data-bs-title", "需要重启 PLS 进程");
entryTitle.appendChild(plsIcon);
}
if (entry.reload) {
const reloadIcon = document.createElement("i");
reloadIcon.classList.add(
@@ -182,14 +209,18 @@ const settingsRenderer = (pendingEl, settingsObj) => {
if (!isShow) entryContainerEl.classList.add("aura-settings-entry-hidden");
if (entry.associateVal) {
document.addEventListener("onHugoAuraConfigUpdate", (event) => {
if (!entry.associateVal.includes(event.detail.path.join("."))) return;
const cls = entryContainerEl.classList;
const isShow = entry.auraIf();
isShow
? cls.remove("aura-settings-entry-hidden")
: cls.add("aura-settings-entry-hidden");
});
document.addEventListener(
isPls ? "onPLSConfigUpdate" : "onHugoAuraConfigUpdate",
(event) => {
if (!entry.associateVal.includes(event.detail.path.join(".")))
return;
const cls = entryContainerEl.classList;
const isShow = entry.auraIf();
isShow
? cls.remove("aura-settings-entry-hidden")
: cls.add("aura-settings-entry-hidden");
}
);
}
formEl.appendChild(entryContainerEl);

0
src/aura/ui/css/assistant.css Normal file → Executable file
View File

26
src/aura/ui/css/form.css Normal file → Executable file
View File

@@ -68,3 +68,29 @@
.aura-settings-entry-property-icon.layui-icon-refresh {
color: rgb(0, 106, 188);
}
/* Animations */
@keyframes invalidShake {
0% {
margin-left: calc(-10px * 2);
}
16% {
margin-left: calc(9px * 2);
}
33% {
margin-left: calc(-6px * 2);
}
50% {
margin-left: calc(5px * 2);
}
66% {
margin-left: calc(-2px * 2);
}
83% {
margin-left: calc(1px * 2);
}
100% {
margin-left: calc(0px * 2);
}
}

0
src/aura/ui/css/global.css Normal file → Executable file
View File

View File

@@ -1,4 +1,9 @@
module.exports = {
// @ts-check
/**
* @type {import("../../types/render/uiHook").UIHookConfig}
*/
const def = {
targets: {
"Aura.UI.Assistant.HeaderEntry": {
active: true,
@@ -28,6 +33,22 @@ module.exports = {
pageCSS:
"ui/pages/configSubPages/disableLimitations/disableLimitations.css",
},
"Aura.UI.Assistant.Config.BehaviourCtrl": {
active: false,
pageURI: "ui/pages/configSubPages/behaviourCtrl/behaviourCtrl.html",
pageScript: "ui/pages/configSubPages/behaviourCtrl/behaviourCtrl.js",
pageSelector: ".aura-config-page-subpage-container",
selectorMode: "appendChild",
pageCSS: "ui/pages/configSubPages/behaviourCtrl/behaviourCtrl.css",
},
"Aura.UI.Assistant.Config.BehaviourCtrl.PlsStatus": {
active: false,
pageURI: "ui/pages/configSubPages/behaviourCtrl/plsStatus.html",
pageScript: "ui/pages/configSubPages/behaviourCtrl/plsStatus.js",
pageSelector: "#status-subpage",
selectorMode: "appendChild",
pageCSS: "ui/pages/configSubPages/behaviourCtrl/plsStatus.css",
},
},
globalStyles: [
"ui/css/global.css",
@@ -41,3 +62,5 @@ module.exports = {
console.log('[HugoAura / UI / Hooks / Assistant] Page loaded.');
`,
};
module.exports = def;

View File

@@ -0,0 +1,15 @@
// @ts-check
/**
* @type {import("../../types/render/uiHook").UIHookConfig}
*/
const def = {
targets: {},
globalStyles: ["ui/css/global.css"],
globalJS: ["ui/js/global.js", "ui/js/plsConnectionManager.js"],
onLoaded: `
console.log('[HugoAura / UI / Hooks / Desktop Assistant] Page loaded.');
`,
};
module.exports = def;

0
src/aura/ui/js/global.js Normal file → Executable file
View File

View File

@@ -0,0 +1,192 @@
// @ts-check
const IPC_METHOD_BASE = "$aura.pls";
const REQUIRE_BASE = "../../aura/ui";
const __SCOPE = "desktopAssistant";
const { pushMsgHandler } = require(`${REQUIRE_BASE}/pls/pushHandler`);
/** @type {number} */
let failedCounter = 0;
/** @type {boolean} */
let isErrorOccurred = false;
/**
*
* @param {string} authToken
* @param {any} callback
* @returns
*/
const createPlsConnection = (authToken, callback) => {
if (failedCounter >= 3) {
console.error(
`[HugoAura / UI / PLS Manager / ERROR] Failed connecting to PLS WebSocket server, please check the status of PLS process.`
);
return;
}
/** @type {WebSocket} */
const plsWs = new WebSocket(
`wss://pls.hugoaura.local:22077/?auth=${authToken}`
);
plsWs.onopen = () => {
callback(true, plsWs);
};
plsWs.onerror = () => {
isErrorOccurred = true;
failedCounter += 1;
callback(false, plsWs);
};
plsWs.onclose = () => {
console.error(
"[HugoAura / UI / PLS Manager / ERROR] WebSocket connection closed."
);
if (isErrorOccurred) return;
failedCounter += 1;
callback(false, plsWs);
};
};
/**
*
* @param {WebSocket} wsObj
*/
const registerSendReqListener = (wsObj) => {
global.ipcRenderer.on(
`${IPC_METHOD_BASE}.ws.post.onReqSendMsg`,
/**
*
* @param {Event} _evt
* @param {any} arg
*/
(_evt, arg) => {
wsObj.send(JSON.stringify(arg));
}
);
};
/**
*
* @param {boolean} result
* @param {WebSocket} wsObj
* @returns
*/
const connectionResultCallback = (result, wsObj) => {
global.__HUGO_AURA_GLOBAL__.plsStats.launched = result;
global.__HUGO_AURA_GLOBAL__.plsStats.connected = result;
global.ipcRenderer.invoke(
`${IPC_METHOD_BASE}.updatePlsStats`,
global.__HUGO_AURA_GLOBAL__.plsStats
);
if (!result) {
console.error(
`[HugoAura / UI / PLS Manager / ERROR] Failed connecting to PLS WebSocket server, retrying ...`
);
createPlsConnection(
global.__HUGO_AURA_GLOBAL__.plsStats.authToken,
connectionResultCallback
);
return;
}
global.__HUGO_AURA_GLOBAL__.plsWs = wsObj;
registerSendReqListener(wsObj);
wsObj.onmessage = plsPushHandler;
};
/**
*
* @param {MessageEvent} event
*/
const plsPushHandler = (event) => {
try {
/** @type {Record<any, any>} */
const parsedEvent = JSON.parse(event.data);
console.debug(
"[HugoAura / UI / PLS Manager / DEBUG] Received new server message: "
);
if (!parsedEvent.eventId) {
// Push
pushMsgHandler(parsedEvent);
} else {
// Not push
global.ipcRenderer.send(
`${IPC_METHOD_BASE}.ws.broadcastMessageRecv`,
parsedEvent
);
}
} catch {
console.error(
"[HugoAura / UI / PLS Manager / ERROR] Failed to resolve server message: ",
event.data
);
}
};
const initPlsConnection = async () => {
failedCounter = 0;
isErrorOccurred = false;
const curPlsStats = await global.ipcRenderer.invoke(
`${IPC_METHOD_BASE}.getPlsStats`
);
let updatedPlsStats = {};
if (curPlsStats === null || !curPlsStats.success) {
updatedPlsStats = {
installed: false,
launched: false,
detached: false,
connected: false,
version: "未知",
status: "dead",
authToken: "66ccff0d000721114514191981023333",
};
} else {
updatedPlsStats = curPlsStats.data;
}
const isPlsFolderExists = (
await global.ipcRenderer.invoke(`${IPC_METHOD_BASE}.getPlsFolderExists`)
).data.isExists;
updatedPlsStats.installed = isPlsFolderExists;
global.__HUGO_AURA_GLOBAL__.plsStats = updatedPlsStats;
console.debug(
"[HugoAura / UI / PLS Manager / DEBUG] Updated plsStats:",
global.__HUGO_AURA_GLOBAL__.plsStats
);
global.ipcRenderer.invoke(
`${IPC_METHOD_BASE}.updatePlsStats`,
updatedPlsStats
);
const startConnPls = () => {
createPlsConnection(updatedPlsStats.authToken, connectionResultCallback);
};
if (updatedPlsStats.detached && updatedPlsStats.installed) {
startConnPls();
}
global.ipcRenderer.on(`${IPC_METHOD_BASE}.post.onPlsLaunched`, (_event) => {
setTimeout(() => {
startConnPls();
}, 5000);
});
};
const onSetup = () => {
if (!global.global.ipcRenderer) {
// @ts-ignore
global.global.ipcRenderer = require("electron").global.ipcRenderer;
}
initPlsConnection();
};
(() => {
onSetup();
})();

0
src/aura/ui/layui/css/layui.css Normal file → Executable file
View File

0
src/aura/ui/layui/font/iconfont.eot Normal file → Executable file
View File

0
src/aura/ui/layui/font/iconfont.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 322 KiB

After

Width:  |  Height:  |  Size: 322 KiB

0
src/aura/ui/layui/font/iconfont.ttf Normal file → Executable file
View File

0
src/aura/ui/layui/font/iconfont.woff Normal file → Executable file
View File

0
src/aura/ui/layui/font/iconfont.woff2 Normal file → Executable file
View File

77
src/aura/ui/pages/config/config.css Normal file → Executable file
View File

@@ -45,6 +45,7 @@
padding-left: 8px;
padding-right: 8px;
color: white;
z-index: 12000;
opacity: 1;
transform: translateY(0);
@@ -368,6 +369,82 @@
font-size: small;
}
/* Auth Dialog */
.aura-config-page-auth-dialog-area {
position: absolute;
height: calc(100% - 40px);
width: 100%;
top: 40px;
left: 0;
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
background-color: rgba(255, 255, 255, 0.35);
opacity: 1;
transition: all 0.5s;
}
.aura-config-page-auth-dialog-area.blur-enabled {
height: 100%;
top: 0;
background-color: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(5px);
filter: blur(0.1px);
/* ↑ 似乎会导致性能问题 */
}
.acp-ada-hidden {
opacity: 0;
}
.acp-ada-hidden.blur-enabled {
backdrop-filter: blur(0.1px);
filter: unset;
}
.aura-config-page-auth-dialog {
height: 40%;
width: 100%;
background-color: rgba(255, 255, 255, 0.625);
display: flex;
flex-direction: column;
text-align: center;
align-items: center;
padding-top: 2rem;
padding-bottom: 2rem;
}
.acp-auth-dialog-title {
font-size: x-large;
margin-bottom: 1.5rem;
}
#acp-auth-user-input {
max-width: 50%;
/* background-color: rgba(255, 255, 255, 0.5); */
border-radius: 35px;
margin-bottom: 2rem;
}
#acp-auth-user-input.invalid {
animation: invalidShake 0.6s linear;
}
.acp-auth-confirm-btn {
background-color: transparent;
border-radius: 35px;
border: 1px solid rgba(0, 0, 0, 0.3);
padding: 0.5rem;
}
.acp-auth-confirm-btn .layui-icon {
font-size: 24px;
margin-left: 2px;
}
/* Toast */
.aura-config-page-toast-area {

51
src/aura/ui/pages/config/config.html Normal file → Executable file
View File

@@ -82,6 +82,8 @@
class="operation-el-hidden aura-config-page-operation-el"
aura-disabled="true"
>
<!-- Still WIP -->
<!-- onclick="window.__HUGO_AURA_UI_FUNCTIONS__.config.toggleSubConfig('behaviourCtrl', true)" -->
<div class="aura-config-page-operation-body">
<img src="../../aura/ui/static/config/behaviour_mon.svg" />
<div>
@@ -114,6 +116,30 @@
</div>
</div>
<div
class="aura-config-page-auth-dialog-area acp-ada-hidden"
style="display: none"
>
<div class="aura-config-page-auth-dialog">
<p class="acp-auth-dialog-title">验证您的身份</p>
<input
type="password"
class="form-control"
placeholder="请输入密码..."
aria-label="Aura Password"
id="acp-auth-user-input"
/>
<button
class="acp-auth-confirm-btn"
onclick="global.__HUGO_AURA_UI_FUNCTIONS__.config.verifyAuthPassword()"
>
<i class="layui-icon layui-icon-right"></i>
</button>
</div>
</div>
<div class="aura-config-page-toast-area">
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<div
@@ -162,5 +188,30 @@
</div>
</div>
</div>
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<div
id="relaunchPlsNotifyToast"
class="toast acp-toast-emerg"
aria-atomic="true"
data-bs-autohide="false"
>
<div class="toast-header">
<i class="layui-icon layui-icon-tips"></i>
<strong class="me-auto">重启 PLS 进程以应用设置</strong>
</div>
<div class="toast-body">
<p>请重启 PLS 进程以应用修改的设置</p>
<button
type="button"
class="btn btn-primary btn-sm"
id="plsRelaunchBtn"
onclick="ipcRenderer.invoke('$aura.pls.relaunchPls')"
>
重启进程
</button>
</div>
</div>
</div>
</div>
</div>

102
src/aura/ui/pages/config/config.js Normal file → Executable file
View File

@@ -49,17 +49,33 @@ global.__HUGO_AURA_UI_FUNCTIONS__.config = {
"aura-config-page-operation-el"
);
let pendingSubPageId = "";
let preserveOperationIdx = 0;
switch (subPage) {
case "disableLimitations":
side
? operationElArr[0].classList.add("preserve-operation")
: operationElArr[0].classList.remove("preserve-operation");
preserveOperationIdx = 0;
pendingSubPageId = "Aura.UI.Assistant.Config.DisableLimitations";
break;
case "behaviourCtrl":
preserveOperationIdx = 1;
pendingSubPageId = "Aura.UI.Assistant.Config.BehaviourCtrl";
if (!side) {
setTimeout(() => {
global.__HUGO_AURA_LOADER__[
"Aura.UI.Assistant.Config.BehaviourCtrl.PlsStatus"
].active = false;
}, 500);
}
break;
default:
break;
}
side
? operationElArr[preserveOperationIdx].classList.add("preserve-operation")
: operationElArr[preserveOperationIdx].classList.remove(
"preserve-operation"
);
const operationAreaEl = document.getElementsByClassName(
"aura-config-page-operation-area"
)[0];
@@ -101,6 +117,48 @@ global.__HUGO_AURA_UI_FUNCTIONS__.config = {
global.__HUGO_AURA_UI_REACTIVES__.config.isInSubPage = side;
},
verifyAuthPassword: async () => {
const showFailedAnimation = async (el) => {
el.classList.remove("invalid");
await window.__HUGO_AURA_GLOBAL__.utils.sleep(50);
el.classList.add("invalid"); // Custom Anim
el.classList.add("is-invalid"); // Bootstrap
};
const inputEl = document.getElementById("acp-auth-user-input");
const userPasswdInput = inputEl.value;
if (!userPasswdInput || userPasswdInput.length < 8) {
showFailedAnimation(inputEl);
return false;
}
const crypto = require("crypto");
const encPasswd = crypto
.createHash("sha512")
.update(userPasswdInput + "EndlessX")
.digest("hex")
.toUpperCase();
if (
encPasswd ===
global.__HUGO_AURA_CONFIG__.auraSettings.settingsPasswordWithSalt
) {
const acsDialogAreaEl = document.getElementsByClassName(
"aura-config-page-auth-dialog-area"
)[0];
acsDialogAreaEl.classList.add("acp-ada-hidden");
await window.__HUGO_AURA_GLOBAL__.utils.sleep(500);
acsDialogAreaEl.style = "display: none;";
await window.__HUGO_AURA_GLOBAL__.utils.sleep(250);
global.__HUGO_AURA_UI_FUNCTIONS__.config.showSecondPhaseAnim();
return true;
} else {
showFailedAnimation(inputEl);
return false;
}
},
};
(() => {
@@ -151,23 +209,51 @@ global.__HUGO_AURA_UI_FUNCTIONS__.config = {
});
};
const showAnimation = async () => {
const defaultHeader = document.getElementsByClassName(
"index__header__16DmR2a5"
)[0];
global.__HUGO_AURA_UI_FUNCTIONS__.config.showSecondPhaseAnim = () => {
showOperationsAnimation();
};
const handleSettingsAuth = async () => {
const isAuthEnabled =
global.__HUGO_AURA_CONFIG__.auraSettings.settingsPasswordEnabled;
if (!isAuthEnabled) {
showOperationsAnimation();
} else {
await window.__HUGO_AURA_GLOBAL__.utils.sleep(50);
const acsDialogAreaEl = document.getElementsByClassName(
"aura-config-page-auth-dialog-area"
)[0];
acsDialogAreaEl.style = "";
if (
global.__HUGO_AURA_CONFIG__.auraSettings.appearance
.enablePasswdDialogBlur
) {
acsDialogAreaEl.classList.add("blur-enabled");
}
await window.__HUGO_AURA_GLOBAL__.utils.sleep(500);
acsDialogAreaEl.classList.remove("acp-ada-hidden");
}
};
const showAnimation = async () => {
const auraConfigPageRoot = document.getElementsByClassName(
"aura-config-page-root"
)[0];
await window.__HUGO_AURA_GLOBAL__.utils.sleep(200);
auraConfigPageRoot.className = "aura-config-page-root";
const defaultHeader = document.getElementsByClassName(
"index__header__16DmR2a5"
)[0];
await window.__HUGO_AURA_GLOBAL__.utils.sleep(500);
defaultHeader.style = "display: none;";
showVersionContainerAnimation();
showHeaderAnimation();
await window.__HUGO_AURA_GLOBAL__.utils.sleep(500);
showOperationsAnimation();
await handleSettingsAuth();
};
const onMounted = () => {

View File

@@ -0,0 +1,8 @@
.aura-config-subpage-behaviour-control {
opacity: 1;
transition: opacity 0.5s;
}
.aura-config-subpage-behaviour-control.acs-behaviour-control-hidden {
opacity: 0;
}

View File

@@ -0,0 +1,69 @@
<div
id="acs-behaviour-control-el"
class="aura-config-subpage-behaviour-control acs-behaviour-control-hidden"
>
<ul class="nav nav-underline mb-3" role="tablist">
<li class="nav-item" role="presentation">
<button
class="nav-link active"
id="status-subpage-tab"
data-bs-toggle="pill"
data-bs-target="#status-subpage"
type="button"
role="tab"
aria-controls="status-subpage"
aria-selected="true"
>
状态
</button>
</li>
<li class="nav-item" role="presentation">
<button
class="nav-link"
id="basic-config-tab"
data-bs-toggle="pill"
data-bs-target="#basic-config-subpage"
type="button"
role="tab"
aria-controls="basic-config-subpage"
aria-selected="false"
>
基本设置
</button>
</li>
<li class="nav-item" role="presentation">
<button
class="nav-link"
id="security-config-tab"
data-bs-toggle="pill"
data-bs-target="#security-config-subpage"
type="button"
role="tab"
aria-controls="security-config-subpage"
aria-selected="false"
>
设备安全
</button>
</li>
</ul>
<div class="tab-content">
<div
class="tab-pane fade show active"
id="status-subpage"
role="tabpanel"
aria-labelledby="status-subpage-tab"
></div>
<div
class="tab-pane fade"
id="basic-config-subpage"
role="tabpanel"
aria-labelledby="basic-config-tab"
></div>
<div
class="tab-pane fade"
id="security-config-subpage"
role="tabpanel"
aria-labelledby="security-config-tab"
></div>
</div>
</div>

View File

@@ -0,0 +1,44 @@
(() => {
const REQUIRE_BASE =
"../../aura/ui/pages/configSubPages/behaviourCtrl/settings";
const {
settingsRenderer,
} = require(`${REQUIRE_BASE}/../../../../composables/settingsRenderer`);
const { basicSettings } = require(`${REQUIRE_BASE}/basic`);
const {
updatePlsSettingsFromLocal,
updatePlsRulesFromLocal,
} = require(`${REQUIRE_BASE}/../../../../composables/plsConfigManager`);
const initStatusPage = () => {
global.__HUGO_AURA_LOADER__[
"Aura.UI.Assistant.Config.BehaviourCtrl.PlsStatus"
].active = true;
};
const initBasicSettingsPage = () => {
const basicSubPageEl = document.getElementById("basic-config-subpage");
settingsRenderer(basicSubPageEl, basicSettings);
};
const renderSubPages = async () => {
await updatePlsSettingsFromLocal();
await updatePlsRulesFromLocal();
initBasicSettingsPage();
};
const onMounted = () => {
const rootEl = document.getElementById("acs-behaviour-control-el");
initStatusPage();
renderSubPages();
setTimeout(() => {
rootEl.classList.remove("acs-behaviour-control-hidden");
}, 500);
};
onMounted();
})();

View File

@@ -0,0 +1,78 @@
.acs-behaviour-control-pls-status-page {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.acs-behaviour-control-pls-status-page p {
font-family: sans-serif;
}
.acs-bc-pls-status-page-pls-description {
margin-top: 0.5rem;
max-width: 80%;
opacity: 0.35;
font-size: small;
text-align: center;
}
.acs-bc-pls-status-page-main-logo {
max-width: 13.5%;
opacity: 0.45;
margin-top: 1.5rem;
margin-bottom: 1.5rem;
}
.acs-bc-pls-status-page-status-el {
display: flex;
align-items: center;
width: 40%;
padding: 0.625rem 0.25rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.25);
}
.acs-bc-pls-status-page-status-el div {
display: flex;
align-items: center;
}
.acs-bc-pls-status-page-status-area {
flex-grow: 1;
justify-content: flex-end;
}
.acs-bc-pls-status-page-status-area-circle {
height: 10px;
width: 10px;
border-radius: 100%;
margin-right: 8px;
margin-top: 2px;
}
.acs-bc-pls-status-page-status-area.pending
.acs-bc-pls-status-page-status-area-circle {
background-color: rgba(0, 0, 0, 0.375);
}
.acs-bc-pls-status-page-status-area.pending p {
opacity: 0.5;
}
.acs-bc-pls-status-page-status-area.success
.acs-bc-pls-status-page-status-area-circle {
background-color: rgb(0, 175, 38);
}
.acs-bc-pls-status-page-status-area.success p {
color: rgb(0, 150, 33);
}
.acs-bc-pls-status-page-status-area.failed
.acs-bc-pls-status-page-status-area-circle {
background-color: rgb(175, 0, 0);
}
.acs-bc-pls-status-page-status-area.failed p {
color: rgb(175, 0, 0);
}

View File

@@ -0,0 +1,51 @@
<div class="acs-behaviour-control-pls-status-page">
<p class="acs-bc-pls-status-page-pls-description">
HugoAura ProxyLayerServices (Aura-PLS) 是基于 Python + MITMProxy 实现的代理服务,
用于解密并修改希沃基础服务 (SeewoCore) 的 MQTT 数据包,
实现行为监控、伪造上报等功能
</p>
<img
src="../../aura/ui/static/aura_pls.png"
class="acs-bc-pls-status-page-main-logo"
/>
<div class="acs-bc-pls-status-page-status-el">
<p>安装状态</p>
<div
class="acs-bc-pls-status-page-status-area pending"
id="acs-bc-psp-installStatus-container"
>
<span class="acs-bc-pls-status-page-status-area-circle"></span>
<p id="acs-bc-psp-installStatus-text">未安装</p>
</div>
</div>
<div class="acs-bc-pls-status-page-status-el">
<p>启动状态</p>
<div
class="acs-bc-pls-status-page-status-area pending"
id="acs-bc-psp-launchStatus-container"
>
<span class="acs-bc-pls-status-page-status-area-circle"></span>
<p id="acs-bc-psp-launchStatus-text">未启动</p>
</div>
</div>
<div class="acs-bc-pls-status-page-status-el">
<p>连接状态</p>
<div
class="acs-bc-pls-status-page-status-area pending"
id="acs-bc-psp-connStatus-container"
>
<span class="acs-bc-pls-status-page-status-area-circle"></span>
<p id="acs-bc-psp-connStatus-text">已断开</p>
</div>
</div>
<div class="acs-bc-pls-status-page-status-el" style="border-bottom: none">
<p>版本</p>
<div class="acs-bc-pls-status-page-status-area">
<p id="acs-bc-psp-version-text">不可用</p>
</div>
</div>
</div>

View File

@@ -0,0 +1,86 @@
(() => {
const REQUIRE_BASE = "../../aura/ui/pages/configSubPages/behaviourCtrl";
const IPC_METHOD_BASE = "$aura.pls";
const {
updatePlsStatusFromLocal,
} = require(`${REQUIRE_BASE}/../../../composables/plsConfigManager`);
const updateStatusEl = (
areaContainerId,
areaTextId,
target,
text = false
) => {
const areaContainerEl = document.getElementById(areaContainerId);
const areaContainerText = document.getElementById(areaTextId);
switch (target) {
case "PENDING":
areaContainerEl.className =
"acs-bc-pls-status-page-status-area pending";
break;
case "SUCCESS":
areaContainerEl.className =
"acs-bc-pls-status-page-status-area success";
break;
case "FAILED":
areaContainerEl.className = "acs-bc-pls-status-page-status-area failed";
break;
case "WARNING":
areaContainerEl.className =
"acs-bc-pls-status-page-status-area warning";
break;
default:
return false;
}
areaContainerText.textContent = text ? text : "";
return true;
};
const updateStatus = async () => {
const curPlsStats = await updatePlsStatusFromLocal();
const acIdInst = "acs-bc-psp-installStatus-container";
const atIdInst = "acs-bc-psp-installStatus-text";
switch (curPlsStats.installed) {
case true:
updateStatusEl(acIdInst, atIdInst, "SUCCESS", "已安装");
break;
case false:
updateStatusEl(acIdInst, atIdInst, "PENDING", "未安装");
}
const acIdLaunch = "acs-bc-psp-launchStatus-container";
const atIdLaunch = "acs-bc-psp-launchStatus-text";
switch (curPlsStats.launched) {
case true:
updateStatusEl(acIdLaunch, atIdLaunch, "SUCCESS", "已启动");
break;
case false:
updateStatusEl(acIdLaunch, atIdLaunch, "PENDING", "未启动");
break;
}
const acIdConn = "acs-bc-psp-connStatus-container";
const atIdConn = "acs-bc-psp-connStatus-text";
switch (curPlsStats.connected) {
case true:
updateStatusEl(acIdConn, atIdConn, "SUCCESS", "已连接");
break;
case false:
updateStatusEl(acIdConn, atIdConn, "FAILED", "连接失败");
break;
}
if (curPlsStats.version && curPlsStats.version !== "未知") {
const versionTextEl = document.getElementById("acs-bc-psp-version-text");
versionTextEl.textContent = "v" + curPlsStats.version;
}
};
const onMounted = () => {
updateStatus();
};
onMounted();
})();

View File

@@ -0,0 +1,45 @@
const REQUIRE_BASE = ".";
const {
updatePlsConfigToRemote,
} = require(`${REQUIRE_BASE}/../../../../composables/plsConfigManager`);
const basicSettings = [
{
id: 0,
categoryName: "可访问性",
child: [
{
index: 0,
id: "authToken",
type: "input",
subType: "text",
name: "WebSocket 认证密钥",
description: "选择一个安全的密钥, 用于 PLS 侧验证 Aura 前端身份",
restart: true,
reload: false,
restartPLS: false,
associateVal: null,
auraIf: () => true,
defaultValue: "",
placeHolder: "输入一个密钥",
valueGetter: () => {
return global.__HUGO_AURA_CONFIG__.plsToken;
},
callbackFn: (newVal) => {
if (newVal === "" || !newVal)
return { valid: false, hint: "请输入认证密钥" };
if (newVal.length < 8) {
return { valid: false, hint: "至少输入 8 位字符" };
}
global.__HUGO_AURA_CONFIG__.plsToken = newVal;
return { valid: true };
},
},
],
},
];
module.exports = { basicSettings };

View File

@@ -1,6 +1,5 @@
.aura-config-subpage-disable-limit-root {
opacity: 1;
transition: opacity 0.5s;
}

View File

@@ -6,6 +6,20 @@
<li class="nav-item" role="presentation">
<button
class="nav-link active"
id="aura-subpage-tab"
data-bs-toggle="pill"
data-bs-target="#aura-subpage"
type="button"
role="tab"
aria-controls="aura-subpage"
aria-selected="true"
>
Aura 设置
</button>
</li>
<li class="nav-item" role="presentation">
<button
class="nav-link"
id="auth-subpage-tab"
data-bs-toggle="pill"
data-bs-target="#auth-subpage"
@@ -14,7 +28,7 @@
aria-controls="auth-subpage"
aria-selected="true"
>
认证与基础设施
认证与环境
</button>
</li>
<li class="nav-item" role="presentation">
@@ -35,6 +49,12 @@
<div class="tab-content">
<div
class="tab-pane fade show active"
id="aura-subpage"
role="tabpanel"
aria-labelledby="aura-subpage-tab"
></div>
<div
class="tab-pane fade show"
id="auth-subpage"
role="tabpanel"
aria-labelledby="auth-subpage-tab"

View File

@@ -5,9 +5,15 @@
const {
settingsRenderer,
} = require("../../aura/ui/composables/settingsRenderer");
const { auraSettings } = require(`${pathBase}/aura`);
const { authSettings } = require(`${pathBase}/auth`);
const { banAuditSettings } = require(`${pathBase}/audit`);
const initAuraSubPage = () => {
const auraSettingsSubPageEl = document.getElementById("aura-subpage");
settingsRenderer(auraSettingsSubPageEl, auraSettings);
};
const initAuthSubPage = () => {
const authSubPageEl = document.getElementById("auth-subpage");
settingsRenderer(authSubPageEl, authSettings);
@@ -19,6 +25,7 @@
};
const onMounted = () => {
initAuraSubPage();
initAuthSubPage();
initBanAuditSubPage();

View File

View File

@@ -0,0 +1,113 @@
const auraSettings = [
{
id: 0,
categoryName: "安全性",
child: [
{
index: 0,
id: "enableAuraSettingsPasswd",
type: "switch",
name: "启用访问密码",
description: "启用后, Aura 设置 UI 需要输入密码才可访问",
restart: false,
reload: false,
tip: true,
tipTitle: "在 0.1.1-beta 版本发布后, 启用访问密码将加密配置文件",
associateVal: null,
auraIf: () => true,
defaultValue: false,
valueGetter: () => {
return global.__HUGO_AURA_CONFIG__.auraSettings
.settingsPasswordEnabled;
},
callbackFn: (newVal) => {
if (typeof newVal !== "boolean") return;
global.__HUGO_AURA_CONFIG__.auraSettings.settingsPasswordEnabled =
newVal;
// TODO: Trigger enc config
},
},
{
index: 1,
id: "auraSettingsPasswd",
type: "input",
subType: "password",
name: "访问密码",
description: "此密码将用于访问 Aura 设置 UI",
restart: false,
reload: false,
associateVal: ["auraSettings.settingsPasswordEnabled"],
auraIf: () => {
return global.__HUGO_AURA_CONFIG__.auraSettings
.settingsPasswordEnabled;
},
defaultValue: "",
placeHolder: "留空表示不修改, 保留已设置值",
valueGetter: () => {
return "";
},
callbackFn: (newVal) => {
if (newVal === "" || !newVal) return { valid: true };
if (newVal.length < 8)
return { valid: false, hint: "请输入至少 8 位密码" };
const hasNumber = /[0-9]/.test(newVal);
const hasLetter = /[a-zA-Z]/.test(newVal);
const hasSpecial = /[^a-zA-Z0-9]/.test(newVal);
const typeCount = [hasNumber, hasLetter, hasSpecial].filter(
Boolean
).length;
if (typeCount < 2) {
return {
valid: false,
hint: "请包含数字 / 字母 / 特殊字符中的至少 2 种",
};
}
const crypto = require("crypto");
const result = crypto
.createHash("sha512")
.update(newVal + "EndlessX")
.digest("hex")
.toUpperCase();
global.__HUGO_AURA_CONFIG__.auraSettings.settingsPasswordWithSalt =
result;
return { valid: true };
},
},
],
},
{
id: 1,
categoryName: "外观",
child: [
{
index: 0,
id: "enablePasswdDialogBlur",
type: "switch",
name: "密码验证框毛玻璃效果",
description: "启用后, 密码验证时, 背景将具有毛玻璃效果",
restart: false,
reload: false,
tip: true,
tipTitle: "不建议在较旧 (如 i5 8 代) 机型上开启, 可能导致性能问题",
associateVal: null,
auraIf: () => true,
defaultValue: true,
valueGetter: () => {
return global.__HUGO_AURA_CONFIG__.auraSettings.appearance
.enablePasswdDialogBlur;
},
callbackFn: (newVal) => {
if (typeof newVal !== "boolean") return;
global.__HUGO_AURA_CONFIG__.auraSettings.appearance.enablePasswdDialogBlur =
newVal;
},
},
],
},
];
module.exports = { auraSettings };

View File

@@ -1,7 +1,7 @@
const authSettings = [
{
id: 0,
categoryName: "身份验证",
categoryName: "管家身份验证",
child: [
{
index: 0,
@@ -114,8 +114,8 @@ const authSettings = [
].enabled;
},
defaultValue: "hybrid",
templates: ["hybrid", "remoteOnly", "passwordOnly"],
templateLabels: ["混合", "仅二维码", "仅密码"],
templates: ["default", "hybrid", "remoteOnly", "passwordOnly"],
templateLabels: ["默认", "混合", "仅二维码", "仅密码"],
valueGetter: () => {
return global.__HUGO_AURA_CONFIG__.rewrite[
"vendor/passwordValidation"

0
src/aura/ui/pages/headerIcon/headerIcon.css Normal file → Executable file
View File

0
src/aura/ui/pages/headerIcon/headerIcon.html Normal file → Executable file
View File

0
src/aura/ui/pages/headerIcon/headerIcon.js Normal file → Executable file
View File

30
src/aura/ui/pls/pushHandler.js Executable file
View File

@@ -0,0 +1,30 @@
// @ts-check
const REQUIRE_BASE = ".";
const { basicRouteHandler } = require(`${REQUIRE_BASE}/routes/basic`);
const { configRouteHandler } = require(`${REQUIRE_BASE}/routes/config`);
/**
*
* @param {PLSPush} parsedWsMsg
* @returns
*/
const pushMsgHandler = (parsedWsMsg) => {
if (!parsedWsMsg.type) return false;
const msgCategory = parsedWsMsg.type.split(".")[0];
switch (msgCategory) {
case "basic":
basicRouteHandler(parsedWsMsg);
break;
case "config":
configRouteHandler(parsedWsMsg);
break;
default:
break;
}
};
module.exports = { pushMsgHandler };

33
src/aura/ui/pls/routes/basic.js Executable file
View File

@@ -0,0 +1,33 @@
// @ts-check
const IPC_METHOD_BASE = "$aura.pls";
/**
*
* @param {PLSPush} parsedWsMsg
* @returns
*/
const basicRouteHandler = (parsedWsMsg) => {
const target = parsedWsMsg.type.split(".").slice(-1)[0];
switch (target) {
case "pushPlsInfo":
global.__HUGO_AURA_GLOBAL__.plsStats.status = parsedWsMsg.data.status;
global.__HUGO_AURA_GLOBAL__.plsStats.version = parsedWsMsg.data.version;
global.ipcRenderer.invoke(
`${IPC_METHOD_BASE}.updatePlsStats`,
global.__HUGO_AURA_GLOBAL__.plsStats
);
console.debug(
"[HugoAura / UI / PLS Routes / DEBUG] Updated plsStats basic info:",
global.__HUGO_AURA_GLOBAL__.plsStats
);
break;
default:
return false;
}
return true;
};
module.exports = { basicRouteHandler };

View File

@@ -0,0 +1,32 @@
// @ts-check
const IPC_METHOD_BASE = "$aura.pls";
/**
*
* @param {PLSPush} parsedWsMsg
* @returns
*/
const configRouteHandler = (parsedWsMsg) => {
const target = parsedWsMsg.type.split(".").slice(-1)[0];
switch (target) {
case "pushBasicConfig":
global.ipcRenderer.invoke(
`${IPC_METHOD_BASE}.updatePlsSettings`,
parsedWsMsg.data
);
break;
case "pushRuleSettings":
global.ipcRenderer.invoke(
`${IPC_METHOD_BASE}.updatePlsRules`,
parsedWsMsg.data
);
break;
default:
return false;
}
return true;
};
module.exports = { configRouteHandler };

0
src/aura/ui/static/aura.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 673 B

After

Width:  |  Height:  |  Size: 673 B

BIN
src/aura/ui/static/aura_pls.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

0
src/aura/ui/static/config/about.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

0
src/aura/ui/static/config/behaviour_mon.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

0
src/aura/ui/static/config/no_limitations.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

0
src/aura/ui/static/config/plugin.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

300
src/core/hook.js Normal file → Executable file
View File

@@ -1,139 +1,161 @@
if (!global.__HUGO_AURA__) {
global.__HUGO_AURA__ = {
hookedWindows: new Map(),
hooks: null,
configInit: false,
};
}
const fs = require("fs");
const util = require("util");
const path = require("path");
const os = require("os");
const HooksManager = require("../aura/init/rendererHook/hooksManager");
const NetworkHook = require("../aura/init/rendererHook/networkHook");
const configManager = require("../aura/init/shared/configManager");
const { buildIpcMain } = require("../aura/init/main/ipcHandler");
const initLogger = () => {
const logDir = path.join(os.homedir(), "Documents", "HugoAura", "logs");
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
const logFile = path.join(
logDir,
`main-process-${new Date().toISOString().replace(/:/g, "-")}.log`
);
const logStream = fs.createWriteStream(logFile, { flags: "a" });
const originalConsole = {
log: console.log,
error: console.error,
warn: console.warn,
info: console.info,
debug: console.debug,
};
console.log = function (...args) {
const msg = util.format("[LOG] ", ...args) + "\n";
logStream.write(msg);
originalConsole.log.apply(console, args);
};
console.error = function (...args) {
const msg = util.format("[ERROR] ", ...args) + "\n";
logStream.write(msg);
originalConsole.error.apply(console, args);
};
console.warn = function (...args) {
const msg = util.format("[WARN] ", ...args) + "\n";
logStream.write(msg);
originalConsole.warn.apply(console, args);
};
console.info = function (...args) {
const msg = util.format("[INFO] ", ...args) + "\n";
logStream.write(msg);
originalConsole.info.apply(console, args);
};
console.debug = function (...args) {
if (!process.argv.includes("--aura-debug")) return;
const msg = util.format("[DEBUG] ", ...args) + "\n";
logStream.write(msg);
originalConsole.debug.apply(console, args);
};
process.on("uncaughtException", (err) => {
console.error("UNCAUGHT EXCEPTION:", err);
});
console.log("Logger initialized. Log file:", logFile);
};
module.exports = function ({ central, windowName, config }) {
process.stdout.isTTY = true;
process.stderr.isTTY = true;
const electron = central(1);
const app = electron.app;
if (!global.__HUGO_AURA__.central) global.__HUGO_AURA__.central = central;
global.reloadApp = () => {
app.relaunch({ args: process.argv.slice(1).concat(["--inspect 5858"]) });
app.exit(0);
};
initLogger();
console.log("[HugoAura / Loaded] Aura is loaded!");
if (!global.__HUGO_AURA__.ipcInit) {
buildIpcMain(electron);
global.__HUGO_AURA__.ipcInit = true;
}
const hooksManager = new HooksManager();
configManager.ensureConfigExists();
const loadedConfig = configManager.loadConfig();
if (!global.__HUGO_AURA__.configInit) global.__HUGO_AURA__.configInit = true;
const hooks = hooksManager.loadHooks();
if (loadedConfig.devTools && !config.canOpenDevTool) {
config.canOpenDevTool = true;
}
const webContentsCreatedListener = (_event, webContents) => {
const hookConfig = hooks.get(windowName);
const initNetworkHook = () => {
const networkHook = new NetworkHook();
networkHook.installHook(webContents.session, loadedConfig);
console.debug(
`[HugoAura / Init / Done / NetworkHook] Network Hook for ${windowName} installed.`
);
};
initNetworkHook();
if (hookConfig) {
hooksManager.handleWindowHook(webContents, hookConfig, windowName);
} else {
console.debug(
`[HugoAura / Init] Window ${windowName} has no corresponding hook, ignoring...`
);
}
};
app.once("web-contents-created", webContentsCreatedListener);
return () => {
app.removeListener("web-contents-created", webContentsCreatedListener);
};
};
// @ts-check
if (!global.__HUGO_AURA__) {
/**
* @type {import("../aura/types/main/core").MainProcessGlobal}
*/
const __HUGO_AURA__ = {
hookedWindows: new Map(),
hooks: new Map(),
configInit: false,
plsStats: null,
plsSettings: null,
plsRules: null,
};
global.__HUGO_AURA__ = __HUGO_AURA__;
}
if (!global.__HUGO_AURA_CONFIG__) {
global.__HUGO_AURA_CONFIG__ = {};
}
const fs = require("fs");
const util = require("util");
const path = require("path");
const os = require("os");
const HooksManager = require("../aura/init/rendererHook/hooksManager");
const NetworkHook = require("../aura/init/rendererHook/networkHook");
const configManager = require("../aura/init/shared/configManager");
const { buildIpcMain } = require("../aura/init/main/ipcHandler");
const initLogger = () => {
const logDir = path.join(os.homedir(), "Documents", "HugoAura", "logs");
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
const logFile = path.join(
logDir,
`main-process-${new Date().toISOString().replace(/:/g, "-")}.log`
);
const logStream = fs.createWriteStream(logFile, { flags: "a" });
const originalConsole = {
log: console.log,
error: console.error,
warn: console.warn,
info: console.info,
debug: console.debug,
};
console.log = function (...args) {
const msg = util.format("[LOG] ", ...args) + "\n";
logStream.write(msg);
originalConsole.log.apply(console, args);
};
console.error = function (...args) {
const msg = util.format("[ERROR] ", ...args) + "\n";
logStream.write(msg);
originalConsole.error.apply(console, args);
};
console.warn = function (...args) {
const msg = util.format("[WARN] ", ...args) + "\n";
logStream.write(msg);
originalConsole.warn.apply(console, args);
};
console.info = function (...args) {
const msg = util.format("[INFO] ", ...args) + "\n";
logStream.write(msg);
originalConsole.info.apply(console, args);
};
console.debug = function (...args) {
if (!process.argv.includes("--aura-debug")) return;
const msg = util.format("[DEBUG] ", ...args) + "\n";
logStream.write(msg);
originalConsole.debug.apply(console, args);
};
process.on("uncaughtException", (err) => {
console.error("UNCAUGHT EXCEPTION:", err);
});
console.log("Logger initialized. Log file:", logFile);
};
/**
*
* @param {import("../aura/types/main/core").LauncherArgs} param0
* @returns
*/
const launcher = ({ central, windowName, config }) => {
process.stdout.isTTY = true;
process.stderr.isTTY = true;
const electron = central(1);
const app = electron.app;
if (!global.__HUGO_AURA__.central) global.__HUGO_AURA__.central = central;
global.reloadApp = () => {
app.relaunch({ args: process.argv.slice(1).concat(["--inspect 5858"]) });
app.exit(0);
};
initLogger();
console.log("[HugoAura / Loaded] Aura is loaded!");
configManager.ensureConfigExists();
const loadedConfig = configManager.loadConfig();
if (!global.__HUGO_AURA__.configInit) global.__HUGO_AURA__.configInit = true;
global.__HUGO_AURA_CONFIG__ = loadedConfig;
if (!global.__HUGO_AURA__.ipcInit) {
buildIpcMain(electron);
global.__HUGO_AURA__.ipcInit = true;
}
const hooksManager = new HooksManager();
const hooks = hooksManager.loadHooks();
if (loadedConfig.devTools && !config.canOpenDevTool) {
config.canOpenDevTool = true;
}
const webContentsCreatedListener = (_event, webContents) => {
const hookConfig = hooks.get(windowName);
const initNetworkHook = () => {
const networkHook = new NetworkHook();
networkHook.installHook(webContents.session, loadedConfig);
console.debug(
`[HugoAura / Init / Done / NetworkHook] Network Hook for ${windowName} installed.`
);
};
initNetworkHook();
if (hookConfig) {
hooksManager.handleWindowHook(webContents, hookConfig, windowName);
} else {
console.debug(
`[HugoAura / Init] Window ${windowName} has no corresponding hook, ignoring...`
);
}
};
app.once("web-contents-created", webContentsCreatedListener);
return () => {
app.removeListener("web-contents-created", webContentsCreatedListener);
};
};
module.exports = launcher;

2
src/core/preload.js Normal file → Executable file
View File

@@ -1,4 +1,4 @@
const __AURA_VERSION__ = "0.1.0-beta";
const __AURA_VERSION__ = "0.1.1-pre-I";
(() => {
if (!global.__HUGO_AURA__) {

54
src/core/zeron.js Normal file → Executable file
View File

@@ -1,23 +1,31 @@
// Ex-Early Load Pro Plus Max ++
console.debug("[HugoAura / Zeron] Early load script loaded.");
module.exports = function (central) {
const originalCentral = { ...central };
const genHookedWS = require("../aura/init/zeron/hookWS");
console.debug(
"[HugoAura / Zeron / WebSocket Hook] WebSocket hooked class generated."
);
return new Proxy(central, {
apply(target, thisArg, args) {
switch (args[0]) {
case 18:
return genHookedWS(central);
default:
return Reflect.apply(target, thisArg, args);
}
},
});
};
// Ex-Early Load Pro Plus Max ++
console.debug("[HugoAura / Zeron] Early load script loaded.");
const appendSwitch = () => {
const { app } = require("electron");
app.commandLine.appendSwitch("host-rules", "MAP *.hugoaura.local 127.0.0.1");
};
module.exports = function (central) {
const originalCentral = { ...central };
appendSwitch();
const genHookedWS = require("../aura/init/zeron/hookWS");
console.debug(
"[HugoAura / Zeron / WebSocket Hook] WebSocket hooked class generated."
);
return new Proxy(central, {
apply(target, thisArg, args) {
switch (args[0]) {
case 18:
return genHookedWS(central);
default:
return Reflect.apply(target, thisArg, args);
}
},
});
};