🏗️ refactor data access layer and enhance service architecture

This commit is contained in:
刘嘉伟 2025-02-25 11:57:54 +08:00
parent ff8b01c360
commit 9946b50d5d
30 changed files with 2255 additions and 2199 deletions

View File

@ -1,337 +1,337 @@
import { app, dialog, ipcMain, shell } from "electron"; // import { app, dialog, ipcMain, shell } from "electron";
import { clearConfig, getConfig, saveConfig } from "../storage/config"; // import { clearConfig, getConfig, saveConfig } from "../storage/config";
import { clearVersion, listVersion } from "../storage/version"; // import { clearVersion, listVersion } from "../storage/version";
import { genIniConfig, genTomlConfig, stopFrpcProcess } from "./frpc"; // import { genIniConfig, genTomlConfig, stopFrpcProcess } from "./frpc";
import { clearProxy, insertProxy, listProxy } from "../storage/proxy"; // import { clearProxy, insertProxy, listProxy } from "../storage/proxy";
import path from "path"; // import path from "path";
import fs from "fs"; // import fs from "fs";
import { logDebug, logError, logInfo, LogModule, logWarn } from "../utils/log"; // import { logDebug, logError, logInfo, LogModule, logWarn } from "../utils/log";
//
const toml = require("@iarna/toml"); // const toml = require("@iarna/toml");
const { v4: uuidv4 } = require("uuid"); // const { v4: uuidv4 } = require("uuid");
//
export const initConfigApi = win => { // export const initConfigApi = win => {
ipcMain.on("config.saveConfig", async (event, args) => { // ipcMain.on("config.saveConfig", async (event, args) => {
logInfo(LogModule.APP, "Attempting to save configuration."); // logInfo(LogModule.APP, "Attempting to save configuration.");
saveConfig(args, (err, numberOfUpdated, upsert) => { // saveConfig(args, (err, numberOfUpdated, upsert) => {
if (!err) { // if (!err) {
const start = args.systemSelfStart || false; // const start = args.systemSelfStart || false;
logDebug(LogModule.APP, "Startup status set to: " + start); // logDebug(LogModule.APP, "Startup status set to: " + start);
app.setLoginItemSettings({ // app.setLoginItemSettings({
openAtLogin: start, //win // openAtLogin: start, //win
openAsHidden: start //macOs // openAsHidden: start //macOs
}); // });
logInfo(LogModule.APP, "Configuration saved successfully."); // logInfo(LogModule.APP, "Configuration saved successfully.");
} else { // } else {
logError(LogModule.APP, `Error saving configuration: ${err}`); // logError(LogModule.APP, `Error saving configuration: ${err}`);
} // }
event.reply("Config.saveConfig.hook", { // event.reply("Config.saveConfig.hook", {
err: err, // err: err,
numberOfUpdated: numberOfUpdated, // numberOfUpdated: numberOfUpdated,
upsert: upsert // upsert: upsert
}); // });
}); // });
}); // });
//
ipcMain.on("config.getConfig", async (event, args) => { // ipcMain.on("config.getConfig", async (event, args) => {
logInfo(LogModule.APP, "Requesting configuration."); // logInfo(LogModule.APP, "Requesting configuration.");
getConfig((err, doc) => { // getConfig((err, doc) => {
if (err) { // if (err) {
logError(LogModule.APP, `Error retrieving configuration: ${err}`); // logError(LogModule.APP, `Error retrieving configuration: ${err}`);
} // }
event.reply("Config.getConfig.hook", { // event.reply("Config.getConfig.hook", {
err: err, // err: err,
data: doc // data: doc
}); // });
}); // });
}); // });
//
ipcMain.on("config.versions", event => { // ipcMain.on("config.versions", event => {
logInfo(LogModule.APP, "Requesting version information."); // logInfo(LogModule.APP, "Requesting version information.");
listVersion((err, doc) => { // listVersion((err, doc) => {
if (err) { // if (err) {
logError(LogModule.APP, `Error retrieving version information: ${err}`); // logError(LogModule.APP, `Error retrieving version information: ${err}`);
} // }
event.reply("Config.versions.hook", { // event.reply("Config.versions.hook", {
err: err, // err: err,
data: doc // data: doc
}); // });
}); // });
}); // });
//
ipcMain.on("config.hasConfig", event => { // ipcMain.on("config.hasConfig", event => {
logInfo(LogModule.APP, "Checking if configuration exists."); // logInfo(LogModule.APP, "Checking if configuration exists.");
getConfig((err, doc) => { // getConfig((err, doc) => {
if (err) { // if (err) {
logError(LogModule.APP, `Error checking configuration: ${err}`); // logError(LogModule.APP, `Error checking configuration: ${err}`);
} // }
event.reply("Config.getConfig.hook", { // event.reply("Config.getConfig.hook", {
err: err, // err: err,
data: doc // data: doc
}); // });
}); // });
}); // });
//
ipcMain.on("config.exportConfig", async (event, args) => { // ipcMain.on("config.exportConfig", async (event, args) => {
logInfo(LogModule.APP, "Attempting to export configuration."); // logInfo(LogModule.APP, "Attempting to export configuration.");
const result = await dialog.showOpenDialog({ // const result = await dialog.showOpenDialog({
properties: ["openDirectory"] // properties: ["openDirectory"]
}); // });
const outputDirectory = result.filePaths[0]; // const outputDirectory = result.filePaths[0];
if (!outputDirectory) { // if (!outputDirectory) {
logWarn(LogModule.APP, "Export canceled by user."); // logWarn(LogModule.APP, "Export canceled by user.");
return; // return;
} // }
//
logInfo( // logInfo(
LogModule.APP, // LogModule.APP,
`Exporting configuration to directory ${outputDirectory} with type: ${args}` // `Exporting configuration to directory ${outputDirectory} with type: ${args}`
); // );
getConfig((err1, config) => { // getConfig((err1, config) => {
if (!err1 && config) { // if (!err1 && config) {
listProxy((err2, proxys) => { // listProxy((err2, proxys) => {
if (!err2) { // if (!err2) {
let configContent = ""; // let configContent = "";
if (args === "ini") { // if (args === "ini") {
configContent = genIniConfig(config, proxys); // configContent = genIniConfig(config, proxys);
} else if (args === "toml") { // } else if (args === "toml") {
configContent = genTomlConfig(config, proxys); // configContent = genTomlConfig(config, proxys);
} // }
const configPath = path.join( // const configPath = path.join(
outputDirectory, // outputDirectory,
`frpc-desktop.${args}` // `frpc-desktop.${args}`
); // );
fs.writeFile( // fs.writeFile(
configPath, // 配置文件目录 // configPath, // 配置文件目录
configContent, // 配置文件内容 // configContent, // 配置文件内容
{ flag: "w" }, // { flag: "w" },
err => { // err => {
if (err) { // if (err) {
logError( // logError(
LogModule.APP, // LogModule.APP,
`Error writing configuration file: ${err}` // `Error writing configuration file: ${err}`
); // );
event.reply("config.exportConfig.hook", { // event.reply("config.exportConfig.hook", {
data: "导出错误", // data: "导出错误",
err: err // err: err
}); // });
} else { // } else {
logInfo( // logInfo(
LogModule.APP, // LogModule.APP,
"Configuration exported successfully." // "Configuration exported successfully."
); // );
event.reply("Config.exportConfig.hook", { // event.reply("Config.exportConfig.hook", {
data: { // data: {
configPath: configPath // configPath: configPath
} // }
}); // });
} // }
} // }
); // );
} else { // } else {
logError(LogModule.APP, `Error listing proxies: ${err2}`); // logError(LogModule.APP, `Error listing proxies: ${err2}`);
} // }
}); // });
} else { // } else {
logError(LogModule.APP, `Error retrieving configuration: ${err1}`); // logError(LogModule.APP, `Error retrieving configuration: ${err1}`);
} // }
}); // });
}); // });
//
const parseTomlConfig = (tomlPath: string) => { // const parseTomlConfig = (tomlPath: string) => {
logInfo(LogModule.APP, `Parsing TOML configuration from ${tomlPath}`); // logInfo(LogModule.APP, `Parsing TOML configuration from ${tomlPath}`);
const importConfigPath = tomlPath; // const importConfigPath = tomlPath;
const tomlData = fs.readFileSync(importConfigPath, "utf-8"); // const tomlData = fs.readFileSync(importConfigPath, "utf-8");
logInfo(LogModule.APP, "Configuration content read successfully."); // logInfo(LogModule.APP, "Configuration content read successfully.");
const sourceConfig = toml.parse(tomlData); // const sourceConfig = toml.parse(tomlData);
// 解析config // // 解析config
const targetConfig: FrpConfig = { // const targetConfig: FrpConfig = {
currentVersion: null, // currentVersion: null,
serverAddr: sourceConfig.serverAddr || "", // serverAddr: sourceConfig.serverAddr || "",
serverPort: sourceConfig.serverPort || "", // serverPort: sourceConfig.serverPort || "",
authMethod: sourceConfig?.user // authMethod: sourceConfig?.user
? "multiuser" // ? "multiuser"
: sourceConfig?.auth?.method || "", // : sourceConfig?.auth?.method || "",
authToken: sourceConfig?.auth?.token || "", // authToken: sourceConfig?.auth?.token || "",
transportHeartbeatInterval: // transportHeartbeatInterval:
sourceConfig?.transport?.heartbeatInterval || 30, // sourceConfig?.transport?.heartbeatInterval || 30,
transportHeartbeatTimeout: // transportHeartbeatTimeout:
sourceConfig?.transport?.heartbeatTimeout || 90, // sourceConfig?.transport?.heartbeatTimeout || 90,
tlsConfigEnable: sourceConfig?.transport?.tls?.enable || false, // tlsConfigEnable: sourceConfig?.transport?.tls?.enable || false,
tlsConfigCertFile: sourceConfig?.transport?.tls?.certFile || "", // tlsConfigCertFile: sourceConfig?.transport?.tls?.certFile || "",
tlsConfigKeyFile: sourceConfig?.transport?.tls?.keyFile || "", // tlsConfigKeyFile: sourceConfig?.transport?.tls?.keyFile || "",
tlsConfigServerName: sourceConfig?.transport?.tls?.serverName || "", // tlsConfigServerName: sourceConfig?.transport?.tls?.serverName || "",
tlsConfigTrustedCaFile: sourceConfig?.transport?.tls?.trustedCaFile || "", // tlsConfigTrustedCaFile: sourceConfig?.transport?.tls?.trustedCaFile || "",
logLevel: sourceConfig?.log?.level || "info", // logLevel: sourceConfig?.log?.level || "info",
logMaxDays: sourceConfig?.log?.maxDays || 3, // logMaxDays: sourceConfig?.log?.maxDays || 3,
proxyConfigProxyUrl: sourceConfig?.transport?.proxyURL || "", // proxyConfigProxyUrl: sourceConfig?.transport?.proxyURL || "",
proxyConfigEnable: Boolean(sourceConfig?.transport?.proxyURL) || false, // proxyConfigEnable: Boolean(sourceConfig?.transport?.proxyURL) || false,
user: sourceConfig?.user || "", // user: sourceConfig?.user || "",
metaToken: sourceConfig?.metadatas?.token || "", // metaToken: sourceConfig?.metadatas?.token || "",
systemSelfStart: false, // systemSelfStart: false,
systemStartupConnect: false, // systemStartupConnect: false,
systemSilentStartup: false, // systemSilentStartup: false,
webEnable: true, // webEnable: true,
webPort: sourceConfig?.webServer?.port || 57400, // webPort: sourceConfig?.webServer?.port || 57400,
transportProtocol: sourceConfig?.transport?.protocol || "tcp", // transportProtocol: sourceConfig?.transport?.protocol || "tcp",
transportDialServerTimeout: // transportDialServerTimeout:
sourceConfig?.transport?.dialServerTimeout || 10, // sourceConfig?.transport?.dialServerTimeout || 10,
transportDialServerKeepalive: // transportDialServerKeepalive:
sourceConfig?.transport?.dialServerKeepalive || 70, // sourceConfig?.transport?.dialServerKeepalive || 70,
transportPoolCount: sourceConfig?.transport?.poolCount || 0, // transportPoolCount: sourceConfig?.transport?.poolCount || 0,
transportTcpMux: sourceConfig?.transport?.tcpMux || true, // transportTcpMux: sourceConfig?.transport?.tcpMux || true,
transportTcpMuxKeepaliveInterval: // transportTcpMuxKeepaliveInterval:
sourceConfig?.transport?.tcpMuxKeepaliveInterval || 7200 // sourceConfig?.transport?.tcpMuxKeepaliveInterval || 7200
}; // };
let frpcProxys = []; // let frpcProxys = [];
// 解析proxy // // 解析proxy
if (sourceConfig?.proxies && sourceConfig.proxies.length > 0) { // if (sourceConfig?.proxies && sourceConfig.proxies.length > 0) {
const frpcProxys1 = sourceConfig.proxies.map(m => { // const frpcProxys1 = sourceConfig.proxies.map(m => {
const rm: Proxy = { // const rm: Proxy = {
_id: uuidv4(), // _id: uuidv4(),
name: m?.name, // name: m?.name,
type: m?.type, // type: m?.type,
localIp: m?.localIP || "", // localIp: m?.localIP || "",
localPort: m?.localPort || null, // localPort: m?.localPort || null,
remotePort: m?.remotePort || null, // remotePort: m?.remotePort || null,
customDomains: m?.customDomains || [], // customDomains: m?.customDomains || [],
subdomain: m.subdomain || "", // subdomain: m.subdomain || "",
basicAuth: m.basicAuth || false, // basicAuth: m.basicAuth || false,
httpUser: m.httpUser || "", // httpUser: m.httpUser || "",
httpPassword: m.httpPassword || "", // httpPassword: m.httpPassword || "",
// 以下为stcp参数 // // 以下为stcp参数
stcpModel: "visited", // stcpModel: "visited",
serverName: "", // serverName: "",
secretKey: m?.secretKey || "", // secretKey: m?.secretKey || "",
bindAddr: "", // bindAddr: "",
bindPort: null, // bindPort: null,
status: m?.status || true, // status: m?.status || true,
fallbackTo: m?.fallbackTo, // fallbackTo: m?.fallbackTo,
fallbackTimeoutMs: m?.fallbackTimeoutMs || 500, // fallbackTimeoutMs: m?.fallbackTimeoutMs || 500,
keepTunnelOpen: m?.keepTunnelOpen || false, // keepTunnelOpen: m?.keepTunnelOpen || false,
https2http: m?.https2http || false, // https2http: m?.https2http || false,
https2httpCaFile: m?.https2httpCaFile || "", // https2httpCaFile: m?.https2httpCaFile || "",
https2httpKeyFile: m?.https2httpKeyFile || "" // https2httpKeyFile: m?.https2httpKeyFile || ""
}; // };
return rm; // return rm;
}); // });
frpcProxys = [...frpcProxys, ...frpcProxys1]; // frpcProxys = [...frpcProxys, ...frpcProxys1];
logInfo(LogModule.APP, "Parsed proxies from configuration."); // logInfo(LogModule.APP, "Parsed proxies from configuration.");
} // }
// 解析stcp的访问者 // // 解析stcp的访问者
if (sourceConfig?.visitors && sourceConfig.visitors.length > 0) { // if (sourceConfig?.visitors && sourceConfig.visitors.length > 0) {
const frpcProxys2 = sourceConfig.visitors.map(m => { // const frpcProxys2 = sourceConfig.visitors.map(m => {
const rm: Proxy = { // const rm: Proxy = {
_id: uuidv4(), // _id: uuidv4(),
name: m?.name, // name: m?.name,
type: m?.type, // type: m?.type,
localIp: "", // localIp: "",
localPort: null, // localPort: null,
remotePort: null, // remotePort: null,
customDomains: [], // customDomains: [],
subdomain: m.subdomain || "", // subdomain: m.subdomain || "",
basicAuth: m.basicAuth || false, // basicAuth: m.basicAuth || false,
httpUser: m.httpUser || "", // httpUser: m.httpUser || "",
httpPassword: m.httpPassword || "", // httpPassword: m.httpPassword || "",
// 以下为stcp参数 // // 以下为stcp参数
stcpModel: "visitors", // stcpModel: "visitors",
serverName: m?.serverName, // serverName: m?.serverName,
secretKey: m?.secretKey || "", // secretKey: m?.secretKey || "",
bindAddr: m?.bindAddr, // bindAddr: m?.bindAddr,
bindPort: m?.bindPort, // bindPort: m?.bindPort,
status: m?.status || true, // status: m?.status || true,
fallbackTo: m?.fallbackTo, // fallbackTo: m?.fallbackTo,
fallbackTimeoutMs: m?.fallbackTimeoutMs || 500, // fallbackTimeoutMs: m?.fallbackTimeoutMs || 500,
keepTunnelOpen: m?.keepTunnelOpen || false, // keepTunnelOpen: m?.keepTunnelOpen || false,
https2http: m?.https2http || false, // https2http: m?.https2http || false,
https2httpCaFile: m?.https2httpCaFile || "", // https2httpCaFile: m?.https2httpCaFile || "",
https2httpKeyFile: m?.https2httpKeyFile || "" // https2httpKeyFile: m?.https2httpKeyFile || ""
}; // };
return rm; // return rm;
}); // });
frpcProxys = [...frpcProxys, ...frpcProxys2]; // frpcProxys = [...frpcProxys, ...frpcProxys2];
logInfo(LogModule.APP, "Parsed visitors from configuration."); // logInfo(LogModule.APP, "Parsed visitors from configuration.");
} // }
if (targetConfig) { // if (targetConfig) {
clearConfig(() => { // clearConfig(() => {
logInfo(LogModule.APP, "Clearing existing configuration."); // logInfo(LogModule.APP, "Clearing existing configuration.");
saveConfig(targetConfig); // saveConfig(targetConfig);
logInfo(LogModule.APP, "New configuration saved."); // logInfo(LogModule.APP, "New configuration saved.");
}); // });
} // }
if (frpcProxys && frpcProxys.length > 0) { // if (frpcProxys && frpcProxys.length > 0) {
clearProxy(() => { // clearProxy(() => {
frpcProxys.forEach(f => { // frpcProxys.forEach(f => {
insertProxy(f, err => { // insertProxy(f, err => {
if (err) { // if (err) {
logError(LogModule.APP, `Error inserting proxy: ${err}`); // logError(LogModule.APP, `Error inserting proxy: ${err}`);
} else { // } else {
logInfo(LogModule.APP, `Inserted proxy: ${JSON.stringify(f)}`); // logInfo(LogModule.APP, `Inserted proxy: ${JSON.stringify(f)}`);
} // }
}); // });
}); // });
}); // });
} // }
}; // };
//
ipcMain.on("config.importConfig", async (event, args) => { // ipcMain.on("config.importConfig", async (event, args) => {
logInfo(LogModule.APP, "Attempting to import configuration."); // logInfo(LogModule.APP, "Attempting to import configuration.");
const result = await dialog.showOpenDialog(win, { // const result = await dialog.showOpenDialog(win, {
properties: ["openFile"], // properties: ["openFile"],
filters: [ // filters: [
{ name: "FrpcConfig Files", extensions: ["toml", "ini"] } // 允许选择的文件类型 // { name: "FrpcConfig Files", extensions: ["toml", "ini"] } // 允许选择的文件类型
] // ]
}); // });
if (result.canceled) { // if (result.canceled) {
logWarn(LogModule.APP, "Import canceled by user."); // logWarn(LogModule.APP, "Import canceled by user.");
return; // return;
} else { // } else {
const filePath = result.filePaths[0]; // const filePath = result.filePaths[0];
const fileExtension = path.extname(filePath); // 获取文件后缀名 // const fileExtension = path.extname(filePath); // 获取文件后缀名
logWarn( // logWarn(
LogModule.APP, // LogModule.APP,
`Importing file ${filePath} with extension ${fileExtension}` // `Importing file ${filePath} with extension ${fileExtension}`
); // );
if (fileExtension === ".toml") { // if (fileExtension === ".toml") {
parseTomlConfig(filePath); // parseTomlConfig(filePath);
event.reply("Config.importConfig.hook", { // event.reply("Config.importConfig.hook", {
success: true // success: true
}); // });
} else { // } else {
logError( // logError(
LogModule.APP, // LogModule.APP,
`Import failed, unsupported file format: ${fileExtension}` // `Import failed, unsupported file format: ${fileExtension}`
); // );
event.reply("Config.importConfig.hook", { // event.reply("Config.importConfig.hook", {
success: false, // success: false,
data: `导入失败,暂不支持 ${fileExtension} 格式文件` // data: `导入失败,暂不支持 ${fileExtension} 格式文件`
}); // });
} // }
} // }
}); // });
//
ipcMain.on("config.clearAll", async (event, args) => { // ipcMain.on("config.clearAll", async (event, args) => {
logInfo(LogModule.APP, "Clearing all configurations."); // logInfo(LogModule.APP, "Clearing all configurations.");
stopFrpcProcess(() => { // stopFrpcProcess(() => {
clearConfig(); // clearConfig();
clearProxy(); // clearProxy();
clearVersion(); // clearVersion();
event.reply("Config.clearAll.hook", {}); // event.reply("Config.clearAll.hook", {});
logInfo(LogModule.APP, "All configurations cleared."); // logInfo(LogModule.APP, "All configurations cleared.");
}); // });
}); // });
//
ipcMain.on("config.openDataFolder", async (event, args) => { // ipcMain.on("config.openDataFolder", async (event, args) => {
const userDataPath = app.getPath("userData"); // const userDataPath = app.getPath("userData");
logInfo(LogModule.APP, "Attempting to open data folder."); // logInfo(LogModule.APP, "Attempting to open data folder.");
shell.openPath(userDataPath).then(errorMessage => { // shell.openPath(userDataPath).then(errorMessage => {
if (errorMessage) { // if (errorMessage) {
logError(LogModule.APP, `Failed to open data folder: ${errorMessage}`); // logError(LogModule.APP, `Failed to open data folder: ${errorMessage}`);
event.reply("Config.openDataFolder.hook", false); // event.reply("Config.openDataFolder.hook", false);
} else { // } else {
logInfo(LogModule.APP, "Data folder opened successfully."); // logInfo(LogModule.APP, "Data folder opened successfully.");
event.reply("Config.openDataFolder.hook", true); // event.reply("Config.openDataFolder.hook", true);
} // }
}); // });
}); // });
}; // };

View File

@ -1,21 +1,21 @@
import {dialog, ipcMain} from "electron"; // import {dialog, ipcMain} from "electron";
import { logInfo, logError, LogModule } from "../utils/log"; // import { logInfo, logError, LogModule } from "../utils/log";
//
export const initFileApi = () => { // export const initFileApi = () => {
ipcMain.handle("file.selectFile", async (event, args) => { // ipcMain.handle("file.selectFile", async (event, args) => {
logInfo(LogModule.APP, `Attempting to open file dialog with filters: ${JSON.stringify(args)}`); // logInfo(LogModule.APP, `Attempting to open file dialog with filters: ${JSON.stringify(args)}`);
try { // try {
const result = dialog.showOpenDialogSync({ // const result = dialog.showOpenDialogSync({
properties: ['openFile'], // properties: ['openFile'],
filters: [ // filters: [
{ name: 'Text Files', extensions: args }, // { name: 'Text Files', extensions: args },
] // ]
}); // });
logInfo(LogModule.APP, `File dialog result: ${JSON.stringify(result)}`); // logInfo(LogModule.APP, `File dialog result: ${JSON.stringify(result)}`);
return result; // return result;
} catch (error) { // } catch (error) {
logError(LogModule.APP, `Error opening file dialog: ${error.message}`); // logError(LogModule.APP, `Error opening file dialog: ${error.message}`);
return null; // return null;
} // }
}); // });
} // }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,81 +1,81 @@
import {app, dialog, autoUpdater, BrowserWindow} from "electron"; // import {app, dialog, autoUpdater, BrowserWindow} from "electron";
//
const log = require('electron-log'); // const log = require('electron-log');
//
//
export const initUpdaterApi = (win: BrowserWindow) => { // export const initUpdaterApi = (win: BrowserWindow) => {
//更新测试打开 // //更新测试打开
Object.defineProperty(app, 'isPackaged', { // Object.defineProperty(app, 'isPackaged', {
get() { // get() {
return true; // return true;
} // }
}); // });
const server = 'https://hazel-git-master-uiluck.vercel.app' // const server = 'https://hazel-git-master-uiluck.vercel.app'
let packageName = null // let packageName = null
const platform = process.platform; // const platform = process.platform;
const arch = process.arch; // const arch = process.arch;
switch (platform) { // switch (platform) {
case "darwin": // case "darwin":
if (arch == "arm64") { // if (arch == "arm64") {
packageName = "darwin_arm64"; // packageName = "darwin_arm64";
} else { // } else {
packageName = "darwin"; // packageName = "darwin";
} // }
break; // break;
case "win32": // case "win32":
packageName = "exe"; // packageName = "exe";
break; // break;
case "linux": // case "linux":
packageName = "AppImage"; // packageName = "AppImage";
if (arch == "arm64") { // if (arch == "arm64") {
packageName = "AppImage_arm64"; // packageName = "AppImage_arm64";
} else { // } else {
packageName = "AppImage"; // packageName = "AppImage";
} // }
break; // break;
} // }
const url = `${server}/update/${packageName}/${app.getVersion()}` // const url = `${server}/update/${packageName}/${app.getVersion()}`
log.info(`开启自动更新 ${url}`); // log.info(`开启自动更新 ${url}`);
autoUpdater.setFeedURL({url: url}) // autoUpdater.setFeedURL({url: url})
//
autoUpdater.on('checking-for-update', () => { // autoUpdater.on('checking-for-update', () => {
log.info("正在检查更新") // log.info("正在检查更新")
}) // })
//
autoUpdater.on('update-available', (event, info) => { // autoUpdater.on('update-available', (event, info) => {
log.info(`发现新版本`) // log.info(`发现新版本`)
}) // })
//
autoUpdater.on('update-not-available', () => { // autoUpdater.on('update-not-available', () => {
log.info('没有可用的更新') // log.info('没有可用的更新')
//
}) // })
//
autoUpdater.on('error', (err) => { // autoUpdater.on('error', (err) => {
log.error(`更新错误:${err.message}`) // log.error(`更新错误:${err.message}`)
//
}) // })
//
autoUpdater.on('update-downloaded', () => { // autoUpdater.on('update-downloaded', () => {
dialog.showMessageBox({ // dialog.showMessageBox({
type: 'info', // type: 'info',
title: '应用更新', // title: '应用更新',
message: '发现新版本,是否更新?', // message: '发现新版本,是否更新?',
buttons: ['是', '否'] // buttons: ['是', '否']
}).then((buttonIndex) => { // }).then((buttonIndex) => {
if (buttonIndex.response == 0) { //选择是,则退出程序,安装新版本 // if (buttonIndex.response == 0) { //选择是,则退出程序,安装新版本
autoUpdater.quitAndInstall() // autoUpdater.quitAndInstall()
app.quit() // app.quit()
} // }
}) // })
}) // })
//
// setInterval(() => { // // setInterval(() => {
// log.initialize("定时检查更新") // // log.initialize("定时检查更新")
// // autoUpdater.checkForUpdates(); // // // autoUpdater.checkForUpdates();
// }, 60000) // // }, 60000)
autoUpdater.checkForUpdates(); // autoUpdater.checkForUpdates();
log.info("手动检查更新一次") // log.info("手动检查更新一次")
//
//
} // }

View File

@ -5,6 +5,7 @@ import PathUtils from "../utils/PathUtils";
import fs from "fs"; import fs from "fs";
import FrpcProcessService from "../service/FrpcProcessService"; import FrpcProcessService from "../service/FrpcProcessService";
import SystemService from "../service/SystemService"; import SystemService from "../service/SystemService";
import moment from "moment";
class ConfigController extends BaseController { class ConfigController extends BaseController {
private readonly _serverService: ServerService; private readonly _serverService: ServerService;
@ -70,8 +71,11 @@ class ConfigController extends BaseController {
exportConfig(req: ControllerParam) { exportConfig(req: ControllerParam) {
this._systemService.openDirectory().then(folder => { this._systemService.openDirectory().then(folder => {
this._serverService.genTomlConfig(folder.filePaths[0]).then(() => { const path = `${folder.filePaths[0]}/frpc-${moment(new Date()).format(
req.event.reply(req.channel, success()); "YYYYMMDDhhmmss"
)}.toml`;
this._serverService.genTomlConfig(path).then(() => {
req.event.reply(req.channel, success(path));
}); });
}); });
} }

View File

@ -1,13 +1,13 @@
import BaseController from "./BaseController"; import BaseController from "./BaseController";
import ProxyService from "../service/ProxyService"; import ProxyService from "../service/ProxyService";
import { success } from "../utils/response"; import { success } from "../utils/response";
import ProxyDao from "../dao/ProxyDao"; import ProxyRepository from "../repository/ProxyRepository";
class ProxyController extends BaseController { class ProxyController extends BaseController {
private readonly _proxyService: ProxyService; private readonly _proxyService: ProxyService;
private readonly _proxyDao: ProxyDao; private readonly _proxyDao: ProxyRepository;
constructor(proxyService: ProxyService, proxyDao: ProxyDao) { constructor(proxyService: ProxyService, proxyDao: ProxyRepository) {
super(); super();
this._proxyService = proxyService; this._proxyService = proxyService;
this._proxyDao = proxyDao; this._proxyDao = proxyDao;

View File

@ -1,13 +1,13 @@
import BaseController from "./BaseController"; import BaseController from "./BaseController";
import VersionService from "../service/VersionService"; import VersionService from "../service/VersionService";
import { fail, success } from "../utils/response"; import { fail, success } from "../utils/response";
import VersionDao from "../dao/VersionDao"; import VersionRepository from "../repository/VersionRepository";
class VersionController extends BaseController { class VersionController extends BaseController {
private readonly _versionService: VersionService; private readonly _versionService: VersionService;
private readonly _versionDao: VersionDao; private readonly _versionDao: VersionRepository;
constructor(versionService: VersionService, versionDao: VersionDao) { constructor(versionService: VersionService, versionDao: VersionRepository) {
super(); super();
this._versionService = versionService; this._versionService = versionService;
this._versionDao = versionDao; this._versionDao = versionDao;

View File

@ -1,13 +1,23 @@
/** import Logger from "./Logger";
* todo DI
*/
class BeanFactory { class BeanFactory {
private static _beans: Map<string, any> = new Map<string, any>(); private static _beans: Map<string, any> = new Map<string, any>();
private static registerBean(name: string, instance: any): void { static registerBean(clazz: Function, beanName?: string): void {
if (!this._beans.has(name)) { if (!beanName) {
this._beans.set(name, instance); beanName = this.getBeanName(clazz.name);
} }
if (this.hasBean(beanName)) {
return;
}
const instance = new (clazz as any)();
this._beans.set(beanName, instance);
}
public static setBean<T>(name: string, bean: T): void {
this._beans.set(name, bean);
Logger.info(`register bean ${name} ${bean}`);
} }
public static getBean<T>(name: string): T { public static getBean<T>(name: string): T {
@ -21,6 +31,14 @@ class BeanFactory {
public static clear(): void { public static clear(): void {
this._beans.clear(); this._beans.clear();
} }
public static getBeanName(className: string) {
return className.charAt(0).toLowerCase() + className.slice(1);
}
} }
export default BeanFactory; export default BeanFactory;

View File

@ -1,6 +1,6 @@
import { app } from "electron";
class GlobalConstant { class GlobalConstant {
public static APP_NAME = "Frpc Desktop";
public static ZIP_EXT = ".zip"; public static ZIP_EXT = ".zip";
public static GZ_EXT = ".gz"; public static GZ_EXT = ".gz";
public static TAR_GZ_EXT = ".tar.gz"; public static TAR_GZ_EXT = ".tar.gz";

View File

@ -1,20 +1,3 @@
import ConfigController from "../controller/ConfigController";
import ServerDao from "../dao/ServerDao";
import ServerService from "../service/ServerService";
import LogService from "../service/LogService";
import VersionService from "../service/VersionService";
import { BrowserWindow, ipcMain } from "electron";
import LogController from "../controller/LogController";
import VersionController from "../controller/VersionController";
import VersionDao from "../dao/VersionDao";
import GitHubService from "../service/GitHubService";
import FrpcProcessService from "../service/FrpcProcessService";
import LaunchController from "../controller/LaunchController";
import ProxyDao from "../dao/ProxyDao";
import ProxyService from "../service/ProxyService";
import ProxyController from "../controller/ProxyController";
import SystemService from "../service/SystemService";
import SystemController from "../controller/SystemController";
export const ipcRouters: IpcRouters = { export const ipcRouters: IpcRouters = {
SERVER: { SERVER: {
@ -133,119 +116,3 @@ export const listeners: Listeners = {
channel: "frpcProcess:watchFrpcLog" channel: "frpcProcess:watchFrpcLog"
} }
}; };
class IpcRouterConfigurate {
ipcRouters: Array<IpcRouter>;
private readonly _beans: Map<string, any> = new Map<string, any>();
private readonly _win: BrowserWindow;
/**
* initBeans
* @private
*/
private initializeBeans() {
const serverDao = new ServerDao();
const versionDao = new VersionDao();
const proxyDao = new ProxyDao();
const systemService = new SystemService();
const serverService = new ServerService(serverDao, proxyDao);
const gitHubService = new GitHubService();
const versionService = new VersionService(
versionDao,
systemService,
gitHubService
);
const logService = new LogService(systemService);
const frpcProcessService = new FrpcProcessService(
serverService,
versionDao
);
const proxyService = new ProxyService(proxyDao);
const configController = new ConfigController(
serverService,
systemService,
frpcProcessService
);
const versionController = new VersionController(versionService, versionDao);
const logController = new LogController(logService);
const launchController = new LaunchController(frpcProcessService);
const proxyController = new ProxyController(proxyService, proxyDao);
const systemController = new SystemController(systemService);
this._beans.set("serverDao", serverDao);
this._beans.set("versionDao", versionDao);
this._beans.set("proxyDao", proxyDao);
this._beans.set("systemService", systemService);
this._beans.set("serverService", serverService);
this._beans.set("versionService", versionService);
this._beans.set("logService", logService);
this._beans.set("proxyService", proxyService);
this._beans.set("frpcProcessService", frpcProcessService);
this._beans.set("configController", configController);
this._beans.set("versionController", versionController);
this._beans.set("logController", logController);
this._beans.set("launchController", launchController);
this._beans.set("proxyController", proxyController);
this._beans.set("systemController", systemController);
}
/**
* initJob
* @private
*/
private initializeListeners() {
Object.keys(listeners).forEach(listenerKey => {
console.log(listenerKey, "listenerKey", listeners[listenerKey]);
const { listenerMethod, channel } = listeners[listenerKey];
const [beanName, method] = listenerMethod.split(".");
const bean = this._beans.get(beanName);
const listenerParam: ListenerParam = {
win: this._win,
channel: channel,
args: []
};
bean[method].call(bean, listenerParam);
});
console.log("initialize listeners success");
// this._beans.get("logService").watchFrpcLog(this._win);
}
/**
* initRouters
* @private
*/
private initializeRouters() {
Object.keys(ipcRouters).forEach(routerKey => {
const routerGroup = ipcRouters[routerKey];
Object.keys(routerGroup).forEach(method => {
const router = routerGroup[method];
ipcMain.on(router.path, (event, args) => {
const req: ControllerParam = {
win: this._win,
channel: `${router.path}:hook`,
event: event,
args: args
};
const [beanName, method] = router.controller.split(".");
const bean = this._beans.get(beanName);
bean[method].call(bean, req);
// bean[method].call(bean, req);
});
});
});
}
/**
* constructor
* @param win mainWindows
*/
constructor(win: BrowserWindow) {
this._win = win;
this.initializeBeans();
this.initializeListeners();
this.initializeRouters();
}
}
export default IpcRouterConfigurate;

18
electron/core/Logger.ts Normal file
View File

@ -0,0 +1,18 @@
import log from "electron-log";
class Logger {
static {
log.transports.file.level = "debug";
log.transports.console.level = "debug";
}
static info(msg: string) {}
static debug(msg: string) {}
static warn(msg: string) {}
static error(msg: string) {}
}
export default Logger;

View File

@ -0,0 +1,25 @@
// // Class decorator
// import "reflect-metadata";
// import BeanFactory from "../BeanFactory";
//
// const Component =
// (): ClassDecorator =>
// target => {
// // BeanFactory.registerBean(
// // beanName || BeanFactory.getBeanName(target.name),
// // target
// // );
// };
//
//
//
//
// export default Component;
//
//
// // export default function Component(beanName?: string): ClassDecorator {
// // return function (target) {
// // const paramtypes = Reflect.getMetadata('design:paramtypes', target);
// // console.log(paramtypes);
// // };
// // }

View File

@ -0,0 +1,5 @@
// export default function Resource(beanName?: string): PropertyDecorator {
// return function (target: Object, propertyKey: string | symbol) {
// console.log(target, propertyKey);
// };
// }

View File

@ -10,14 +10,25 @@ import {
} from "electron"; } from "electron";
import { release } from "node:os"; import { release } from "node:os";
import node_path, { join } from "node:path"; import node_path, { join } from "node:path";
import { initGitHubApi } from "../api/github"; import BeanFactory from "../core/BeanFactory";
import { initConfigApi } from "../api/config"; import ServerRepository from "../repository/ServerRepository";
import { startFrpWorkerProcess, stopFrpcProcess } from "../api/frpc"; import VersionRepository from "../repository/VersionRepository";
import { initFileApi } from "../api/file"; import ProxyRepository from "../repository/ProxyRepository";
import { getConfig } from "../storage/config"; import SystemService from "../service/SystemService";
import { initLog, logError, logInfo, LogModule } from "../utils/log"; import ServerService from "../service/ServerService";
import { maskSensitiveData } from "../utils/desensitize"; import GitHubService from "../service/GitHubService";
import IpcRouterConfigurate from "../core/IpcRouter"; import VersionService from "../service/VersionService";
import LogService from "../service/LogService";
import FrpcProcessService from "../service/FrpcProcessService";
import ProxyService from "../service/ProxyService";
import ConfigController from "../controller/ConfigController";
import VersionController from "../controller/VersionController";
import LogController from "../controller/LogController";
import LaunchController from "../controller/LaunchController";
import ProxyController from "../controller/ProxyController";
import SystemController from "../controller/SystemController";
import { ipcRouters, listeners } from "../core/IpcRouter";
import Logger from "../core/Logger";
process.env.DIST_ELECTRON = join(__dirname, ".."); process.env.DIST_ELECTRON = join(__dirname, "..");
process.env.DIST = join(process.env.DIST_ELECTRON, "../dist"); process.env.DIST = join(process.env.DIST_ELECTRON, "../dist");
@ -25,260 +36,351 @@ process.env.VITE_PUBLIC = process.env.VITE_DEV_SERVER_URL
? join(process.env.DIST_ELECTRON, "../public") ? join(process.env.DIST_ELECTRON, "../public")
: process.env.DIST; : process.env.DIST;
let win: BrowserWindow | null = null;
let tray = null;
const preload = join(__dirname, "../preload/index.js"); const preload = join(__dirname, "../preload/index.js");
const url = process.env.VITE_DEV_SERVER_URL; const url = process.env.VITE_DEV_SERVER_URL;
const indexHtml = join(process.env.DIST, "index.html"); const indexHtml = join(process.env.DIST, "index.html");
let isQuiting;
// Disable GPU Acceleration for Windows 7
if (release().startsWith("6.1")) app.disableHardwareAcceleration();
// Set application name for Windows 10+ notifications class FrpcDesktopApp {
if (process.platform === "win32") app.setAppUserModelId(app.getName()); private _win: BrowserWindow | null = null;
private readonly _silentStart = false;
private _quitting = false;
if (!app.requestSingleInstanceLock()) { constructor() {
app.quit(); this.initializeBeans();
process.exit(0); this.initializeListeners();
} this.initializeRouters();
this.initializeElectronApp();
async function createWindow(config: FrpConfig) {
let show = true;
if (config) {
show = !config.systemSilentStartup;
}
win = new BrowserWindow({
title: "Frpc Desktop",
icon: join(process.env.VITE_PUBLIC, "logo/only/16x16.png"),
width: 800,
height: 600,
minWidth: 800,
minHeight: 600,
maxWidth: 1280,
maxHeight: 960,
webPreferences: {
preload,
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
// Consider using contextBridge.exposeInMainWorld
// Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
nodeIntegration: true,
contextIsolation: false
},
show: show
});
if (process.env.VITE_DEV_SERVER_URL) {
// electron-vite-vue#298
win.loadURL(url);
// Open devTool if the app is not packaged
win.webContents.openDevTools();
} else {
win.loadFile(indexHtml);
} }
// Test actively push message to the Electron-Renderer initializeWindow() {
win.webContents.on("did-finish-load", () => { if (this._win) {
win?.webContents.send("main-process-message", new Date().toLocaleString());
});
// Make all links open with the browser, not with the application
win.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith("https:")) shell.openExternal(url);
return { action: "deny" };
});
// 隐藏菜单栏
const { Menu } = require("electron");
Menu.setApplicationMenu(null);
// hide menu for Mac
// if (process.platform !== "darwin") {
// app.dock.hide();
// }
win.on("minimize", function (event) {
event.preventDefault();
win.hide();
});
win.on("close", function (event) {
if (!isQuiting) {
event.preventDefault();
win.hide();
if (process.platform === "darwin") {
app.dock.hide();
}
}
return false;
});
}
export const createTray = (config: FrpConfig) => {
let menu: Array<MenuItemConstructorOptions | MenuItem> = [
{
label: "显示主窗口",
click: function () {
win.show();
if (process.platform === "darwin") {
app.dock.show();
}
}
},
{
label: "退出",
click: () => {
isQuiting = true;
stopFrpcProcess(() => {
app.quit();
});
}
}
];
tray = new Tray(
node_path.join(process.env.VITE_PUBLIC, "logo/only/16x16.png")
);
tray.setToolTip("Frpc Desktop");
const contextMenu = Menu.buildFromTemplate(menu);
tray.setContextMenu(contextMenu);
// 托盘双击打开
tray.on("double-click", () => {
win.show();
});
logInfo(LogModule.APP, `Tray created successfully.`);
};
app.whenReady().then(() => {
initLog();
logInfo(
LogModule.APP,
`Application started. Current system architecture: ${
process.arch
}, platform: ${process.platform}, version: ${app.getVersion()}.`
);
getConfig((err, config) => {
if (err) {
logError(LogModule.APP, `Failed to get config: ${err.message}`);
return; return;
} }
this._win = new BrowserWindow({
title: app.getName(),
icon: join(process.env.VITE_PUBLIC, "logo/only/16x16.png"),
width: 800,
height: 600,
minWidth: 800,
minHeight: 600,
maxWidth: 1280,
maxHeight: 960,
webPreferences: {
preload,
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
// Consider using contextBridge.exposeInMainWorld
// Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
nodeIntegration: true,
contextIsolation: false
},
show: true
});
BeanFactory.setBean("win", this._win);
if (process.env.VITE_DEV_SERVER_URL) {
// electron-vite-vue#298
this._win.loadURL(url).then(() => {});
// Open devTool if the app is not packaged
this._win.webContents.openDevTools();
} else {
this._win.loadFile(indexHtml).then(() => {});
}
createWindow(config) this._win.webContents.on("did-finish-load", () => {
.then(r => { this._win?.webContents.send(
logInfo(LogModule.APP, `Window created successfully.`); "main-process-message",
createTray(config); new Date().toLocaleString()
);
});
this._win.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith("https:")) shell.openExternal(url);
return { action: "deny" };
});
const { Menu } = require("electron");
Menu.setApplicationMenu(null);
if (config) { const that = this;
logInfo( this._win.on("minimize", function (event) {
LogModule.APP, event.preventDefault();
`Config retrieved: ${JSON.stringify( that._win.hide();
maskSensitiveData(config, [ });
"serverAddr",
"serverPort",
"authToken",
"user",
"metaToken"
])
)}`
);
if (config.systemStartupConnect) { this._win.on("close", function (event) {
startFrpWorkerProcess(config); if (!that._quitting) {
event.preventDefault();
that._win.hide();
if (process.platform === "darwin") {
app.dock.hide();
}
}
return false;
});
}
initializeTray() {
const that = this;
let menu: Array<MenuItemConstructorOptions | MenuItem> = [
{
label: "显示主窗口",
click: function () {
that._win.show();
if (process.platform === "darwin") {
app.dock.show().then(() => {});
} }
} }
const ipcRouterConfig = new IpcRouterConfigurate(win); },
// Initialize APIs {
try { label: "退出",
initGitHubApi(win); click: () => {
logInfo(LogModule.APP, `GitHub API initialized.`); that._quitting = true;
// todo stop frpc process
initConfigApi(win); app.quit();
logInfo(LogModule.APP, `Config API initialized.`);
initFileApi();
logInfo(LogModule.APP, `File API initialized.`);
// initUpdaterApi(win);
logInfo(LogModule.APP, `Updater API initialization skipped.`);
} catch (error) {
logError(
LogModule.APP,
`Error during API initialization: ${error.message}`
);
} }
})
.catch(error => {
logError(LogModule.APP, `Error creating window: ${error.message}`);
});
});
});
app.on("window-all-closed", () => {
logInfo(LogModule.APP, `All windows closed.`);
win = null;
if (process.platform !== "darwin") {
stopFrpcProcess(() => {
logInfo(LogModule.APP, `FRPC process stopped. Quitting application.`);
app.quit();
});
}
});
app.on("second-instance", () => {
logInfo(LogModule.APP, `Second instance detected.`);
if (win) {
// Focus on the main window if the user tried to open another
if (win.isMinimized()) win.restore();
win.focus();
}
});
app.on("activate", () => {
logInfo(LogModule.APP, `Application activated.`);
const allWindows = BrowserWindow.getAllWindows();
if (allWindows.length) {
allWindows[0].focus();
} else {
getConfig((err, config) => {
if (err) {
logError(
LogModule.APP,
`Failed to get config on activate: ${err.message}`
);
return;
} }
createWindow(config).then(r => { ];
logInfo(LogModule.APP, `Window created on activate.`); const tray = new Tray(
}); node_path.join(process.env.VITE_PUBLIC, "logo/only/16x16.png")
);
tray.setToolTip(app.getName());
const contextMenu = Menu.buildFromTemplate(menu);
tray.setContextMenu(contextMenu);
// 托盘双击打开
tray.on("double-click", () => {
this._win.show();
}); });
} }
});
app.on("before-quit", () => { initializeElectronApp() {
logInfo(LogModule.APP, `Application is about to quit.`); // Disable GPU Acceleration for Windows 7
stopFrpcProcess(() => { if (release().startsWith("6.1")) app.disableHardwareAcceleration();
isQuiting = true;
});
});
ipcMain.handle("open-win", (_, arg) => { // Set application name for Windows 10+ notifications
logInfo(LogModule.APP, `Opening new window with argument: ${arg}`); if (process.platform === "win32") app.setAppUserModelId(app.getName());
const childWindow = new BrowserWindow({
webPreferences: { if (!app.requestSingleInstanceLock()) {
preload, app.quit();
nodeIntegration: true, process.exit(0);
contextIsolation: false
} }
}); app.whenReady().then(() => {
this.initializeWindow();
this.initializeTray();
// initLog();
// logInfo(
// LogModule.APP,
// `Application started. Current system architecture: ${
// process.arch
// }, platform: ${process.platform}, version: ${app.getVersion()}.`
// );
if (process.env.VITE_DEV_SERVER_URL) { // getConfig((err, config) => {
childWindow.loadURL(`${url}#${arg}`); // if (err) {
logInfo(LogModule.APP, `Child window loaded URL: ${url}#${arg}`); // logError(LogModule.APP, `Failed to get config: ${err.message}`);
} else { // return;
childWindow.loadFile(indexHtml, { hash: arg }); // }
logInfo(
LogModule.APP, // createWindow(config)
`Child window loaded file: ${indexHtml} with hash: ${arg}` // .then(r => {
// logInfo(LogModule.APP, `Window created successfully.`);
// createTray(config);
//
// // if (config) {
// // logInfo(
// // LogModule.APP,
// // `Config retrieved: ${JSON.stringify(
// // maskSensitiveData(config, [
// // "serverAddr",
// // "serverPort",
// // "authToken",
// // "user",
// // "metaToken"
// // ])
// // )}`
// // );
// //
// // if (config.systemStartupConnect) {
// // startFrpWorkerProcess(config);
// // }
// // }
// // const ipcRouterConfig = new IpcRouterConfigurate(win);
// // Initialize APIs
// // try {
// // initGitHubApi(win);
// // logInfo(LogModule.APP, `GitHub API initialized.`);
// //
// // initConfigApi(win);
// // logInfo(LogModule.APP, `Config API initialized.`);
// //
// // initFileApi();
// // logInfo(LogModule.APP, `File API initialized.`);
// //
// // // initUpdaterApi(win);
// // logInfo(LogModule.APP, `Updater API initialization skipped.`);
// // } catch (error) {
// // logError(
// // LogModule.APP,
// // `Error during API initialization: ${error.message}`
// // );
// // }
// })
// .catch(error => {
// logError(LogModule.APP, `Error creating window: ${error.message}`);
// });
// });
});
app.on("window-all-closed", () => {
// logInfo(LogModule.APP, `All windows closed.`);
this._win = null;
if (process.platform !== "darwin") {
// todo stop frpc process
// stopFrpcProcess(() => {
// logInfo(LogModule.APP, `FRPC process stopped. Quitting application.`);
app.quit();
// });
}
});
app.on("second-instance", () => {
if (this._win) {
if (this._win.isMinimized()) this._win.restore();
this._win.focus();
}
});
app.on("activate", () => {
// logInfo(LogModule.APP, `Application activated.`);
const allWindows = BrowserWindow.getAllWindows();
if (allWindows.length) {
allWindows[0].focus();
} else {
this.initializeWindow();
}
});
app.on("before-quit", () => {
// todo stop frpc process
this._quitting = true;
});
}
initializeBeans() {
BeanFactory.setBean("serverRepository", new ServerRepository());
BeanFactory.setBean("versionRepository", new VersionRepository());
BeanFactory.setBean("proxyRepository", new ProxyRepository());
BeanFactory.setBean("systemService", new SystemService());
BeanFactory.setBean(
"serverService",
new ServerService(
BeanFactory.getBean("serverRepository"),
BeanFactory.getBean("proxyRepository")
)
);
BeanFactory.setBean("gitHubService", new GitHubService());
BeanFactory.setBean(
"versionService",
new VersionService(
BeanFactory.getBean("versionRepository"),
BeanFactory.getBean("systemService"),
BeanFactory.getBean("gitHubService")
)
);
BeanFactory.setBean(
"logService",
new LogService(BeanFactory.getBean("systemService"))
);
BeanFactory.setBean(
"frpcProcessService",
new FrpcProcessService(
BeanFactory.getBean("serverService"),
BeanFactory.getBean("versionRepository")
)
);
BeanFactory.setBean(
"proxyService",
new ProxyService(BeanFactory.getBean("proxyRepository"))
);
BeanFactory.setBean(
"configController",
new ConfigController(
BeanFactory.getBean("serverService"),
BeanFactory.getBean("systemService"),
BeanFactory.getBean("frpcProcessService")
)
);
BeanFactory.setBean(
"versionController",
new VersionController(
BeanFactory.getBean("versionService"),
BeanFactory.getBean("versionRepository")
)
);
BeanFactory.setBean(
"logController",
new LogController(BeanFactory.getBean("logService"))
);
BeanFactory.setBean(
"launchController",
new LaunchController(BeanFactory.getBean("frpcProcessService"))
);
BeanFactory.setBean(
"proxyController",
new ProxyController(
BeanFactory.getBean("proxyService"),
BeanFactory.getBean("proxyRepository")
)
);
BeanFactory.setBean(
"systemController",
new SystemController(BeanFactory.getBean("systemService"))
); );
} }
});
/**
* initJob
* @private
*/
private initializeListeners() {
Object.keys(listeners).forEach(listenerKey => {
console.log(listenerKey, "listenerKey", listeners[listenerKey]);
const { listenerMethod, channel } = listeners[listenerKey];
const [beanName, method] = listenerMethod.split(".");
const bean = BeanFactory.getBean(beanName);
const listenerParam: ListenerParam = {
// win: BeanFactory.getBean("win"),
channel: channel,
args: []
};
bean[method].call(bean, listenerParam);
});
Logger.info("initialize listeners success");
// this._beans.get("logService").watchFrpcLog(this._win);
}
/**
* initRouters
* @private
*/
private initializeRouters() {
Object.keys(ipcRouters).forEach(routerKey => {
const routerGroup = ipcRouters[routerKey];
Object.keys(routerGroup).forEach(method => {
const router = routerGroup[method];
ipcMain.on(router.path, (event, args) => {
const req: ControllerParam = {
// win: BeanFactory.getBean("win"),
channel: `${router.path}:hook`,
event: event,
args: args
};
const [beanName, method] = router.controller.split(".");
const bean = BeanFactory.getBean(beanName);
bean[method].call(bean, req);
});
});
});
}
}
new FrpcDesktopApp();

View File

@ -20,7 +20,7 @@ import IdUtils from "../utils/IdUtils";
// } // }
class BaseDao<T> { class BaseRepository<T> {
protected readonly db: Datastore; protected readonly db: Datastore;
constructor(dbName: string) { constructor(dbName: string) {
@ -135,4 +135,4 @@ class BaseDao<T> {
} }
} }
export default BaseDao; export default BaseRepository;

View File

@ -1,6 +1,8 @@
import BaseDao from "./BaseDao"; import BaseRepository from "./BaseRepository";
import Component from "../core/annotation/Component";
class ProxyDao extends BaseDao<FrpcProxy> { // @Component()
class ProxyRepository extends BaseRepository<FrpcProxy> {
constructor() { constructor() {
super("proxy"); super("proxy");
} }
@ -23,4 +25,4 @@ class ProxyDao extends BaseDao<FrpcProxy> {
} }
} }
export default ProxyDao; export default ProxyRepository;

View File

@ -1,6 +1,8 @@
import BaseDao from "./BaseDao"; import BaseRepository from "./BaseRepository";
import Component from "../core/annotation/Component";
class ServerDao extends BaseDao<OpenSourceFrpcDesktopServer> { // @Component()
class ServerRepository extends BaseRepository<OpenSourceFrpcDesktopServer> {
constructor() { constructor() {
super("server"); super("server");
} }
@ -18,4 +20,4 @@ class ServerDao extends BaseDao<OpenSourceFrpcDesktopServer> {
} }
} }
export default ServerDao export default ServerRepository

View File

@ -1,6 +1,8 @@
import BaseDao from "./BaseDao"; import BaseRepository from "./BaseRepository";
import Component from "../core/annotation/Component";
class VersionDao extends BaseDao<FrpcVersion> { // @Component()
class VersionRepository extends BaseRepository<FrpcVersion> {
constructor() { constructor() {
super("version"); super("version");
} }
@ -18,7 +20,7 @@ class VersionDao extends BaseDao<FrpcVersion> {
} }
exists(githubReleaseId: number): Promise<boolean> { exists(githubReleaseId: number): Promise<boolean> {
return new Promise(( resolve, reject) => { return new Promise((resolve, reject) => {
this.db.count({ githubReleaseId: githubReleaseId }, (err, count) => { this.db.count({ githubReleaseId: githubReleaseId }, (err, count) => {
if (err) { if (err) {
reject(err); reject(err);
@ -30,4 +32,4 @@ class VersionDao extends BaseDao<FrpcVersion> {
} }
} }
export default VersionDao; export default VersionRepository;

View File

@ -1,14 +1,14 @@
import BaseDao from "../dao/BaseDao"; import BaseRepository from "../repository/BaseRepository";
interface BaseServiceInterface<T> { interface BaseServiceInterface<T> {
// dao: BaseDao<T>; // dao: BaseRepository<T>;
} }
class BaseService<T> implements BaseServiceInterface<T> { class BaseService<T> implements BaseServiceInterface<T> {
// dao: BaseDao<T>; // dao: BaseRepository<T>;
// //
// constructor(dao: BaseDao<T>) { // constructor(dao: BaseRepository<T>) {
// this.dao = dao; // this.dao = dao;
// } // }
} }

View File

@ -1,18 +1,19 @@
import ServerService from "./ServerService"; import ServerService from "./ServerService";
import VersionDao from "../dao/VersionDao"; import VersionRepository from "../repository/VersionRepository";
import PathUtils from "../utils/PathUtils"; import PathUtils from "../utils/PathUtils";
import GlobalConstant from "../core/GlobalConstant"; import GlobalConstant from "../core/GlobalConstant";
import { Notification } from "electron"; import { app, BrowserWindow, Notification } from "electron";
import { success } from "../utils/response"; import { success } from "../utils/response";
import treeKill from "tree-kill"; import treeKill from "tree-kill";
import BeanFactory from "../core/BeanFactory";
class FrpcProcessService { class FrpcProcessService {
private readonly _serverService: ServerService; private readonly _serverService: ServerService;
private readonly _versionDao: VersionDao; private readonly _versionDao: VersionRepository;
private _frpcProcess: any; private _frpcProcess: any;
private _frpcProcessListener: any; private _frpcProcessListener: any;
constructor(serverService: ServerService, versionDao: VersionDao) { constructor(serverService: ServerService, versionDao: VersionRepository) {
this._serverService = serverService; this._serverService = serverService;
this._versionDao = versionDao; this._versionDao = versionDao;
} }
@ -81,7 +82,7 @@ class FrpcProcessService {
console.log("running", running); console.log("running", running);
if (!running) { if (!running) {
new Notification({ new Notification({
title: GlobalConstant.APP_NAME, title: app.getName(),
body: "Connection lost, please check the logs for details." body: "Connection lost, please check the logs for details."
}).show(); }).show();
// logError( // logError(
@ -90,7 +91,8 @@ class FrpcProcessService {
// ); // );
// clearInterval(this._frpcProcessListener); // clearInterval(this._frpcProcessListener);
} }
listenerParam.win.webContents.send( const win: BrowserWindow = BeanFactory.getBean("win");
win.webContents.send(
listenerParam.channel, listenerParam.channel,
success(running) success(running)
); );

View File

@ -2,6 +2,8 @@ import fs from "fs";
import { success } from "../utils/response"; import { success } from "../utils/response";
import PathUtils from "../utils/PathUtils"; import PathUtils from "../utils/PathUtils";
import SystemService from "./SystemService"; import SystemService from "./SystemService";
import BeanFactory from "../core/BeanFactory";
import { BrowserWindow } from "electron";
class LogService { class LogService {
private readonly _systemService: SystemService; private readonly _systemService: SystemService;
@ -11,8 +13,11 @@ class LogService {
this._systemService = systemService; this._systemService = systemService;
} }
getFrpLogContent(): Promise<string> { async getFrpLogContent() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!fs.existsSync(this._logPath)) {
resolve("");
}
fs.readFile(this._logPath, "utf-8", (error, data) => { fs.readFile(this._logPath, "utf-8", (error, data) => {
if (!error) { if (!error) {
resolve(data); resolve(data);
@ -28,11 +33,12 @@ class LogService {
setTimeout(() => this.watchFrpcLog(listenerParam), 1000); setTimeout(() => this.watchFrpcLog(listenerParam), 1000);
return; return;
} }
console.log('watchFrpcLog succcess'); console.log("watchFrpcLog succcess");
fs.watch(this._logPath, (eventType, filename) => { fs.watch(this._logPath, (eventType, filename) => {
if (eventType === "change") { if (eventType === "change") {
console.log("change", eventType, listenerParam.channel); console.log("change", eventType, listenerParam.channel);
listenerParam.win.webContents.send( const win: BrowserWindow = BeanFactory.getBean("win");
win.webContents.send(
listenerParam.channel, listenerParam.channel,
success(true) success(true)
); );

View File

@ -1,11 +1,13 @@
import ProxyDao from "../dao/ProxyDao"; import ProxyRepository from "../repository/ProxyRepository";
import Component from "../core/annotation/Component";
const { exec, spawn } = require("child_process"); const { exec, spawn } = require("child_process");
class ProxyService { class ProxyService {
private readonly _proxyDao: ProxyDao;
constructor(proxyDao: ProxyDao) { private readonly _proxyDao: ProxyRepository;
constructor(public proxyDao: ProxyRepository) {
this._proxyDao = proxyDao; this._proxyDao = proxyDao;
} }

View File

@ -1,16 +1,16 @@
import BaseService from "./BaseService"; import BaseService from "./BaseService";
import ServerDao from "../dao/ServerDao"; import ServerRepository from "../repository/ServerRepository";
import TOML from "smol-toml"; import TOML from "smol-toml";
import fs from "fs"; import fs from "fs";
import PathUtils from "../utils/PathUtils"; import PathUtils from "../utils/PathUtils";
import ProxyDao from "../dao/ProxyDao"; import ProxyRepository from "../repository/ProxyRepository";
class ServerService extends BaseService<OpenSourceFrpcDesktopServer> { class ServerService extends BaseService<OpenSourceFrpcDesktopServer> {
private readonly _serverDao: ServerDao; private readonly _serverDao: ServerRepository;
private readonly _proxyDao: ProxyDao; private readonly _proxyDao: ProxyRepository;
private readonly _serverId: string = "1"; private readonly _serverId: string = "1";
constructor(serverDao: ServerDao, proxyDao: ProxyDao) { constructor(serverDao: ServerRepository, proxyDao: ProxyRepository) {
super(); super();
this._serverDao = serverDao; this._serverDao = serverDao;
this._proxyDao = proxyDao; this._proxyDao = proxyDao;

View File

@ -4,6 +4,7 @@ import path from "path";
import fs from "fs"; import fs from "fs";
import zlib from "zlib"; import zlib from "zlib";
import admZip from "adm-zip"; import admZip from "adm-zip";
import Component from "../core/annotation/Component";
const tar = require("tar"); const tar = require("tar");

View File

@ -1,4 +1,4 @@
import VersionDao from "../dao/VersionDao"; import VersionRepository from "../repository/VersionRepository";
import BaseService from "./BaseService"; import BaseService from "./BaseService";
import GitHubService from "./GitHubService"; import GitHubService from "./GitHubService";
import frpReleasesJson from "../json/frp-releases.json"; import frpReleasesJson from "../json/frp-releases.json";
@ -14,14 +14,14 @@ import frpChecksums from "../json/frp_all_sha256_checksums.json";
import SystemService from "./SystemService"; import SystemService from "./SystemService";
class VersionService extends BaseService<FrpcVersion> { class VersionService extends BaseService<FrpcVersion> {
private readonly _versionDao: VersionDao; private readonly _versionDao: VersionRepository;
private readonly _systemService: SystemService; private readonly _systemService: SystemService;
private readonly _gitHubService: GitHubService; private readonly _gitHubService: GitHubService;
private readonly _currFrpArch: Array<string>; private readonly _currFrpArch: Array<string>;
private _versions: Array<FrpcVersion> = []; private _versions: Array<FrpcVersion> = [];
constructor( constructor(
versionDao: VersionDao, versionDao: VersionRepository,
systemService: SystemService, systemService: SystemService,
gitHubService: GitHubService gitHubService: GitHubService
) { ) {

View File

@ -64,9 +64,9 @@
"sass": "^1.66.1", "sass": "^1.66.1",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"tree-kill": "^1.2.2", "tree-kill": "^1.2.2",
"typescript": "^5.1.6", "typescript": "5.7.3",
"vite": "^4.4.9", "vite": "^5.4.11",
"vite-plugin-electron": "^0.15.3", "vite-plugin-electron": "^0.28.6",
"vite-plugin-electron-renderer": "^0.14.5", "vite-plugin-electron-renderer": "^0.14.5",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-router": "^4.2.4", "vue-router": "^4.2.4",
@ -82,10 +82,10 @@
"intro.js": "^8.0.0-beta.1", "intro.js": "^8.0.0-beta.1",
"isbinaryfile": "4.0.10", "isbinaryfile": "4.0.10",
"js-base64": "^3.7.7", "js-base64": "^3.7.7",
"smol-toml": "^1.3.1",
"snowflakify": "^1.0.5", "snowflakify": "^1.0.5",
"tar": "^6.2.0", "tar": "^6.2.0",
"unused-filename": "^4.0.1", "unused-filename": "^4.0.1",
"uuid": "^10.0.0", "uuid": "^10.0.0"
"smol-toml": "^1.3.1"
} }
} }

View File

@ -362,21 +362,22 @@ onMounted(() => {
}); });
}); });
on(ipcRouters.SERVER.exportConfig, () => { on(ipcRouters.SERVER.exportConfig, (data) => {
// ElMessageBox.alert(`配置路径:${data}`, `🎉 导出成功`);
confetti({ // //
zIndex: 12002, // confetti({
particleCount: 200, // zIndex: 12002,
spread: 70, // particleCount: 200,
origin: { y: 0.6 } // spread: 70,
}); // origin: { y: 0.6 }
ElMessageBox.alert("🎉 恭喜你,导入成功 请重启软件", `提示`, { // });
closeOnClickModal: false, // ElMessageBox.alert("🎉 ", ``, {
showClose: false, // closeOnClickModal: false,
confirmButtonText: "立即重启" // showClose: false,
}).then(() => { // confirmButtonText: ""
send(ipcRouters.SYSTEM.relaunchApp); // }).then(() => {
}); // send(ipcRouters.SYSTEM.relaunchApp);
// });
}); });
// ElMessageBox.alert(data, ``); // ElMessageBox.alert(data, ``);
on(ipcRouters.SYSTEM.openAppData, () => { on(ipcRouters.SYSTEM.openAppData, () => {
@ -514,7 +515,6 @@ onUnmounted(() => {
removeRouterListeners(ipcRouters.SERVER.resetAllConfig); removeRouterListeners(ipcRouters.SERVER.resetAllConfig);
removeRouterListeners(ipcRouters.VERSION.getDownloadedVersions); removeRouterListeners(ipcRouters.VERSION.getDownloadedVersions);
removeRouterListeners(ipcRouters.SERVER.exportConfig); removeRouterListeners(ipcRouters.SERVER.exportConfig);
// ipcRenderer.removeAllListeners("Config.clearAll.hook");
removeRouterListeners(ipcRouters.SYSTEM.openAppData); removeRouterListeners(ipcRouters.SYSTEM.openAppData);
}); });
</script> </script>

View File

@ -22,7 +22,7 @@
] ]
}, },
"types": [ "types": [
// "node", // "node",
"vite/client", "vite/client",
"element-plus/global" "element-plus/global"
], ],

4
types/core.d.ts vendored
View File

@ -5,14 +5,14 @@ interface ApiResponse<T> {
} }
interface ControllerParam { interface ControllerParam {
win: BrowserWindow; // win: BrowserWindow;
channel: string; channel: string;
event: Electron.IpcMainEvent; event: Electron.IpcMainEvent;
args: any; args: any;
} }
interface ListenerParam { interface ListenerParam {
win: BrowserWindow; // win: BrowserWindow;
channel: string; channel: string;
args: any[]; args: any[];
} }