From 9946b50d5d315010856d68a83eb1ac6fc2d0f40a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=98=89=E4=BC=9F?= <8473136@qq.com> Date: Tue, 25 Feb 2025 11:57:54 +0800 Subject: [PATCH] :building_construction: refactor data access layer and enhance service architecture --- electron/api/config.ts | 674 ++++---- electron/api/file.ts | 42 +- electron/api/frpc.ts | 1444 ++++++++--------- electron/api/github.ts | 1180 +++++++------- electron/api/update.ts | 162 +- electron/controller/ConfigController.ts | 8 +- electron/controller/ProxyController.ts | 6 +- electron/controller/VersionController.ts | 6 +- electron/core/BeanFactory.ts | 30 +- electron/core/GlobalConstant.ts | 2 +- electron/core/IpcRouter.ts | 133 -- electron/core/Logger.ts | 18 + electron/core/annotation/Component.ts | 25 + electron/core/annotation/Resource.ts | 5 + electron/main/index.ts | 582 ++++--- .../BaseRepository.ts} | 4 +- .../ProxyRepository.ts} | 8 +- .../ServerRepository.ts} | 8 +- .../VersionRepository.ts} | 10 +- electron/service/BaseService.ts | 8 +- electron/service/FrpcProcessService.ts | 14 +- electron/service/LogService.ts | 12 +- electron/service/ProxyService.ts | 8 +- electron/service/ServerService.ts | 10 +- electron/service/SystemService.ts | 1 + electron/service/VersionService.ts | 6 +- package.json | 10 +- src/views/config/index.vue | 32 +- tsconfig.json | 2 +- types/core.d.ts | 4 +- 30 files changed, 2255 insertions(+), 2199 deletions(-) create mode 100644 electron/core/Logger.ts create mode 100644 electron/core/annotation/Component.ts create mode 100644 electron/core/annotation/Resource.ts rename electron/{dao/BaseDao.ts => repository/BaseRepository.ts} (98%) rename electron/{dao/ProxyDao.ts => repository/ProxyRepository.ts} (67%) rename electron/{dao/ServerDao.ts => repository/ServerRepository.ts} (57%) rename electron/{dao/VersionDao.ts => repository/VersionRepository.ts} (70%) diff --git a/electron/api/config.ts b/electron/api/config.ts index ef90b87..17b1fb6 100644 --- a/electron/api/config.ts +++ b/electron/api/config.ts @@ -1,337 +1,337 @@ -import { app, dialog, ipcMain, shell } from "electron"; -import { clearConfig, getConfig, saveConfig } from "../storage/config"; -import { clearVersion, listVersion } from "../storage/version"; -import { genIniConfig, genTomlConfig, stopFrpcProcess } from "./frpc"; -import { clearProxy, insertProxy, listProxy } from "../storage/proxy"; -import path from "path"; -import fs from "fs"; -import { logDebug, logError, logInfo, LogModule, logWarn } from "../utils/log"; - -const toml = require("@iarna/toml"); -const { v4: uuidv4 } = require("uuid"); - -export const initConfigApi = win => { - ipcMain.on("config.saveConfig", async (event, args) => { - logInfo(LogModule.APP, "Attempting to save configuration."); - saveConfig(args, (err, numberOfUpdated, upsert) => { - if (!err) { - const start = args.systemSelfStart || false; - logDebug(LogModule.APP, "Startup status set to: " + start); - app.setLoginItemSettings({ - openAtLogin: start, //win - openAsHidden: start //macOs - }); - logInfo(LogModule.APP, "Configuration saved successfully."); - } else { - logError(LogModule.APP, `Error saving configuration: ${err}`); - } - event.reply("Config.saveConfig.hook", { - err: err, - numberOfUpdated: numberOfUpdated, - upsert: upsert - }); - }); - }); - - ipcMain.on("config.getConfig", async (event, args) => { - logInfo(LogModule.APP, "Requesting configuration."); - getConfig((err, doc) => { - if (err) { - logError(LogModule.APP, `Error retrieving configuration: ${err}`); - } - event.reply("Config.getConfig.hook", { - err: err, - data: doc - }); - }); - }); - - ipcMain.on("config.versions", event => { - logInfo(LogModule.APP, "Requesting version information."); - listVersion((err, doc) => { - if (err) { - logError(LogModule.APP, `Error retrieving version information: ${err}`); - } - event.reply("Config.versions.hook", { - err: err, - data: doc - }); - }); - }); - - ipcMain.on("config.hasConfig", event => { - logInfo(LogModule.APP, "Checking if configuration exists."); - getConfig((err, doc) => { - if (err) { - logError(LogModule.APP, `Error checking configuration: ${err}`); - } - event.reply("Config.getConfig.hook", { - err: err, - data: doc - }); - }); - }); - - ipcMain.on("config.exportConfig", async (event, args) => { - logInfo(LogModule.APP, "Attempting to export configuration."); - const result = await dialog.showOpenDialog({ - properties: ["openDirectory"] - }); - const outputDirectory = result.filePaths[0]; - if (!outputDirectory) { - logWarn(LogModule.APP, "Export canceled by user."); - return; - } - - logInfo( - LogModule.APP, - `Exporting configuration to directory ${outputDirectory} with type: ${args}` - ); - getConfig((err1, config) => { - if (!err1 && config) { - listProxy((err2, proxys) => { - if (!err2) { - let configContent = ""; - if (args === "ini") { - configContent = genIniConfig(config, proxys); - } else if (args === "toml") { - configContent = genTomlConfig(config, proxys); - } - const configPath = path.join( - outputDirectory, - `frpc-desktop.${args}` - ); - fs.writeFile( - configPath, // 配置文件目录 - configContent, // 配置文件内容 - { flag: "w" }, - err => { - if (err) { - logError( - LogModule.APP, - `Error writing configuration file: ${err}` - ); - event.reply("config.exportConfig.hook", { - data: "导出错误", - err: err - }); - } else { - logInfo( - LogModule.APP, - "Configuration exported successfully." - ); - event.reply("Config.exportConfig.hook", { - data: { - configPath: configPath - } - }); - } - } - ); - } else { - logError(LogModule.APP, `Error listing proxies: ${err2}`); - } - }); - } else { - logError(LogModule.APP, `Error retrieving configuration: ${err1}`); - } - }); - }); - - const parseTomlConfig = (tomlPath: string) => { - logInfo(LogModule.APP, `Parsing TOML configuration from ${tomlPath}`); - const importConfigPath = tomlPath; - const tomlData = fs.readFileSync(importConfigPath, "utf-8"); - logInfo(LogModule.APP, "Configuration content read successfully."); - const sourceConfig = toml.parse(tomlData); - // 解析config - const targetConfig: FrpConfig = { - currentVersion: null, - serverAddr: sourceConfig.serverAddr || "", - serverPort: sourceConfig.serverPort || "", - authMethod: sourceConfig?.user - ? "multiuser" - : sourceConfig?.auth?.method || "", - authToken: sourceConfig?.auth?.token || "", - transportHeartbeatInterval: - sourceConfig?.transport?.heartbeatInterval || 30, - transportHeartbeatTimeout: - sourceConfig?.transport?.heartbeatTimeout || 90, - tlsConfigEnable: sourceConfig?.transport?.tls?.enable || false, - tlsConfigCertFile: sourceConfig?.transport?.tls?.certFile || "", - tlsConfigKeyFile: sourceConfig?.transport?.tls?.keyFile || "", - tlsConfigServerName: sourceConfig?.transport?.tls?.serverName || "", - tlsConfigTrustedCaFile: sourceConfig?.transport?.tls?.trustedCaFile || "", - logLevel: sourceConfig?.log?.level || "info", - logMaxDays: sourceConfig?.log?.maxDays || 3, - proxyConfigProxyUrl: sourceConfig?.transport?.proxyURL || "", - proxyConfigEnable: Boolean(sourceConfig?.transport?.proxyURL) || false, - user: sourceConfig?.user || "", - metaToken: sourceConfig?.metadatas?.token || "", - systemSelfStart: false, - systemStartupConnect: false, - systemSilentStartup: false, - webEnable: true, - webPort: sourceConfig?.webServer?.port || 57400, - transportProtocol: sourceConfig?.transport?.protocol || "tcp", - transportDialServerTimeout: - sourceConfig?.transport?.dialServerTimeout || 10, - transportDialServerKeepalive: - sourceConfig?.transport?.dialServerKeepalive || 70, - transportPoolCount: sourceConfig?.transport?.poolCount || 0, - transportTcpMux: sourceConfig?.transport?.tcpMux || true, - transportTcpMuxKeepaliveInterval: - sourceConfig?.transport?.tcpMuxKeepaliveInterval || 7200 - }; - let frpcProxys = []; - // 解析proxy - if (sourceConfig?.proxies && sourceConfig.proxies.length > 0) { - const frpcProxys1 = sourceConfig.proxies.map(m => { - const rm: Proxy = { - _id: uuidv4(), - name: m?.name, - type: m?.type, - localIp: m?.localIP || "", - localPort: m?.localPort || null, - remotePort: m?.remotePort || null, - customDomains: m?.customDomains || [], - subdomain: m.subdomain || "", - basicAuth: m.basicAuth || false, - httpUser: m.httpUser || "", - httpPassword: m.httpPassword || "", - // 以下为stcp参数 - stcpModel: "visited", - serverName: "", - secretKey: m?.secretKey || "", - bindAddr: "", - bindPort: null, - status: m?.status || true, - fallbackTo: m?.fallbackTo, - fallbackTimeoutMs: m?.fallbackTimeoutMs || 500, - keepTunnelOpen: m?.keepTunnelOpen || false, - https2http: m?.https2http || false, - https2httpCaFile: m?.https2httpCaFile || "", - https2httpKeyFile: m?.https2httpKeyFile || "" - }; - return rm; - }); - frpcProxys = [...frpcProxys, ...frpcProxys1]; - logInfo(LogModule.APP, "Parsed proxies from configuration."); - } - // 解析stcp的访问者 - if (sourceConfig?.visitors && sourceConfig.visitors.length > 0) { - const frpcProxys2 = sourceConfig.visitors.map(m => { - const rm: Proxy = { - _id: uuidv4(), - name: m?.name, - type: m?.type, - localIp: "", - localPort: null, - remotePort: null, - customDomains: [], - subdomain: m.subdomain || "", - basicAuth: m.basicAuth || false, - httpUser: m.httpUser || "", - httpPassword: m.httpPassword || "", - // 以下为stcp参数 - stcpModel: "visitors", - serverName: m?.serverName, - secretKey: m?.secretKey || "", - bindAddr: m?.bindAddr, - bindPort: m?.bindPort, - status: m?.status || true, - fallbackTo: m?.fallbackTo, - fallbackTimeoutMs: m?.fallbackTimeoutMs || 500, - keepTunnelOpen: m?.keepTunnelOpen || false, - https2http: m?.https2http || false, - https2httpCaFile: m?.https2httpCaFile || "", - https2httpKeyFile: m?.https2httpKeyFile || "" - }; - return rm; - }); - frpcProxys = [...frpcProxys, ...frpcProxys2]; - logInfo(LogModule.APP, "Parsed visitors from configuration."); - } - if (targetConfig) { - clearConfig(() => { - logInfo(LogModule.APP, "Clearing existing configuration."); - saveConfig(targetConfig); - logInfo(LogModule.APP, "New configuration saved."); - }); - } - if (frpcProxys && frpcProxys.length > 0) { - clearProxy(() => { - frpcProxys.forEach(f => { - insertProxy(f, err => { - if (err) { - logError(LogModule.APP, `Error inserting proxy: ${err}`); - } else { - logInfo(LogModule.APP, `Inserted proxy: ${JSON.stringify(f)}`); - } - }); - }); - }); - } - }; - - ipcMain.on("config.importConfig", async (event, args) => { - logInfo(LogModule.APP, "Attempting to import configuration."); - const result = await dialog.showOpenDialog(win, { - properties: ["openFile"], - filters: [ - { name: "FrpcConfig Files", extensions: ["toml", "ini"] } // 允许选择的文件类型 - ] - }); - if (result.canceled) { - logWarn(LogModule.APP, "Import canceled by user."); - return; - } else { - const filePath = result.filePaths[0]; - const fileExtension = path.extname(filePath); // 获取文件后缀名 - logWarn( - LogModule.APP, - `Importing file ${filePath} with extension ${fileExtension}` - ); - if (fileExtension === ".toml") { - parseTomlConfig(filePath); - event.reply("Config.importConfig.hook", { - success: true - }); - } else { - logError( - LogModule.APP, - `Import failed, unsupported file format: ${fileExtension}` - ); - event.reply("Config.importConfig.hook", { - success: false, - data: `导入失败,暂不支持 ${fileExtension} 格式文件` - }); - } - } - }); - - ipcMain.on("config.clearAll", async (event, args) => { - logInfo(LogModule.APP, "Clearing all configurations."); - stopFrpcProcess(() => { - clearConfig(); - clearProxy(); - clearVersion(); - event.reply("Config.clearAll.hook", {}); - logInfo(LogModule.APP, "All configurations cleared."); - }); - }); - - ipcMain.on("config.openDataFolder", async (event, args) => { - const userDataPath = app.getPath("userData"); - logInfo(LogModule.APP, "Attempting to open data folder."); - shell.openPath(userDataPath).then(errorMessage => { - if (errorMessage) { - logError(LogModule.APP, `Failed to open data folder: ${errorMessage}`); - event.reply("Config.openDataFolder.hook", false); - } else { - logInfo(LogModule.APP, "Data folder opened successfully."); - event.reply("Config.openDataFolder.hook", true); - } - }); - }); -}; +// import { app, dialog, ipcMain, shell } from "electron"; +// import { clearConfig, getConfig, saveConfig } from "../storage/config"; +// import { clearVersion, listVersion } from "../storage/version"; +// import { genIniConfig, genTomlConfig, stopFrpcProcess } from "./frpc"; +// import { clearProxy, insertProxy, listProxy } from "../storage/proxy"; +// import path from "path"; +// import fs from "fs"; +// import { logDebug, logError, logInfo, LogModule, logWarn } from "../utils/log"; +// +// const toml = require("@iarna/toml"); +// const { v4: uuidv4 } = require("uuid"); +// +// export const initConfigApi = win => { +// ipcMain.on("config.saveConfig", async (event, args) => { +// logInfo(LogModule.APP, "Attempting to save configuration."); +// saveConfig(args, (err, numberOfUpdated, upsert) => { +// if (!err) { +// const start = args.systemSelfStart || false; +// logDebug(LogModule.APP, "Startup status set to: " + start); +// app.setLoginItemSettings({ +// openAtLogin: start, //win +// openAsHidden: start //macOs +// }); +// logInfo(LogModule.APP, "Configuration saved successfully."); +// } else { +// logError(LogModule.APP, `Error saving configuration: ${err}`); +// } +// event.reply("Config.saveConfig.hook", { +// err: err, +// numberOfUpdated: numberOfUpdated, +// upsert: upsert +// }); +// }); +// }); +// +// ipcMain.on("config.getConfig", async (event, args) => { +// logInfo(LogModule.APP, "Requesting configuration."); +// getConfig((err, doc) => { +// if (err) { +// logError(LogModule.APP, `Error retrieving configuration: ${err}`); +// } +// event.reply("Config.getConfig.hook", { +// err: err, +// data: doc +// }); +// }); +// }); +// +// ipcMain.on("config.versions", event => { +// logInfo(LogModule.APP, "Requesting version information."); +// listVersion((err, doc) => { +// if (err) { +// logError(LogModule.APP, `Error retrieving version information: ${err}`); +// } +// event.reply("Config.versions.hook", { +// err: err, +// data: doc +// }); +// }); +// }); +// +// ipcMain.on("config.hasConfig", event => { +// logInfo(LogModule.APP, "Checking if configuration exists."); +// getConfig((err, doc) => { +// if (err) { +// logError(LogModule.APP, `Error checking configuration: ${err}`); +// } +// event.reply("Config.getConfig.hook", { +// err: err, +// data: doc +// }); +// }); +// }); +// +// ipcMain.on("config.exportConfig", async (event, args) => { +// logInfo(LogModule.APP, "Attempting to export configuration."); +// const result = await dialog.showOpenDialog({ +// properties: ["openDirectory"] +// }); +// const outputDirectory = result.filePaths[0]; +// if (!outputDirectory) { +// logWarn(LogModule.APP, "Export canceled by user."); +// return; +// } +// +// logInfo( +// LogModule.APP, +// `Exporting configuration to directory ${outputDirectory} with type: ${args}` +// ); +// getConfig((err1, config) => { +// if (!err1 && config) { +// listProxy((err2, proxys) => { +// if (!err2) { +// let configContent = ""; +// if (args === "ini") { +// configContent = genIniConfig(config, proxys); +// } else if (args === "toml") { +// configContent = genTomlConfig(config, proxys); +// } +// const configPath = path.join( +// outputDirectory, +// `frpc-desktop.${args}` +// ); +// fs.writeFile( +// configPath, // 配置文件目录 +// configContent, // 配置文件内容 +// { flag: "w" }, +// err => { +// if (err) { +// logError( +// LogModule.APP, +// `Error writing configuration file: ${err}` +// ); +// event.reply("config.exportConfig.hook", { +// data: "导出错误", +// err: err +// }); +// } else { +// logInfo( +// LogModule.APP, +// "Configuration exported successfully." +// ); +// event.reply("Config.exportConfig.hook", { +// data: { +// configPath: configPath +// } +// }); +// } +// } +// ); +// } else { +// logError(LogModule.APP, `Error listing proxies: ${err2}`); +// } +// }); +// } else { +// logError(LogModule.APP, `Error retrieving configuration: ${err1}`); +// } +// }); +// }); +// +// const parseTomlConfig = (tomlPath: string) => { +// logInfo(LogModule.APP, `Parsing TOML configuration from ${tomlPath}`); +// const importConfigPath = tomlPath; +// const tomlData = fs.readFileSync(importConfigPath, "utf-8"); +// logInfo(LogModule.APP, "Configuration content read successfully."); +// const sourceConfig = toml.parse(tomlData); +// // 解析config +// const targetConfig: FrpConfig = { +// currentVersion: null, +// serverAddr: sourceConfig.serverAddr || "", +// serverPort: sourceConfig.serverPort || "", +// authMethod: sourceConfig?.user +// ? "multiuser" +// : sourceConfig?.auth?.method || "", +// authToken: sourceConfig?.auth?.token || "", +// transportHeartbeatInterval: +// sourceConfig?.transport?.heartbeatInterval || 30, +// transportHeartbeatTimeout: +// sourceConfig?.transport?.heartbeatTimeout || 90, +// tlsConfigEnable: sourceConfig?.transport?.tls?.enable || false, +// tlsConfigCertFile: sourceConfig?.transport?.tls?.certFile || "", +// tlsConfigKeyFile: sourceConfig?.transport?.tls?.keyFile || "", +// tlsConfigServerName: sourceConfig?.transport?.tls?.serverName || "", +// tlsConfigTrustedCaFile: sourceConfig?.transport?.tls?.trustedCaFile || "", +// logLevel: sourceConfig?.log?.level || "info", +// logMaxDays: sourceConfig?.log?.maxDays || 3, +// proxyConfigProxyUrl: sourceConfig?.transport?.proxyURL || "", +// proxyConfigEnable: Boolean(sourceConfig?.transport?.proxyURL) || false, +// user: sourceConfig?.user || "", +// metaToken: sourceConfig?.metadatas?.token || "", +// systemSelfStart: false, +// systemStartupConnect: false, +// systemSilentStartup: false, +// webEnable: true, +// webPort: sourceConfig?.webServer?.port || 57400, +// transportProtocol: sourceConfig?.transport?.protocol || "tcp", +// transportDialServerTimeout: +// sourceConfig?.transport?.dialServerTimeout || 10, +// transportDialServerKeepalive: +// sourceConfig?.transport?.dialServerKeepalive || 70, +// transportPoolCount: sourceConfig?.transport?.poolCount || 0, +// transportTcpMux: sourceConfig?.transport?.tcpMux || true, +// transportTcpMuxKeepaliveInterval: +// sourceConfig?.transport?.tcpMuxKeepaliveInterval || 7200 +// }; +// let frpcProxys = []; +// // 解析proxy +// if (sourceConfig?.proxies && sourceConfig.proxies.length > 0) { +// const frpcProxys1 = sourceConfig.proxies.map(m => { +// const rm: Proxy = { +// _id: uuidv4(), +// name: m?.name, +// type: m?.type, +// localIp: m?.localIP || "", +// localPort: m?.localPort || null, +// remotePort: m?.remotePort || null, +// customDomains: m?.customDomains || [], +// subdomain: m.subdomain || "", +// basicAuth: m.basicAuth || false, +// httpUser: m.httpUser || "", +// httpPassword: m.httpPassword || "", +// // 以下为stcp参数 +// stcpModel: "visited", +// serverName: "", +// secretKey: m?.secretKey || "", +// bindAddr: "", +// bindPort: null, +// status: m?.status || true, +// fallbackTo: m?.fallbackTo, +// fallbackTimeoutMs: m?.fallbackTimeoutMs || 500, +// keepTunnelOpen: m?.keepTunnelOpen || false, +// https2http: m?.https2http || false, +// https2httpCaFile: m?.https2httpCaFile || "", +// https2httpKeyFile: m?.https2httpKeyFile || "" +// }; +// return rm; +// }); +// frpcProxys = [...frpcProxys, ...frpcProxys1]; +// logInfo(LogModule.APP, "Parsed proxies from configuration."); +// } +// // 解析stcp的访问者 +// if (sourceConfig?.visitors && sourceConfig.visitors.length > 0) { +// const frpcProxys2 = sourceConfig.visitors.map(m => { +// const rm: Proxy = { +// _id: uuidv4(), +// name: m?.name, +// type: m?.type, +// localIp: "", +// localPort: null, +// remotePort: null, +// customDomains: [], +// subdomain: m.subdomain || "", +// basicAuth: m.basicAuth || false, +// httpUser: m.httpUser || "", +// httpPassword: m.httpPassword || "", +// // 以下为stcp参数 +// stcpModel: "visitors", +// serverName: m?.serverName, +// secretKey: m?.secretKey || "", +// bindAddr: m?.bindAddr, +// bindPort: m?.bindPort, +// status: m?.status || true, +// fallbackTo: m?.fallbackTo, +// fallbackTimeoutMs: m?.fallbackTimeoutMs || 500, +// keepTunnelOpen: m?.keepTunnelOpen || false, +// https2http: m?.https2http || false, +// https2httpCaFile: m?.https2httpCaFile || "", +// https2httpKeyFile: m?.https2httpKeyFile || "" +// }; +// return rm; +// }); +// frpcProxys = [...frpcProxys, ...frpcProxys2]; +// logInfo(LogModule.APP, "Parsed visitors from configuration."); +// } +// if (targetConfig) { +// clearConfig(() => { +// logInfo(LogModule.APP, "Clearing existing configuration."); +// saveConfig(targetConfig); +// logInfo(LogModule.APP, "New configuration saved."); +// }); +// } +// if (frpcProxys && frpcProxys.length > 0) { +// clearProxy(() => { +// frpcProxys.forEach(f => { +// insertProxy(f, err => { +// if (err) { +// logError(LogModule.APP, `Error inserting proxy: ${err}`); +// } else { +// logInfo(LogModule.APP, `Inserted proxy: ${JSON.stringify(f)}`); +// } +// }); +// }); +// }); +// } +// }; +// +// ipcMain.on("config.importConfig", async (event, args) => { +// logInfo(LogModule.APP, "Attempting to import configuration."); +// const result = await dialog.showOpenDialog(win, { +// properties: ["openFile"], +// filters: [ +// { name: "FrpcConfig Files", extensions: ["toml", "ini"] } // 允许选择的文件类型 +// ] +// }); +// if (result.canceled) { +// logWarn(LogModule.APP, "Import canceled by user."); +// return; +// } else { +// const filePath = result.filePaths[0]; +// const fileExtension = path.extname(filePath); // 获取文件后缀名 +// logWarn( +// LogModule.APP, +// `Importing file ${filePath} with extension ${fileExtension}` +// ); +// if (fileExtension === ".toml") { +// parseTomlConfig(filePath); +// event.reply("Config.importConfig.hook", { +// success: true +// }); +// } else { +// logError( +// LogModule.APP, +// `Import failed, unsupported file format: ${fileExtension}` +// ); +// event.reply("Config.importConfig.hook", { +// success: false, +// data: `导入失败,暂不支持 ${fileExtension} 格式文件` +// }); +// } +// } +// }); +// +// ipcMain.on("config.clearAll", async (event, args) => { +// logInfo(LogModule.APP, "Clearing all configurations."); +// stopFrpcProcess(() => { +// clearConfig(); +// clearProxy(); +// clearVersion(); +// event.reply("Config.clearAll.hook", {}); +// logInfo(LogModule.APP, "All configurations cleared."); +// }); +// }); +// +// ipcMain.on("config.openDataFolder", async (event, args) => { +// const userDataPath = app.getPath("userData"); +// logInfo(LogModule.APP, "Attempting to open data folder."); +// shell.openPath(userDataPath).then(errorMessage => { +// if (errorMessage) { +// logError(LogModule.APP, `Failed to open data folder: ${errorMessage}`); +// event.reply("Config.openDataFolder.hook", false); +// } else { +// logInfo(LogModule.APP, "Data folder opened successfully."); +// event.reply("Config.openDataFolder.hook", true); +// } +// }); +// }); +// }; diff --git a/electron/api/file.ts b/electron/api/file.ts index 2180366..643cd44 100644 --- a/electron/api/file.ts +++ b/electron/api/file.ts @@ -1,21 +1,21 @@ -import {dialog, ipcMain} from "electron"; -import { logInfo, logError, LogModule } from "../utils/log"; - -export const initFileApi = () => { - ipcMain.handle("file.selectFile", async (event, args) => { - logInfo(LogModule.APP, `Attempting to open file dialog with filters: ${JSON.stringify(args)}`); - try { - const result = dialog.showOpenDialogSync({ - properties: ['openFile'], - filters: [ - { name: 'Text Files', extensions: args }, - ] - }); - logInfo(LogModule.APP, `File dialog result: ${JSON.stringify(result)}`); - return result; - } catch (error) { - logError(LogModule.APP, `Error opening file dialog: ${error.message}`); - return null; - } - }); -} +// import {dialog, ipcMain} from "electron"; +// import { logInfo, logError, LogModule } from "../utils/log"; +// +// export const initFileApi = () => { +// ipcMain.handle("file.selectFile", async (event, args) => { +// logInfo(LogModule.APP, `Attempting to open file dialog with filters: ${JSON.stringify(args)}`); +// try { +// const result = dialog.showOpenDialogSync({ +// properties: ['openFile'], +// filters: [ +// { name: 'Text Files', extensions: args }, +// ] +// }); +// logInfo(LogModule.APP, `File dialog result: ${JSON.stringify(result)}`); +// return result; +// } catch (error) { +// logError(LogModule.APP, `Error opening file dialog: ${error.message}`); +// return null; +// } +// }); +// } diff --git a/electron/api/frpc.ts b/electron/api/frpc.ts index 8e16388..e9d061c 100644 --- a/electron/api/frpc.ts +++ b/electron/api/frpc.ts @@ -1,722 +1,722 @@ -import { app, ipcMain, Notification } from "electron"; -import { getConfig } from "../storage/config"; -import { listProxy } from "../storage/proxy"; -import { getVersionById } from "../storage/version"; -import treeKill from "tree-kill"; -import { logInfo, logError, LogModule, logDebug, logWarn } from "../utils/log"; - -const fs = require("fs"); -const path = require("path"); -const { exec, spawn } = require("child_process"); - -export let frpcProcess = null; -const runningCmd = { - commandPath: null, - configPath: null -}; -let frpcStatusListener = null; - -const getFrpcVersionWorkerPath = ( - versionId: number, - callback: (workerPath: string) => void -) => { - getVersionById(versionId, (err2, version) => { - if (!err2) { - if (version) { - callback(version["frpcVersionPath"]); - } - } - }); -}; - -const isRangePort = (m: Proxy) => { - return ( - (m.type === "tcp" || m.type === "udp") && - (String(m.localPort).indexOf("-") !== -1 || - String(m.localPort).indexOf(",") !== -1) - ); -}; - -/** - * 生成toml配置文件 - * @param config - * @param proxys - */ -export const genTomlConfig = (config: FrpConfig, proxys: Proxy[]) => { - const proxyToml = proxys.map(m => { - const rangePort = isRangePort(m); - config.tlsConfigKeyFile = config.tlsConfigKeyFile.replace(/\\/g, "\\\\"); - config.tlsConfigCertFile = config.tlsConfigCertFile.replace(/\\/g, "\\\\"); - config.tlsConfigTrustedCaFile = config.tlsConfigTrustedCaFile.replace( - /\\/g, - "\\\\" - ); - let toml = `${ - rangePort - ? `{{- range $_, $v := parseNumberRangePair "${m.localPort}" "${m.remotePort}" }}` - : "" - } -[[${ - (m.type === "stcp" || m.type === "xtcp" || m.type === "sudp") && - m.stcpModel === "visitors" - ? "visitors" - : "proxies" - }]] -${rangePort ? "" : `name = "${m.name}"`} -type = "${m.type}"\n`; - - switch (m.type) { - case "tcp": - case "udp": - if (rangePort) { - toml += `name = "${m.name}-{{ $v.First }}" -localPort = {{ $v.First }} -remotePort = {{ $v.Second }}\n`; - } else { - toml += `localIP = "${m.localIp}" -localPort = ${m.localPort} -remotePort = ${m.remotePort}\n`; - } - break; - case "http": - case "https": - const customDomains = m.customDomains.filter(f1 => f1 !== ""); - if (customDomains && customDomains.length > 0) { - toml += `customDomains=[${m.customDomains.map(m => `"${m}"`)}]\n`; - } - if (m.subdomain) { - toml += `subdomain="${m.subdomain}"\n`; - } - if (m.basicAuth) { - toml += `httpUser = "${m.httpUser}" -httpPassword = "${m.httpPassword}"\n`; - } - if (m.https2http) { - toml += `[proxies.plugin] -type = "https2http" -localAddr = "${m.localIp}:${m.localPort}" - -crtPath = "${m.https2httpCaFile}" -keyPath = "${m.https2httpKeyFile}"\n`; - } else { - toml += `localIP = "${m.localIp}" -localPort = ${m.localPort}\n`; - } - - break; - case "xtcp": - if (m.stcpModel === "visitors") { - toml += `keepTunnelOpen = ${m.keepTunnelOpen}\n`; - } - case "stcp": - case "sudp": - if (m.stcpModel === "visitors") { - // 访问者 - toml += `serverName = "${m.serverName}" -bindAddr = "${m.bindAddr}" -bindPort = ${m.bindPort}\n`; - if (m.fallbackTo) { - toml += `fallbackTo = "${m.fallbackTo}" -fallbackTimeoutMs = ${m.fallbackTimeoutMs || 500}\n`; - } - } else if (m.stcpModel === "visited") { - // 被访问者 - toml += `localIP = "${m.localIp}" -localPort = ${m.localPort}\n`; - } - - toml += `secretKey="${m.secretKey}"\n`; - break; - default: - break; - } - - if (rangePort) { - toml += `{{- end }}\n`; - } - return toml; - }); - const toml = `serverAddr = "${config.serverAddr}" -serverPort = ${config.serverPort} -${ - config.authMethod === "token" - ? `auth.method = "token" -auth.token = "${config.authToken}"` - : "" -} -${ - config.authMethod === "multiuser" - ? `user = "${config.user}" -metadatas.token = "${config.metaToken}"` - : "" -} -log.to = "frpc.log" -log.level = "${config.logLevel}" -log.maxDays = ${config.logMaxDays} -webServer.addr = "127.0.0.1" -webServer.port = ${config.webPort} -${ - config.transportProtocol - ? `transport.protocol = "${config.transportProtocol}"` - : "" -} -${ - config.transportPoolCount - ? `transport.poolCount = ${config.transportPoolCount}` - : "" -} -${ - config.transportDialServerTimeout - ? `transport.dialServerTimeout = ${config.transportDialServerTimeout}` - : "" -} -${ - config.transportDialServerKeepalive - ? `transport.dialServerKeepalive = ${config.transportDialServerKeepalive}` - : "" -} -${ - config.transportHeartbeatInterval - ? `transport.heartbeatInterval = ${config.transportHeartbeatInterval}` - : "" -} -${ - config.transportHeartbeatTimeout - ? `transport.heartbeatTimeout = ${config.transportHeartbeatTimeout}` - : "" -} -${config.transportTcpMux ? `transport.tcpMux = ${config.transportTcpMux}` : ""} -${ - config.transportTcpMux && config.transportTcpMuxKeepaliveInterval - ? `transport.tcpMuxKeepaliveInterval = ${config.transportTcpMuxKeepaliveInterval}` - : "" -} -transport.tls.enable = ${config.tlsConfigEnable} -${ - config.tlsConfigEnable && config.tlsConfigCertFile - ? `transport.tls.certFile = "${config.tlsConfigCertFile}"` - : "" -} -${ - config.tlsConfigEnable && config.tlsConfigKeyFile - ? `transport.tls.keyFile = "${config.tlsConfigKeyFile}"` - : "" -} - ${ - config.tlsConfigEnable && config.tlsConfigTrustedCaFile - ? `transport.tls.trustedCaFile = "${config.tlsConfigTrustedCaFile}"` - : "" - } - ${ - config.tlsConfigEnable && config.tlsConfigServerName - ? `transport.tls.serverName = "${config.tlsConfigServerName}"` - : "" - } -${ - config.proxyConfigEnable - ? `transport.proxyURL = "${config.proxyConfigProxyUrl}"` - : "" -} -${proxyToml.join("")}`; - return toml; -}; - -/** - * 生成ini配置 - * @param config - * @param proxys - */ -export const genIniConfig = (config: FrpConfig, proxys: Proxy[]) => { - const proxyIni = proxys.map(m => { - const rangePort = isRangePort(m); - let ini = `[${rangePort ? "range:" : ""}${m.name}] -type = "${m.type}" -`; - switch (m.type) { - case "tcp": - case "udp": - ini += ` -local_ip = "${m.localIp}" -local_port = ${m.localPort} -remote_port = ${m.remotePort}\n`; - break; - case "http": - ini += ` - local_ip = "${m.localIp}" - local_port = ${m.localPort} - custom_domains=[${m.customDomains.map(m => `${m}`)}] - subdomain="${m.subdomain}"\n`; - if (m.basicAuth) { - ini += ` - httpUser = "${m.httpUser}" - httpPassword = "${m.httpPassword}"\n`; - } - break; - case "https": - ini += ` -custom_domains=[${m.customDomains.map(m => `${m}`)}] -subdomain="${m.subdomain}"\n`; - if (m.basicAuth) { - ini += ` -httpUser = "${m.httpUser}" -httpPassword = "${m.httpPassword}"\n`; - } - if (m.https2http) { - ini += ` -plugin = https2http -plugin_local_addr = ${m.localIp}:${m.localPort} -plugin_crt_path = ${m.https2httpCaFile} -plugin_key_path = ${m.https2httpKeyFile}\n`; - } else { - ini += ` -local_ip = "${m.localIp}" -local_port = ${m.localPort}\n`; - } - break; - case "xtcp": - if (m.stcpModel === "visitors") { - ini += `keep_tunnel_open = ${m.keepTunnelOpen}\n`; - } - case "stcp": - case "sudp": - if (m.stcpModel === "visitors") { - // 访问者 - ini += ` -role = visitor -server_name = "${m.serverName}" -bind_addr = "${m.bindAddr}" -bind_port = ${m.bindPort}\n`; - if (m.fallbackTo) { - ini += ` -fallback_to = ${m.fallbackTo} -fallback_timeout_ms = ${m.fallbackTimeoutMs || 500}\n`; - } - } else if (m.stcpModel === "visited") { - // 被访问者 - ini += ` -local_ip = "${m.localIp}" -local_port = ${m.localPort}\n`; - } - ini += ` -sk="${m.secretKey}"\n`; - break; - default: - break; - } - - return ini; - }); - const ini = ` -[common] -server_addr = ${config.serverAddr} -server_port = ${config.serverPort} -${ - config.authMethod === "token" - ? ` -authentication_method = ${config.authMethod} -token = ${config.authToken}\n` - : "" -} -${ - config.authMethod === "multiuser" - ? ` -user = ${config.user} -meta_token = ${config.metaToken}\n` - : "" -} - -${config.transportProtocol ? `protocol = ${config.transportProtocol}` : ""} -${config.transportPoolCount ? `pool_count = ${config.transportPoolCount}` : ""} -${ - config.transportDialServerTimeout - ? `dial_server_timeout = ${config.transportDialServerTimeout}` - : "" -} -${ - config.transportDialServerKeepalive - ? `dial_server_keepalive = ${config.transportDialServerKeepalive}` - : "" -} -${ - config.transportHeartbeatInterval - ? ` -heartbeat_interval = ${config.transportHeartbeatInterval} -` - : "" -} -${ - config.transportHeartbeatTimeout - ? ` -heartbeat_timeout = ${config.transportHeartbeatTimeout} -` - : "" -} -${config.transportTcpMux ? `transport.tcp_mux = ${config.transportTcpMux}` : ""} -${ - config.transportTcpMux && config.transportTcpMuxKeepaliveInterval - ? `tcp_mux_keepalive_interval = ${config.transportTcpMuxKeepaliveInterval}` - : "" -} -${ - config.tlsConfigEnable && config.tlsConfigCertFile - ? ` -tls_cert_file = ${config.tlsConfigCertFile}\n` - : "" -} - ${ - config.tlsConfigEnable && config.tlsConfigKeyFile - ? ` -tls_key_file = ${config.tlsConfigKeyFile}\n` - : "" - } - ${ - config.tlsConfigEnable && config.tlsConfigTrustedCaFile - ? ` -tls_trusted_ca_file = ${config.tlsConfigTrustedCaFile}\n` - : "" - } - ${ - config.tlsConfigEnable && config.tlsConfigServerName - ? ` -tls_server_name = ${config.tlsConfigServerName}\n` - : "" - } - - ${ - config.proxyConfigEnable - ? ` - http_proxy = "${config.proxyConfigProxyUrl}"\n` - : "" - } - -log_file = "frpc.log" -log_level = ${config.logLevel} -log_max_days = ${config.logMaxDays} -admin_addr = 127.0.0.1 -admin_port = ${config.webPort} -tls_enable = ${config.tlsConfigEnable} - - -${proxyIni.join("")} - `; - return ini; -}; - -/** - * 生成配置文件 - */ -export const generateConfig = ( - config: FrpConfig, - callback: (configPath: string) => void -) => { - listProxy((err3, proxys) => { - if (err3) { - logError(LogModule.FRP_CLIENT, `Failed to list proxies: ${err3.message}`); - return; - } - - const { currentVersion } = config; - let filename = null; - let configContent = ""; - const filtered = proxys - .map(m => { - if (m.status == null || m.status == undefined) { - m.status = true; - } - return m; - }) - .filter(f => f.status); - - if (currentVersion < 124395282) { - // 版本小于v0.52.0 - filename = "frp.ini"; - configContent = genIniConfig(config, filtered); - logInfo( - LogModule.FRP_CLIENT, - `Using INI format for configuration: ${filename}` - ); - } else { - filename = "frp.toml"; - configContent = genTomlConfig(config, filtered); - logInfo( - LogModule.FRP_CLIENT, - `Using TOML format for configuration: ${filename}` - ); - } - - const configPath = path.join(app.getPath("userData"), filename); - logInfo( - LogModule.FRP_CLIENT, - `Writing configuration to file: ${configPath}` - ); - - fs.writeFile( - configPath, // 配置文件目录 - configContent, // 配置文件内容 - { flag: "w" }, - err => { - if (err) { - logError( - LogModule.FRP_CLIENT, - `Failed to write configuration file: ${err.message}` - ); - } else { - logInfo( - LogModule.FRP_CLIENT, - `Configuration file written successfully: ${filename}` - ); - callback(filename); - } - } - ); - }); -}; - -/** - * 启动frpc子进程 - * @param cwd - * @param commandPath - * @param configPath - */ -const startFrpcProcess = (commandPath: string, configPath: string) => { - logInfo( - LogModule.FRP_CLIENT, - `Starting frpc process. Directory: ${app.getPath( - "userData" - )}, Command: ${commandPath}` - ); - - const command = `${commandPath} -c ${configPath}`; - frpcProcess = spawn(command, { - cwd: app.getPath("userData"), - shell: true - }); - runningCmd.commandPath = commandPath; - runningCmd.configPath = configPath; - - frpcProcess.stdout.on("data", data => { - logDebug(LogModule.FRP_CLIENT, `Frpc process output: ${data}`); - }); - - frpcProcess.stdout.on("error", data => { - logError(LogModule.FRP_CLIENT, `Frpc process error: ${data}`); - stopFrpcProcess(() => {}); - }); - - frpcStatusListener = setInterval(() => { - const status = frpcProcessStatus(); - logDebug( - LogModule.FRP_CLIENT, - `Monitoring frpc process status: ${status}, Listener ID: ${frpcStatusListener}` - ); - if (!status) { - new Notification({ - title: "Frpc Desktop", - body: "Connection lost, please check the logs for details." - }).show(); - logError( - LogModule.FRP_CLIENT, - "Frpc process status check failed. Connection lost." - ); - clearInterval(frpcStatusListener); - } - }, 3000); -}; - -/** - * 重载frpc配置 - */ -export const reloadFrpcProcess = () => { - if (frpcProcess && !frpcProcess.killed) { - logDebug( - LogModule.FRP_CLIENT, - "Attempting to reload frpc process configuration." - ); - getConfig((err1, config) => { - if (!err1) { - if (config) { - generateConfig(config, configPath => { - const command = `${runningCmd.commandPath} reload -c ${configPath}`; - logInfo( - LogModule.FRP_CLIENT, - `Reloading configuration: ${command}` - ); - exec( - command, - { - cwd: app.getPath("userData"), - shell: true - }, - (error, stdout, stderr) => { - if (error) { - logError( - LogModule.FRP_CLIENT, - `Error reloading configuration: ${error.message}` - ); - return; - } - logDebug( - LogModule.FRP_CLIENT, - `Configuration reload output: ${stdout}` - ); - if (stderr) { - logWarn( - LogModule.FRP_CLIENT, - `Configuration reload warnings: ${stderr}` - ); - } - } - ); - }); - } else { - logWarn(LogModule.FRP_CLIENT, "No configuration found to reload."); - } - } else { - logError(LogModule.FRP_CLIENT, `Error getting configuration: ${err1}`); - } - }); - } else { - logDebug( - LogModule.FRP_CLIENT, - "frpc process is not running or has been killed." - ); - } -}; - -/** - * 停止frpc子进程 - */ -export const stopFrpcProcess = (callback?: () => void) => { - if (frpcProcess) { - treeKill(frpcProcess.pid, (error: Error) => { - if (error) { - logError( - LogModule.FRP_CLIENT, - `Failed to stop frpc process with pid: ${frpcProcess.pid}. Error: ${error.message}` - ); - callback(); - } else { - logInfo( - LogModule.FRP_CLIENT, - `Successfully stopped frpc process with pid: ${frpcProcess.pid}.` - ); - frpcProcess = null; - clearInterval(frpcStatusListener); - callback(); - } - }); - } else { - logWarn( - LogModule.FRP_CLIENT, - "Attempted to stop frpc process, but no process is running." - ); - logWarn(LogModule.FRP_CLIENT, "No frpc process to stop."); - callback(); - } -}; - -/** - * 获取frpc子进程状态 - */ -export const frpcProcessStatus = () => { - if (!frpcProcess) { - logDebug(LogModule.FRP_CLIENT, "frpc process is not running."); - return false; - } - try { - // 发送信号给进程,如果进程存在,会正常返回 - process.kill(frpcProcess.pid, 0); - logDebug( - LogModule.FRP_CLIENT, - `frpc process is running with pid: ${frpcProcess.pid}` - ); - return true; - } catch (error) { - // 进程不存在,抛出异常 - logError( - LogModule.FRP_CLIENT, - `frpc process not found. Error: ${error.message}` - ); - return false; - } -}; - -/** - * 启动frpc流程 - * @param config - */ -export const startFrpWorkerProcess = async (config: FrpConfig) => { - logInfo(LogModule.FRP_CLIENT, "Starting frpc worker process..."); - getFrpcVersionWorkerPath(config.currentVersion, (frpcVersionPath: string) => { - if (frpcVersionPath) { - logInfo( - LogModule.FRP_CLIENT, - `Found frpc version path: ${frpcVersionPath}` - ); - generateConfig(config, configPath => { - const platform = process.platform; - if (platform === "win32") { - logInfo(LogModule.FRP_CLIENT, "Starting frpc on Windows."); - startFrpcProcess(path.join(frpcVersionPath, "frpc.exe"), configPath); - } else { - logInfo( - LogModule.FRP_CLIENT, - "Starting frpc on non-Windows platform." - ); - startFrpcProcess(path.join(frpcVersionPath, "frpc"), configPath); - } - }); - } else { - logError(LogModule.FRP_CLIENT, "frpc version path not found."); - } - }); -}; - -export const initFrpcApi = () => { - ipcMain.handle("frpc.running", async (event, args) => { - logDebug(LogModule.FRP_CLIENT, "Checking if frpc process is running..."); - return frpcProcessStatus(); - }); - - ipcMain.on("frpc.start", async (event, args) => { - logInfo(LogModule.FRP_CLIENT, "Received request to start frpc process."); - getConfig((err1, config) => { - if (!err1) { - if (!config) { - logWarn( - LogModule.FRP_CLIENT, - "Configuration not found. Prompting user to set configuration." - ); - event.reply( - "Home.frpc.start.error.hook", - "请先前往设置页面,修改配置后再启动" - ); - return; - } - if (!config.currentVersion) { - logWarn( - LogModule.FRP_CLIENT, - "Current version not set in configuration. Prompting user." - ); - event.reply( - "Home.frpc.start.error.hook", - "请先前往设置页面,修改配置后再启动" - ); - return; - } - startFrpWorkerProcess(config); - } else { - logError(LogModule.FRP_CLIENT, `Error getting configuration: ${err1}`); - } - }); - }); - - ipcMain.on("frpc.stop", () => { - logInfo(LogModule.FRP_CLIENT, "Received request to stop frpc process."); - if (frpcProcess && !frpcProcess.killed) { - stopFrpcProcess(() => {}); - } else { - logWarn(LogModule.FRP_CLIENT, "No frpc process to stop."); - } - }); -}; +// import { app, ipcMain, Notification } from "electron"; +// import { getConfig } from "../storage/config"; +// import { listProxy } from "../storage/proxy"; +// import { getVersionById } from "../storage/version"; +// import treeKill from "tree-kill"; +// import { logInfo, logError, LogModule, logDebug, logWarn } from "../utils/log"; +// +// const fs = require("fs"); +// const path = require("path"); +// const { exec, spawn } = require("child_process"); +// +// export let frpcProcess = null; +// const runningCmd = { +// commandPath: null, +// configPath: null +// }; +// let frpcStatusListener = null; +// +// const getFrpcVersionWorkerPath = ( +// versionId: number, +// callback: (workerPath: string) => void +// ) => { +// getVersionById(versionId, (err2, version) => { +// if (!err2) { +// if (version) { +// callback(version["frpcVersionPath"]); +// } +// } +// }); +// }; +// +// const isRangePort = (m: Proxy) => { +// return ( +// (m.type === "tcp" || m.type === "udp") && +// (String(m.localPort).indexOf("-") !== -1 || +// String(m.localPort).indexOf(",") !== -1) +// ); +// }; +// +// /** +// * 生成toml配置文件 +// * @param config +// * @param proxys +// */ +// export const genTomlConfig = (config: FrpConfig, proxys: Proxy[]) => { +// const proxyToml = proxys.map(m => { +// const rangePort = isRangePort(m); +// config.tlsConfigKeyFile = config.tlsConfigKeyFile.replace(/\\/g, "\\\\"); +// config.tlsConfigCertFile = config.tlsConfigCertFile.replace(/\\/g, "\\\\"); +// config.tlsConfigTrustedCaFile = config.tlsConfigTrustedCaFile.replace( +// /\\/g, +// "\\\\" +// ); +// let toml = `${ +// rangePort +// ? `{{- range $_, $v := parseNumberRangePair "${m.localPort}" "${m.remotePort}" }}` +// : "" +// } +// [[${ +// (m.type === "stcp" || m.type === "xtcp" || m.type === "sudp") && +// m.stcpModel === "visitors" +// ? "visitors" +// : "proxies" +// }]] +// ${rangePort ? "" : `name = "${m.name}"`} +// type = "${m.type}"\n`; +// +// switch (m.type) { +// case "tcp": +// case "udp": +// if (rangePort) { +// toml += `name = "${m.name}-{{ $v.First }}" +// localPort = {{ $v.First }} +// remotePort = {{ $v.Second }}\n`; +// } else { +// toml += `localIP = "${m.localIp}" +// localPort = ${m.localPort} +// remotePort = ${m.remotePort}\n`; +// } +// break; +// case "http": +// case "https": +// const customDomains = m.customDomains.filter(f1 => f1 !== ""); +// if (customDomains && customDomains.length > 0) { +// toml += `customDomains=[${m.customDomains.map(m => `"${m}"`)}]\n`; +// } +// if (m.subdomain) { +// toml += `subdomain="${m.subdomain}"\n`; +// } +// if (m.basicAuth) { +// toml += `httpUser = "${m.httpUser}" +// httpPassword = "${m.httpPassword}"\n`; +// } +// if (m.https2http) { +// toml += `[proxies.plugin] +// type = "https2http" +// localAddr = "${m.localIp}:${m.localPort}" +// +// crtPath = "${m.https2httpCaFile}" +// keyPath = "${m.https2httpKeyFile}"\n`; +// } else { +// toml += `localIP = "${m.localIp}" +// localPort = ${m.localPort}\n`; +// } +// +// break; +// case "xtcp": +// if (m.stcpModel === "visitors") { +// toml += `keepTunnelOpen = ${m.keepTunnelOpen}\n`; +// } +// case "stcp": +// case "sudp": +// if (m.stcpModel === "visitors") { +// // 访问者 +// toml += `serverName = "${m.serverName}" +// bindAddr = "${m.bindAddr}" +// bindPort = ${m.bindPort}\n`; +// if (m.fallbackTo) { +// toml += `fallbackTo = "${m.fallbackTo}" +// fallbackTimeoutMs = ${m.fallbackTimeoutMs || 500}\n`; +// } +// } else if (m.stcpModel === "visited") { +// // 被访问者 +// toml += `localIP = "${m.localIp}" +// localPort = ${m.localPort}\n`; +// } +// +// toml += `secretKey="${m.secretKey}"\n`; +// break; +// default: +// break; +// } +// +// if (rangePort) { +// toml += `{{- end }}\n`; +// } +// return toml; +// }); +// const toml = `serverAddr = "${config.serverAddr}" +// serverPort = ${config.serverPort} +// ${ +// config.authMethod === "token" +// ? `auth.method = "token" +// auth.token = "${config.authToken}"` +// : "" +// } +// ${ +// config.authMethod === "multiuser" +// ? `user = "${config.user}" +// metadatas.token = "${config.metaToken}"` +// : "" +// } +// log.to = "frpc.log" +// log.level = "${config.logLevel}" +// log.maxDays = ${config.logMaxDays} +// webServer.addr = "127.0.0.1" +// webServer.port = ${config.webPort} +// ${ +// config.transportProtocol +// ? `transport.protocol = "${config.transportProtocol}"` +// : "" +// } +// ${ +// config.transportPoolCount +// ? `transport.poolCount = ${config.transportPoolCount}` +// : "" +// } +// ${ +// config.transportDialServerTimeout +// ? `transport.dialServerTimeout = ${config.transportDialServerTimeout}` +// : "" +// } +// ${ +// config.transportDialServerKeepalive +// ? `transport.dialServerKeepalive = ${config.transportDialServerKeepalive}` +// : "" +// } +// ${ +// config.transportHeartbeatInterval +// ? `transport.heartbeatInterval = ${config.transportHeartbeatInterval}` +// : "" +// } +// ${ +// config.transportHeartbeatTimeout +// ? `transport.heartbeatTimeout = ${config.transportHeartbeatTimeout}` +// : "" +// } +// ${config.transportTcpMux ? `transport.tcpMux = ${config.transportTcpMux}` : ""} +// ${ +// config.transportTcpMux && config.transportTcpMuxKeepaliveInterval +// ? `transport.tcpMuxKeepaliveInterval = ${config.transportTcpMuxKeepaliveInterval}` +// : "" +// } +// transport.tls.enable = ${config.tlsConfigEnable} +// ${ +// config.tlsConfigEnable && config.tlsConfigCertFile +// ? `transport.tls.certFile = "${config.tlsConfigCertFile}"` +// : "" +// } +// ${ +// config.tlsConfigEnable && config.tlsConfigKeyFile +// ? `transport.tls.keyFile = "${config.tlsConfigKeyFile}"` +// : "" +// } +// ${ +// config.tlsConfigEnable && config.tlsConfigTrustedCaFile +// ? `transport.tls.trustedCaFile = "${config.tlsConfigTrustedCaFile}"` +// : "" +// } +// ${ +// config.tlsConfigEnable && config.tlsConfigServerName +// ? `transport.tls.serverName = "${config.tlsConfigServerName}"` +// : "" +// } +// ${ +// config.proxyConfigEnable +// ? `transport.proxyURL = "${config.proxyConfigProxyUrl}"` +// : "" +// } +// ${proxyToml.join("")}`; +// return toml; +// }; +// +// /** +// * 生成ini配置 +// * @param config +// * @param proxys +// */ +// export const genIniConfig = (config: FrpConfig, proxys: Proxy[]) => { +// const proxyIni = proxys.map(m => { +// const rangePort = isRangePort(m); +// let ini = `[${rangePort ? "range:" : ""}${m.name}] +// type = "${m.type}" +// `; +// switch (m.type) { +// case "tcp": +// case "udp": +// ini += ` +// local_ip = "${m.localIp}" +// local_port = ${m.localPort} +// remote_port = ${m.remotePort}\n`; +// break; +// case "http": +// ini += ` +// local_ip = "${m.localIp}" +// local_port = ${m.localPort} +// custom_domains=[${m.customDomains.map(m => `${m}`)}] +// subdomain="${m.subdomain}"\n`; +// if (m.basicAuth) { +// ini += ` +// httpUser = "${m.httpUser}" +// httpPassword = "${m.httpPassword}"\n`; +// } +// break; +// case "https": +// ini += ` +// custom_domains=[${m.customDomains.map(m => `${m}`)}] +// subdomain="${m.subdomain}"\n`; +// if (m.basicAuth) { +// ini += ` +// httpUser = "${m.httpUser}" +// httpPassword = "${m.httpPassword}"\n`; +// } +// if (m.https2http) { +// ini += ` +// plugin = https2http +// plugin_local_addr = ${m.localIp}:${m.localPort} +// plugin_crt_path = ${m.https2httpCaFile} +// plugin_key_path = ${m.https2httpKeyFile}\n`; +// } else { +// ini += ` +// local_ip = "${m.localIp}" +// local_port = ${m.localPort}\n`; +// } +// break; +// case "xtcp": +// if (m.stcpModel === "visitors") { +// ini += `keep_tunnel_open = ${m.keepTunnelOpen}\n`; +// } +// case "stcp": +// case "sudp": +// if (m.stcpModel === "visitors") { +// // 访问者 +// ini += ` +// role = visitor +// server_name = "${m.serverName}" +// bind_addr = "${m.bindAddr}" +// bind_port = ${m.bindPort}\n`; +// if (m.fallbackTo) { +// ini += ` +// fallback_to = ${m.fallbackTo} +// fallback_timeout_ms = ${m.fallbackTimeoutMs || 500}\n`; +// } +// } else if (m.stcpModel === "visited") { +// // 被访问者 +// ini += ` +// local_ip = "${m.localIp}" +// local_port = ${m.localPort}\n`; +// } +// ini += ` +// sk="${m.secretKey}"\n`; +// break; +// default: +// break; +// } +// +// return ini; +// }); +// const ini = ` +// [common] +// server_addr = ${config.serverAddr} +// server_port = ${config.serverPort} +// ${ +// config.authMethod === "token" +// ? ` +// authentication_method = ${config.authMethod} +// token = ${config.authToken}\n` +// : "" +// } +// ${ +// config.authMethod === "multiuser" +// ? ` +// user = ${config.user} +// meta_token = ${config.metaToken}\n` +// : "" +// } +// +// ${config.transportProtocol ? `protocol = ${config.transportProtocol}` : ""} +// ${config.transportPoolCount ? `pool_count = ${config.transportPoolCount}` : ""} +// ${ +// config.transportDialServerTimeout +// ? `dial_server_timeout = ${config.transportDialServerTimeout}` +// : "" +// } +// ${ +// config.transportDialServerKeepalive +// ? `dial_server_keepalive = ${config.transportDialServerKeepalive}` +// : "" +// } +// ${ +// config.transportHeartbeatInterval +// ? ` +// heartbeat_interval = ${config.transportHeartbeatInterval} +// ` +// : "" +// } +// ${ +// config.transportHeartbeatTimeout +// ? ` +// heartbeat_timeout = ${config.transportHeartbeatTimeout} +// ` +// : "" +// } +// ${config.transportTcpMux ? `transport.tcp_mux = ${config.transportTcpMux}` : ""} +// ${ +// config.transportTcpMux && config.transportTcpMuxKeepaliveInterval +// ? `tcp_mux_keepalive_interval = ${config.transportTcpMuxKeepaliveInterval}` +// : "" +// } +// ${ +// config.tlsConfigEnable && config.tlsConfigCertFile +// ? ` +// tls_cert_file = ${config.tlsConfigCertFile}\n` +// : "" +// } +// ${ +// config.tlsConfigEnable && config.tlsConfigKeyFile +// ? ` +// tls_key_file = ${config.tlsConfigKeyFile}\n` +// : "" +// } +// ${ +// config.tlsConfigEnable && config.tlsConfigTrustedCaFile +// ? ` +// tls_trusted_ca_file = ${config.tlsConfigTrustedCaFile}\n` +// : "" +// } +// ${ +// config.tlsConfigEnable && config.tlsConfigServerName +// ? ` +// tls_server_name = ${config.tlsConfigServerName}\n` +// : "" +// } +// +// ${ +// config.proxyConfigEnable +// ? ` +// http_proxy = "${config.proxyConfigProxyUrl}"\n` +// : "" +// } +// +// log_file = "frpc.log" +// log_level = ${config.logLevel} +// log_max_days = ${config.logMaxDays} +// admin_addr = 127.0.0.1 +// admin_port = ${config.webPort} +// tls_enable = ${config.tlsConfigEnable} +// +// +// ${proxyIni.join("")} +// `; +// return ini; +// }; +// +// /** +// * 生成配置文件 +// */ +// export const generateConfig = ( +// config: FrpConfig, +// callback: (configPath: string) => void +// ) => { +// listProxy((err3, proxys) => { +// if (err3) { +// logError(LogModule.FRP_CLIENT, `Failed to list proxies: ${err3.message}`); +// return; +// } +// +// const { currentVersion } = config; +// let filename = null; +// let configContent = ""; +// const filtered = proxys +// .map(m => { +// if (m.status == null || m.status == undefined) { +// m.status = true; +// } +// return m; +// }) +// .filter(f => f.status); +// +// if (currentVersion < 124395282) { +// // 版本小于v0.52.0 +// filename = "frp.ini"; +// configContent = genIniConfig(config, filtered); +// logInfo( +// LogModule.FRP_CLIENT, +// `Using INI format for configuration: ${filename}` +// ); +// } else { +// filename = "frp.toml"; +// configContent = genTomlConfig(config, filtered); +// logInfo( +// LogModule.FRP_CLIENT, +// `Using TOML format for configuration: ${filename}` +// ); +// } +// +// const configPath = path.join(app.getPath("userData"), filename); +// logInfo( +// LogModule.FRP_CLIENT, +// `Writing configuration to file: ${configPath}` +// ); +// +// fs.writeFile( +// configPath, // 配置文件目录 +// configContent, // 配置文件内容 +// { flag: "w" }, +// err => { +// if (err) { +// logError( +// LogModule.FRP_CLIENT, +// `Failed to write configuration file: ${err.message}` +// ); +// } else { +// logInfo( +// LogModule.FRP_CLIENT, +// `Configuration file written successfully: ${filename}` +// ); +// callback(filename); +// } +// } +// ); +// }); +// }; +// +// /** +// * 启动frpc子进程 +// * @param cwd +// * @param commandPath +// * @param configPath +// */ +// const startFrpcProcess = (commandPath: string, configPath: string) => { +// logInfo( +// LogModule.FRP_CLIENT, +// `Starting frpc process. Directory: ${app.getPath( +// "userData" +// )}, Command: ${commandPath}` +// ); +// +// const command = `${commandPath} -c ${configPath}`; +// frpcProcess = spawn(command, { +// cwd: app.getPath("userData"), +// shell: true +// }); +// runningCmd.commandPath = commandPath; +// runningCmd.configPath = configPath; +// +// frpcProcess.stdout.on("data", data => { +// logDebug(LogModule.FRP_CLIENT, `Frpc process output: ${data}`); +// }); +// +// frpcProcess.stdout.on("error", data => { +// logError(LogModule.FRP_CLIENT, `Frpc process error: ${data}`); +// stopFrpcProcess(() => {}); +// }); +// +// frpcStatusListener = setInterval(() => { +// const status = frpcProcessStatus(); +// logDebug( +// LogModule.FRP_CLIENT, +// `Monitoring frpc process status: ${status}, Listener ID: ${frpcStatusListener}` +// ); +// if (!status) { +// new Notification({ +// title: "Frpc Desktop", +// body: "Connection lost, please check the logs for details." +// }).show(); +// logError( +// LogModule.FRP_CLIENT, +// "Frpc process status check failed. Connection lost." +// ); +// clearInterval(frpcStatusListener); +// } +// }, 3000); +// }; +// +// /** +// * 重载frpc配置 +// */ +// export const reloadFrpcProcess = () => { +// if (frpcProcess && !frpcProcess.killed) { +// logDebug( +// LogModule.FRP_CLIENT, +// "Attempting to reload frpc process configuration." +// ); +// getConfig((err1, config) => { +// if (!err1) { +// if (config) { +// generateConfig(config, configPath => { +// const command = `${runningCmd.commandPath} reload -c ${configPath}`; +// logInfo( +// LogModule.FRP_CLIENT, +// `Reloading configuration: ${command}` +// ); +// exec( +// command, +// { +// cwd: app.getPath("userData"), +// shell: true +// }, +// (error, stdout, stderr) => { +// if (error) { +// logError( +// LogModule.FRP_CLIENT, +// `Error reloading configuration: ${error.message}` +// ); +// return; +// } +// logDebug( +// LogModule.FRP_CLIENT, +// `Configuration reload output: ${stdout}` +// ); +// if (stderr) { +// logWarn( +// LogModule.FRP_CLIENT, +// `Configuration reload warnings: ${stderr}` +// ); +// } +// } +// ); +// }); +// } else { +// logWarn(LogModule.FRP_CLIENT, "No configuration found to reload."); +// } +// } else { +// logError(LogModule.FRP_CLIENT, `Error getting configuration: ${err1}`); +// } +// }); +// } else { +// logDebug( +// LogModule.FRP_CLIENT, +// "frpc process is not running or has been killed." +// ); +// } +// }; +// +// /** +// * 停止frpc子进程 +// */ +// export const stopFrpcProcess = (callback?: () => void) => { +// if (frpcProcess) { +// treeKill(frpcProcess.pid, (error: Error) => { +// if (error) { +// logError( +// LogModule.FRP_CLIENT, +// `Failed to stop frpc process with pid: ${frpcProcess.pid}. Error: ${error.message}` +// ); +// callback(); +// } else { +// logInfo( +// LogModule.FRP_CLIENT, +// `Successfully stopped frpc process with pid: ${frpcProcess.pid}.` +// ); +// frpcProcess = null; +// clearInterval(frpcStatusListener); +// callback(); +// } +// }); +// } else { +// logWarn( +// LogModule.FRP_CLIENT, +// "Attempted to stop frpc process, but no process is running." +// ); +// logWarn(LogModule.FRP_CLIENT, "No frpc process to stop."); +// callback(); +// } +// }; +// +// /** +// * 获取frpc子进程状态 +// */ +// export const frpcProcessStatus = () => { +// if (!frpcProcess) { +// logDebug(LogModule.FRP_CLIENT, "frpc process is not running."); +// return false; +// } +// try { +// // 发送信号给进程,如果进程存在,会正常返回 +// process.kill(frpcProcess.pid, 0); +// logDebug( +// LogModule.FRP_CLIENT, +// `frpc process is running with pid: ${frpcProcess.pid}` +// ); +// return true; +// } catch (error) { +// // 进程不存在,抛出异常 +// logError( +// LogModule.FRP_CLIENT, +// `frpc process not found. Error: ${error.message}` +// ); +// return false; +// } +// }; +// +// /** +// * 启动frpc流程 +// * @param config +// */ +// export const startFrpWorkerProcess = async (config: FrpConfig) => { +// logInfo(LogModule.FRP_CLIENT, "Starting frpc worker process..."); +// getFrpcVersionWorkerPath(config.currentVersion, (frpcVersionPath: string) => { +// if (frpcVersionPath) { +// logInfo( +// LogModule.FRP_CLIENT, +// `Found frpc version path: ${frpcVersionPath}` +// ); +// generateConfig(config, configPath => { +// const platform = process.platform; +// if (platform === "win32") { +// logInfo(LogModule.FRP_CLIENT, "Starting frpc on Windows."); +// startFrpcProcess(path.join(frpcVersionPath, "frpc.exe"), configPath); +// } else { +// logInfo( +// LogModule.FRP_CLIENT, +// "Starting frpc on non-Windows platform." +// ); +// startFrpcProcess(path.join(frpcVersionPath, "frpc"), configPath); +// } +// }); +// } else { +// logError(LogModule.FRP_CLIENT, "frpc version path not found."); +// } +// }); +// }; +// +// export const initFrpcApi = () => { +// ipcMain.handle("frpc.running", async (event, args) => { +// logDebug(LogModule.FRP_CLIENT, "Checking if frpc process is running..."); +// return frpcProcessStatus(); +// }); +// +// ipcMain.on("frpc.start", async (event, args) => { +// logInfo(LogModule.FRP_CLIENT, "Received request to start frpc process."); +// getConfig((err1, config) => { +// if (!err1) { +// if (!config) { +// logWarn( +// LogModule.FRP_CLIENT, +// "Configuration not found. Prompting user to set configuration." +// ); +// event.reply( +// "Home.frpc.start.error.hook", +// "请先前往设置页面,修改配置后再启动" +// ); +// return; +// } +// if (!config.currentVersion) { +// logWarn( +// LogModule.FRP_CLIENT, +// "Current version not set in configuration. Prompting user." +// ); +// event.reply( +// "Home.frpc.start.error.hook", +// "请先前往设置页面,修改配置后再启动" +// ); +// return; +// } +// startFrpWorkerProcess(config); +// } else { +// logError(LogModule.FRP_CLIENT, `Error getting configuration: ${err1}`); +// } +// }); +// }); +// +// ipcMain.on("frpc.stop", () => { +// logInfo(LogModule.FRP_CLIENT, "Received request to stop frpc process."); +// if (frpcProcess && !frpcProcess.killed) { +// stopFrpcProcess(() => {}); +// } else { +// logWarn(LogModule.FRP_CLIENT, "No frpc process to stop."); +// } +// }); +// }; diff --git a/electron/api/github.ts b/electron/api/github.ts index d28b201..85115cd 100644 --- a/electron/api/github.ts +++ b/electron/api/github.ts @@ -1,590 +1,590 @@ -import { app, BrowserWindow, dialog, ipcMain, net } from "electron"; -import { - deleteVersionById, - getVersionById, - insertVersion, - listVersion -} from "../storage/version"; -import frpReleasesJson from "../json/frp-releases.json"; -import frpChecksums from "../json/frp_all_sha256_checksums.json"; -import { logDebug, logError, logInfo, LogModule, logWarn } from "../utils/log"; -// import { calculateFileChecksum, formatBytes } from "../utils/FileUtils"; - -const fs = require("fs"); -const path = require("path"); -const zlib = require("zlib"); -const { download } = require("electron-dl"); -const AdmZip = require("adm-zip"); - -const versionRelation = { - win32_x64: ["window", "amd64"], - win32_arm64: ["window", "arm64"], - win32_ia32: ["window", "386"], - darwin_arm64: ["darwin", "arm64"], - darwin_x64: ["darwin", "amd64"], - darwin_amd64: ["darwin", "amd64"], - linux_x64: ["linux", "amd64"], - linux_arm64: ["linux", "arm64"] -}; -const platform = process.platform; -const arch = process.arch; -let currArch = `${platform}_${arch}`; -const frpArch = versionRelation[currArch]; - -const unTarGZ = (tarGzPath: string, targetPath: string) => { - const tar = require("tar"); - const unzip = zlib.createGunzip(); - logInfo( - LogModule.APP, - `Starting to extract tar.gz: ${tarGzPath} to ${targetPath}` - ); - - const readStream = fs.createReadStream(tarGzPath); - if (!fs.existsSync(unzip)) { - fs.mkdirSync(targetPath, { recursive: true, mode: 0o777 }); - logInfo(LogModule.APP, `Created target directory: ${targetPath}`); - } - - readStream.on("error", err => { - logError(LogModule.APP, `Error reading tar.gz file: ${err.message}`); - }); - - readStream - .pipe(unzip) - .on("error", err => { - logError(LogModule.APP, `Error during gunzip: ${err.message}`); - }) - .pipe( - tar - .extract({ - cwd: targetPath, - filter: filePath => path.basename(filePath) === "frpc" - }) - .on("error", err => { - logError(LogModule.APP, `Error extracting tar file: ${err.message}`); - }) - ) - .on("finish", () => { - const frpcPath = path.join("frp", path.basename(tarGzPath, ".tar.gz")); - logInfo( - LogModule.APP, - `Extraction completed. Extracted directory: ${frpcPath}` - ); - }); - return path.join("frp", path.basename(tarGzPath, ".tar.gz")); -}; - -const unZip = (zipPath: string, targetPath: string) => { - if (!fs.existsSync(path.join(targetPath, path.basename(zipPath, ".zip")))) { - fs.mkdirSync(path.join(targetPath, path.basename(zipPath, ".zip")), { - recursive: true - }); - logInfo(LogModule.APP, `Created target directory: ${targetPath}`); - logInfo( - LogModule.APP, - `Created directory for zip extraction: ${path.basename(zipPath, ".zip")}` - ); - } - - logDebug( - LogModule.APP, - `Starting to unzip: ${zipPath} to target directory: ${targetPath}` - ); - logInfo(LogModule.APP, `Starting to extract zip file: ${zipPath}`); - - const zip = new AdmZip(zipPath); - try { - zip.extractAllTo(targetPath, true); // 第二个参数为 true,表示覆盖已存在的文件 - const frpcPath = path.join("frp", path.basename(zipPath, ".zip")); - logInfo( - LogModule.APP, - `Extraction completed. Extracted directory: ${frpcPath}` - ); - logDebug( - LogModule.APP, - `Unzip completed. Extracted directory: ${frpcPath}` - ); - return frpcPath; - } catch (error) { - logError(LogModule.APP, `Error extracting zip file: ${error.message}`); - } - - return null; -}; - -export const initGitHubApi = win => { - // 版本 - let versions: FrpVersion[] = []; - - const getVersionByGithubVersionId = versionId => { - logDebug(LogModule.APP, `Attempting to get version with ID: ${versionId}`); - const version = versions.find(f => f.id === versionId); - if (version) { - logInfo( - LogModule.APP, - `Version details ID:${version.id}, Name:${version.name}, Published At:${version.published_at}` - ); - } else { - logWarn(LogModule.APP, `No version found for ID: ${versionId}`); - } - return version; - }; - - const getVersionByAssetName = (assetName: string) => { - logDebug( - LogModule.APP, - `Attempting to get version with asset name: ${assetName}` - ); - const version = versions.find(f => - f.assets.some(asset => asset.name === assetName) - ); - if (version) { - logInfo( - LogModule.APP, - `Version details ID:${version.id}, Name:${version.name}, Published At:${version.published_at}` - ); - } else { - logWarn(LogModule.APP, `No version found for asset name: ${assetName}`); - } - return version; - }; - - const getAdaptiveAsset = versionId => { - const { assets } = getVersionByGithubVersionId(versionId); - if (!assets || assets.length === 0) { - logWarn(LogModule.GITHUB, `No assets found for version ID: ${versionId}`); - return null; - } - - const asset = assets.find(f => { - const a = frpArch; - if (a) { - const flag = a.every(item => f.name.includes(item)); - if (flag) { - logInfo( - LogModule.GITHUB, - `Found matching asset: ${f.name} for version ID: ${versionId}` - ); - } - return flag; - } - logWarn( - LogModule.GITHUB, - `No architecture match found for asset: ${f.name}` - ); - return false; - }); - - if (!asset) { - logError( - LogModule.GITHUB, - `No adaptive asset found for version ID: ${versionId}` - ); - } - return asset; - }; - - /** - * handle github api release json - * @param githubReleaseJsonStr jsonStr - * @returns versions - */ - const handleApiResponse = (githubReleaseJsonStr: string) => { - const downloadPath = path.join(app.getPath("userData"), "download"); - const frpPath = path.join(app.getPath("userData"), "frp"); - logInfo(LogModule.GITHUB, "Parsing GitHub release JSON response."); - - versions = JSON.parse(githubReleaseJsonStr); - if (versions) { - logInfo( - LogModule.GITHUB, - "Successfully parsed versions from GitHub response." - ); - - const returnVersionsData = versions - .filter(f => getAdaptiveAsset(f.id)) - .map(m => { - const asset = getAdaptiveAsset(m.id); - const download_count = m.assets.reduce( - (sum, item) => sum + item.download_count, - 0 - ); - if (asset) { - const absPath = path.join( - frpPath, - asset.name.replace(/(\.tar\.gz|\.zip)$/, "") - ); - m.absPath = absPath; - m.download_completed = fs.existsSync(absPath); - m.download_count = download_count; - m.size = formatBytes(asset.size); - logInfo( - LogModule.GITHUB, - `Asset found: ${asset.name}, download count: ${download_count}` - ); - } else { - logWarn(LogModule.GITHUB, `No asset found for version ID: ${m.id}`); - } - return m; - }); - logDebug( - LogModule.GITHUB, - `Retrieved FRP versions: ${JSON.stringify(returnVersionsData)}` - ); - return returnVersionsData; - } else { - logError( - LogModule.GITHUB, - "Failed to parse versions: No versions found in response." - ); - return []; - } - }; - - /** - * conventMirrorUrl - * @param mirror mirror - * @returns mirrorUrl - */ - const conventMirrorUrl = (mirror: string) => { - switch (mirror) { - case "github": - return { - api: "https://api.github.com", - asset: "https://github.com" - }; - default: - return { - api: "https://api.github.com", - asset: "https://github.com" - }; - } - }; - - /** - * 获取github上的frp所有版本 - */ - ipcMain.on("github.getFrpVersions", async (event, mirror: string) => { - const { api } = conventMirrorUrl(mirror); - const mirrorUrl = api; - logInfo(LogModule.GITHUB, `Requesting mirror URL: ${mirrorUrl}`); - const request = net.request({ - method: "get", - url: `${mirrorUrl}/repos/fatedier/frp/releases?page=1&per_page=1000` - }); - - let githubReleaseJsonStr = null; - request.on("response", response => { - logInfo( - LogModule.GITHUB, - `Received response with status code: ${response.statusCode}` - ); - let responseData: Buffer = Buffer.alloc(0); - response.on("data", (data: Buffer) => { - responseData = Buffer.concat([responseData, data]); - }); - response.on("end", () => { - if (response.statusCode === 200) { - githubReleaseJsonStr = responseData.toString(); - logInfo( - LogModule.GITHUB, - "Successfully retrieved GitHub release data." - ); - } else { - logWarn( - LogModule.GITHUB, - "Failed to retrieve data, using local JSON instead. Status code: " + - response.statusCode - ); - githubReleaseJsonStr = JSON.stringify(frpReleasesJson); - } - const versions = handleApiResponse(githubReleaseJsonStr); - event.reply("Download.frpVersionHook", versions); - }); - }); - - request.on("error", jerror => { - logError( - LogModule.GITHUB, - "Error occurred while requesting GitHub releases: " + error - ); - githubReleaseJsonStr = JSON.stringify(frpReleasesJson); - const versions = handleApiResponse(githubReleaseJsonStr); - event.reply("Download.frpVersionHook", versions); - }); - - request.end(); - }); - - const decompressFrp = (frpFilename: string, compressedFilePath: string) => { - const targetPath = path.resolve(path.join(app.getPath("userData"), "frp")); - const ext = path.extname(frpFilename); - let frpcVersionPath = ""; - try { - if (ext === ".zip") { - unZip( - // path.join( - // path.join(app.getPath("userData"), "download"), - // `${frpFilename}` - // ), - compressedFilePath, - targetPath - ); - logInfo(LogModule.APP, `Unzipped file to path: ${frpcVersionPath}`); - frpcVersionPath = path.join("frp", path.basename(frpFilename, ".zip")); - } else if (ext === ".gz" && frpFilename.includes(".tar.gz")) { - unTarGZ( - // path.join( - // path.join(app.getPath("userData"), "download"), - // `${frpFilename}` - // ), - compressedFilePath, - targetPath - ); - frpcVersionPath = path.join( - "frp", - path.basename(frpFilename, ".tar.gz") - ); - logInfo(LogModule.APP, `Untarred file to path: ${frpcVersionPath}`); - } - } catch (error) { - logError(LogModule.APP, `Error during extraction: ${error.message}`); - } - - return frpcVersionPath; - }; - - /** - * 下载请求 - */ - ipcMain.on("github.download", async (event, args) => { - const { versionId, mirror } = args; - const version = getVersionByGithubVersionId(versionId); - const asset = getAdaptiveAsset(versionId); - const { browser_download_url } = asset; - - let url = browser_download_url.replace( - "https://github.com", - conventMirrorUrl(mirror).asset - ); - - logDebug( - LogModule.GITHUB, - `Starting download for versionId: ${versionId}, mirror: ${mirror}, download URL: ${url}` - ); - - await download(BrowserWindow.getFocusedWindow(), url, { - filename: `${asset.name}`, - directory: path.join(app.getPath("userData"), "download"), - onProgress: progress => { - event.reply("Download.frpVersionDownloadOnProgress", { - id: versionId, - progress: progress - }); - logDebug( - LogModule.GITHUB, - `Download progress for versionId: ${versionId} is ${ - progress.percent * 100 - }%` - ); - }, - onCompleted: () => { - logInfo( - LogModule.GITHUB, - `Download completed for versionId: ${versionId}, asset: ${asset.name}` - ); - - const frpcVersionPath = decompressFrp( - asset.name, - path.join( - path.join(app.getPath("userData"), "download"), - `${asset.name}` - ) - ); - version["frpcVersionPath"] = frpcVersionPath; - insertVersion(version, (err, document) => { - if (!err) { - listVersion((err, doc) => { - event.reply("Config.versions.hook", { err, data: doc }); - event.reply("Download.frpVersionDownloadOnCompleted", versionId); - version.download_completed = true; - logInfo( - LogModule.GITHUB, - `Version ${versionId} has been inserted successfully.` - ); - }); - } else { - logError(LogModule.GITHUB, `Error inserting version: ${err}`); - } - }); - } - }); - }); - - /** - * 删除下载 - */ - ipcMain.on("github.deleteVersion", async (event, args) => { - const { absPath, id } = args; - logDebug( - LogModule.GITHUB, - `Attempting to delete version with ID: ${id} and path: ${absPath}` - ); - if (fs.existsSync(absPath)) { - // if (process.platform === 'darwin') { - // fs.unlinkSync(absPath.replace(/ /g, '\\ ')); - // }else{ - // fs.unlinkSync(absPath); - // } - fs.rmSync(absPath, { recursive: true, force: true }); - deleteVersionById(id, () => { - logInfo( - LogModule.GITHUB, - `Successfully deleted version with ID: ${id}` - ); - }); - } else { - logWarn( - LogModule.GITHUB, - `Version with ID: ${id} not found at path: ${absPath}` - ); - } - listVersion((err, doc) => { - if (err) { - logError(LogModule.GITHUB, `Error listing versions: ${err}`); - } else { - event.reply("Config.versions.hook", { err, data: doc }); - event.reply("Download.deleteVersion.hook", { - err: null, - data: "删除成功" - }); - } - }); - }); - - /** - * 获取最后版本 - */ - ipcMain.on("github.getFrpcDesktopLastVersions", async event => { - logInfo(LogModule.GITHUB, "Requesting the latest version from GitHub."); - const request = net.request({ - method: "get", - url: "https://api.github.com/repos/luckjiawei/frpc-desktop/releases/latest" - }); - request.on("response", response => { - let responseData: Buffer = Buffer.alloc(0); - response.on("data", (data: Buffer) => { - responseData = Buffer.concat([responseData, data]); - }); - response.on("end", () => { - try { - versions = JSON.parse(responseData.toString()); - logInfo( - LogModule.GITHUB, - "Successfully retrieved the latest version." - ); - event.reply("github.getFrpcDesktopLastVersionsHook", versions); - } catch (error) { - logError( - LogModule.GITHUB, - `Error parsing response data: ${error.message}` - ); - } - }); - }); - request.on("error", error => { - logError(LogModule.GITHUB, `Request error: ${error.message}`); - }); - request.end(); - }); - - ipcMain.on( - "download.importFrpFile", - async (event, filePath: string, targetPath: string) => { - const result = await dialog.showOpenDialog(win, { - properties: ["openFile"], - filters: [ - { name: "Frp 文件", extensions: ["tar.gz", "zip"] } // 允许选择的文件类型,分开后缀以确保可以选择 - ] - }); - if (result.canceled) { - logWarn(LogModule.APP, "Import canceled by user."); - logWarn(LogModule.GITHUB, "User canceled the file import operation."); - return; - } else { - const filePath = result.filePaths[0]; - // const fileExtension = path.extname(filePath); - logInfo(LogModule.APP, `User selected file: ${filePath}`); - const checksum = calculateFileChecksum(filePath); - logInfo(LogModule.APP, `Calculated checksum for the file: ${checksum}`); - const frpName = frpChecksums[checksum]; - if (frpName) { - logInfo(LogModule.APP, `FRP file name found: ${frpName}`); - if (frpArch.every(item => frpName.includes(item))) { - logInfo( - LogModule.APP, - `Architecture matches for FRP file: ${frpName}` - ); - const version = getVersionByAssetName(frpName); - getVersionById(version.id, (err, existingVersion) => { - if (!err && existingVersion) { - logInfo( - LogModule.APP, - `Version already exists: ${JSON.stringify(existingVersion)}` - ); - event.reply("Download.importFrpFile.hook", { - success: false, - data: `导入失败,版本已存在` - }); - return; // 终止后续执行 - } - - const frpcVersionPath = decompressFrp(frpName, filePath); - logInfo( - LogModule.APP, - `Successfully decompressed FRP file: ${frpName} to path: ${frpcVersionPath}` - ); - version["frpcVersionPath"] = frpcVersionPath; - insertVersion(version, (err, document) => { - if (!err) { - listVersion((err, doc) => { - event.reply("Config.versions.hook", { err, data: doc }); - version.download_completed = true; - event.reply("Download.importFrpFile.hook", { - success: true, - data: `导入成功` - }); - }); - } else { - logError(LogModule.APP, `Error inserting version: ${err}`); - event.reply("Download.importFrpFile.hook", { - success: true, - data: `导入失败,未知错误` - }); - } - }); - }); - } else { - logWarn( - LogModule.APP, - `Architecture does not match for FRP file: ${frpName}` - ); - event.reply("Download.importFrpFile.hook", { - success: false, - data: `导入失败,所选 frp 架构与操作系统不符` - }); - } - } else { - logWarn( - LogModule.APP, - `No matching FRP file name found for checksum: ${checksum}` - ); - event.reply("Download.importFrpFile.hook", { - success: false, - data: `导入失败,无法识别文件` - }); - } - } - } - ); -}; +// import { app, BrowserWindow, dialog, ipcMain, net } from "electron"; +// import { +// deleteVersionById, +// getVersionById, +// insertVersion, +// listVersion +// } from "../storage/version"; +// import frpReleasesJson from "../json/frp-releases.json"; +// import frpChecksums from "../json/frp_all_sha256_checksums.json"; +// import { logDebug, logError, logInfo, LogModule, logWarn } from "../utils/log"; +// // import { calculateFileChecksum, formatBytes } from "../utils/FileUtils"; +// +// const fs = require("fs"); +// const path = require("path"); +// const zlib = require("zlib"); +// const { download } = require("electron-dl"); +// const AdmZip = require("adm-zip"); +// +// const versionRelation = { +// win32_x64: ["window", "amd64"], +// win32_arm64: ["window", "arm64"], +// win32_ia32: ["window", "386"], +// darwin_arm64: ["darwin", "arm64"], +// darwin_x64: ["darwin", "amd64"], +// darwin_amd64: ["darwin", "amd64"], +// linux_x64: ["linux", "amd64"], +// linux_arm64: ["linux", "arm64"] +// }; +// const platform = process.platform; +// const arch = process.arch; +// let currArch = `${platform}_${arch}`; +// const frpArch = versionRelation[currArch]; +// +// const unTarGZ = (tarGzPath: string, targetPath: string) => { +// const tar = require("tar"); +// const unzip = zlib.createGunzip(); +// logInfo( +// LogModule.APP, +// `Starting to extract tar.gz: ${tarGzPath} to ${targetPath}` +// ); +// +// const readStream = fs.createReadStream(tarGzPath); +// if (!fs.existsSync(unzip)) { +// fs.mkdirSync(targetPath, { recursive: true, mode: 0o777 }); +// logInfo(LogModule.APP, `Created target directory: ${targetPath}`); +// } +// +// readStream.on("error", err => { +// logError(LogModule.APP, `Error reading tar.gz file: ${err.message}`); +// }); +// +// readStream +// .pipe(unzip) +// .on("error", err => { +// logError(LogModule.APP, `Error during gunzip: ${err.message}`); +// }) +// .pipe( +// tar +// .extract({ +// cwd: targetPath, +// filter: filePath => path.basename(filePath) === "frpc" +// }) +// .on("error", err => { +// logError(LogModule.APP, `Error extracting tar file: ${err.message}`); +// }) +// ) +// .on("finish", () => { +// const frpcPath = path.join("frp", path.basename(tarGzPath, ".tar.gz")); +// logInfo( +// LogModule.APP, +// `Extraction completed. Extracted directory: ${frpcPath}` +// ); +// }); +// return path.join("frp", path.basename(tarGzPath, ".tar.gz")); +// }; +// +// const unZip = (zipPath: string, targetPath: string) => { +// if (!fs.existsSync(path.join(targetPath, path.basename(zipPath, ".zip")))) { +// fs.mkdirSync(path.join(targetPath, path.basename(zipPath, ".zip")), { +// recursive: true +// }); +// logInfo(LogModule.APP, `Created target directory: ${targetPath}`); +// logInfo( +// LogModule.APP, +// `Created directory for zip extraction: ${path.basename(zipPath, ".zip")}` +// ); +// } +// +// logDebug( +// LogModule.APP, +// `Starting to unzip: ${zipPath} to target directory: ${targetPath}` +// ); +// logInfo(LogModule.APP, `Starting to extract zip file: ${zipPath}`); +// +// const zip = new AdmZip(zipPath); +// try { +// zip.extractAllTo(targetPath, true); // 第二个参数为 true,表示覆盖已存在的文件 +// const frpcPath = path.join("frp", path.basename(zipPath, ".zip")); +// logInfo( +// LogModule.APP, +// `Extraction completed. Extracted directory: ${frpcPath}` +// ); +// logDebug( +// LogModule.APP, +// `Unzip completed. Extracted directory: ${frpcPath}` +// ); +// return frpcPath; +// } catch (error) { +// logError(LogModule.APP, `Error extracting zip file: ${error.message}`); +// } +// +// return null; +// }; +// +// export const initGitHubApi = win => { +// // 版本 +// let versions: FrpVersion[] = []; +// +// const getVersionByGithubVersionId = versionId => { +// logDebug(LogModule.APP, `Attempting to get version with ID: ${versionId}`); +// const version = versions.find(f => f.id === versionId); +// if (version) { +// logInfo( +// LogModule.APP, +// `Version details ID:${version.id}, Name:${version.name}, Published At:${version.published_at}` +// ); +// } else { +// logWarn(LogModule.APP, `No version found for ID: ${versionId}`); +// } +// return version; +// }; +// +// const getVersionByAssetName = (assetName: string) => { +// logDebug( +// LogModule.APP, +// `Attempting to get version with asset name: ${assetName}` +// ); +// const version = versions.find(f => +// f.assets.some(asset => asset.name === assetName) +// ); +// if (version) { +// logInfo( +// LogModule.APP, +// `Version details ID:${version.id}, Name:${version.name}, Published At:${version.published_at}` +// ); +// } else { +// logWarn(LogModule.APP, `No version found for asset name: ${assetName}`); +// } +// return version; +// }; +// +// const getAdaptiveAsset = versionId => { +// const { assets } = getVersionByGithubVersionId(versionId); +// if (!assets || assets.length === 0) { +// logWarn(LogModule.GITHUB, `No assets found for version ID: ${versionId}`); +// return null; +// } +// +// const asset = assets.find(f => { +// const a = frpArch; +// if (a) { +// const flag = a.every(item => f.name.includes(item)); +// if (flag) { +// logInfo( +// LogModule.GITHUB, +// `Found matching asset: ${f.name} for version ID: ${versionId}` +// ); +// } +// return flag; +// } +// logWarn( +// LogModule.GITHUB, +// `No architecture match found for asset: ${f.name}` +// ); +// return false; +// }); +// +// if (!asset) { +// logError( +// LogModule.GITHUB, +// `No adaptive asset found for version ID: ${versionId}` +// ); +// } +// return asset; +// }; +// +// /** +// * handle github api release json +// * @param githubReleaseJsonStr jsonStr +// * @returns versions +// */ +// const handleApiResponse = (githubReleaseJsonStr: string) => { +// const downloadPath = path.join(app.getPath("userData"), "download"); +// const frpPath = path.join(app.getPath("userData"), "frp"); +// logInfo(LogModule.GITHUB, "Parsing GitHub release JSON response."); +// +// versions = JSON.parse(githubReleaseJsonStr); +// if (versions) { +// logInfo( +// LogModule.GITHUB, +// "Successfully parsed versions from GitHub response." +// ); +// +// const returnVersionsData = versions +// .filter(f => getAdaptiveAsset(f.id)) +// .map(m => { +// const asset = getAdaptiveAsset(m.id); +// const download_count = m.assets.reduce( +// (sum, item) => sum + item.download_count, +// 0 +// ); +// if (asset) { +// const absPath = path.join( +// frpPath, +// asset.name.replace(/(\.tar\.gz|\.zip)$/, "") +// ); +// m.absPath = absPath; +// m.download_completed = fs.existsSync(absPath); +// m.download_count = download_count; +// m.size = formatBytes(asset.size); +// logInfo( +// LogModule.GITHUB, +// `Asset found: ${asset.name}, download count: ${download_count}` +// ); +// } else { +// logWarn(LogModule.GITHUB, `No asset found for version ID: ${m.id}`); +// } +// return m; +// }); +// logDebug( +// LogModule.GITHUB, +// `Retrieved FRP versions: ${JSON.stringify(returnVersionsData)}` +// ); +// return returnVersionsData; +// } else { +// logError( +// LogModule.GITHUB, +// "Failed to parse versions: No versions found in response." +// ); +// return []; +// } +// }; +// +// /** +// * conventMirrorUrl +// * @param mirror mirror +// * @returns mirrorUrl +// */ +// const conventMirrorUrl = (mirror: string) => { +// switch (mirror) { +// case "github": +// return { +// api: "https://api.github.com", +// asset: "https://github.com" +// }; +// default: +// return { +// api: "https://api.github.com", +// asset: "https://github.com" +// }; +// } +// }; +// +// /** +// * 获取github上的frp所有版本 +// */ +// ipcMain.on("github.getFrpVersions", async (event, mirror: string) => { +// const { api } = conventMirrorUrl(mirror); +// const mirrorUrl = api; +// logInfo(LogModule.GITHUB, `Requesting mirror URL: ${mirrorUrl}`); +// const request = net.request({ +// method: "get", +// url: `${mirrorUrl}/repos/fatedier/frp/releases?page=1&per_page=1000` +// }); +// +// let githubReleaseJsonStr = null; +// request.on("response", response => { +// logInfo( +// LogModule.GITHUB, +// `Received response with status code: ${response.statusCode}` +// ); +// let responseData: Buffer = Buffer.alloc(0); +// response.on("data", (data: Buffer) => { +// responseData = Buffer.concat([responseData, data]); +// }); +// response.on("end", () => { +// if (response.statusCode === 200) { +// githubReleaseJsonStr = responseData.toString(); +// logInfo( +// LogModule.GITHUB, +// "Successfully retrieved GitHub release data." +// ); +// } else { +// logWarn( +// LogModule.GITHUB, +// "Failed to retrieve data, using local JSON instead. Status code: " + +// response.statusCode +// ); +// githubReleaseJsonStr = JSON.stringify(frpReleasesJson); +// } +// const versions = handleApiResponse(githubReleaseJsonStr); +// event.reply("Download.frpVersionHook", versions); +// }); +// }); +// +// request.on("error", jerror => { +// logError( +// LogModule.GITHUB, +// "Error occurred while requesting GitHub releases: " + error +// ); +// githubReleaseJsonStr = JSON.stringify(frpReleasesJson); +// const versions = handleApiResponse(githubReleaseJsonStr); +// event.reply("Download.frpVersionHook", versions); +// }); +// +// request.end(); +// }); +// +// const decompressFrp = (frpFilename: string, compressedFilePath: string) => { +// const targetPath = path.resolve(path.join(app.getPath("userData"), "frp")); +// const ext = path.extname(frpFilename); +// let frpcVersionPath = ""; +// try { +// if (ext === ".zip") { +// unZip( +// // path.join( +// // path.join(app.getPath("userData"), "download"), +// // `${frpFilename}` +// // ), +// compressedFilePath, +// targetPath +// ); +// logInfo(LogModule.APP, `Unzipped file to path: ${frpcVersionPath}`); +// frpcVersionPath = path.join("frp", path.basename(frpFilename, ".zip")); +// } else if (ext === ".gz" && frpFilename.includes(".tar.gz")) { +// unTarGZ( +// // path.join( +// // path.join(app.getPath("userData"), "download"), +// // `${frpFilename}` +// // ), +// compressedFilePath, +// targetPath +// ); +// frpcVersionPath = path.join( +// "frp", +// path.basename(frpFilename, ".tar.gz") +// ); +// logInfo(LogModule.APP, `Untarred file to path: ${frpcVersionPath}`); +// } +// } catch (error) { +// logError(LogModule.APP, `Error during extraction: ${error.message}`); +// } +// +// return frpcVersionPath; +// }; +// +// /** +// * 下载请求 +// */ +// ipcMain.on("github.download", async (event, args) => { +// const { versionId, mirror } = args; +// const version = getVersionByGithubVersionId(versionId); +// const asset = getAdaptiveAsset(versionId); +// const { browser_download_url } = asset; +// +// let url = browser_download_url.replace( +// "https://github.com", +// conventMirrorUrl(mirror).asset +// ); +// +// logDebug( +// LogModule.GITHUB, +// `Starting download for versionId: ${versionId}, mirror: ${mirror}, download URL: ${url}` +// ); +// +// await download(BrowserWindow.getFocusedWindow(), url, { +// filename: `${asset.name}`, +// directory: path.join(app.getPath("userData"), "download"), +// onProgress: progress => { +// event.reply("Download.frpVersionDownloadOnProgress", { +// id: versionId, +// progress: progress +// }); +// logDebug( +// LogModule.GITHUB, +// `Download progress for versionId: ${versionId} is ${ +// progress.percent * 100 +// }%` +// ); +// }, +// onCompleted: () => { +// logInfo( +// LogModule.GITHUB, +// `Download completed for versionId: ${versionId}, asset: ${asset.name}` +// ); +// +// const frpcVersionPath = decompressFrp( +// asset.name, +// path.join( +// path.join(app.getPath("userData"), "download"), +// `${asset.name}` +// ) +// ); +// version["frpcVersionPath"] = frpcVersionPath; +// insertVersion(version, (err, document) => { +// if (!err) { +// listVersion((err, doc) => { +// event.reply("Config.versions.hook", { err, data: doc }); +// event.reply("Download.frpVersionDownloadOnCompleted", versionId); +// version.download_completed = true; +// logInfo( +// LogModule.GITHUB, +// `Version ${versionId} has been inserted successfully.` +// ); +// }); +// } else { +// logError(LogModule.GITHUB, `Error inserting version: ${err}`); +// } +// }); +// } +// }); +// }); +// +// /** +// * 删除下载 +// */ +// ipcMain.on("github.deleteVersion", async (event, args) => { +// const { absPath, id } = args; +// logDebug( +// LogModule.GITHUB, +// `Attempting to delete version with ID: ${id} and path: ${absPath}` +// ); +// if (fs.existsSync(absPath)) { +// // if (process.platform === 'darwin') { +// // fs.unlinkSync(absPath.replace(/ /g, '\\ ')); +// // }else{ +// // fs.unlinkSync(absPath); +// // } +// fs.rmSync(absPath, { recursive: true, force: true }); +// deleteVersionById(id, () => { +// logInfo( +// LogModule.GITHUB, +// `Successfully deleted version with ID: ${id}` +// ); +// }); +// } else { +// logWarn( +// LogModule.GITHUB, +// `Version with ID: ${id} not found at path: ${absPath}` +// ); +// } +// listVersion((err, doc) => { +// if (err) { +// logError(LogModule.GITHUB, `Error listing versions: ${err}`); +// } else { +// event.reply("Config.versions.hook", { err, data: doc }); +// event.reply("Download.deleteVersion.hook", { +// err: null, +// data: "删除成功" +// }); +// } +// }); +// }); +// +// /** +// * 获取最后版本 +// */ +// ipcMain.on("github.getFrpcDesktopLastVersions", async event => { +// logInfo(LogModule.GITHUB, "Requesting the latest version from GitHub."); +// const request = net.request({ +// method: "get", +// url: "https://api.github.com/repos/luckjiawei/frpc-desktop/releases/latest" +// }); +// request.on("response", response => { +// let responseData: Buffer = Buffer.alloc(0); +// response.on("data", (data: Buffer) => { +// responseData = Buffer.concat([responseData, data]); +// }); +// response.on("end", () => { +// try { +// versions = JSON.parse(responseData.toString()); +// logInfo( +// LogModule.GITHUB, +// "Successfully retrieved the latest version." +// ); +// event.reply("github.getFrpcDesktopLastVersionsHook", versions); +// } catch (error) { +// logError( +// LogModule.GITHUB, +// `Error parsing response data: ${error.message}` +// ); +// } +// }); +// }); +// request.on("error", error => { +// logError(LogModule.GITHUB, `Request error: ${error.message}`); +// }); +// request.end(); +// }); +// +// ipcMain.on( +// "download.importFrpFile", +// async (event, filePath: string, targetPath: string) => { +// const result = await dialog.showOpenDialog(win, { +// properties: ["openFile"], +// filters: [ +// { name: "Frp 文件", extensions: ["tar.gz", "zip"] } // 允许选择的文件类型,分开后缀以确保可以选择 +// ] +// }); +// if (result.canceled) { +// logWarn(LogModule.APP, "Import canceled by user."); +// logWarn(LogModule.GITHUB, "User canceled the file import operation."); +// return; +// } else { +// const filePath = result.filePaths[0]; +// // const fileExtension = path.extname(filePath); +// logInfo(LogModule.APP, `User selected file: ${filePath}`); +// const checksum = calculateFileChecksum(filePath); +// logInfo(LogModule.APP, `Calculated checksum for the file: ${checksum}`); +// const frpName = frpChecksums[checksum]; +// if (frpName) { +// logInfo(LogModule.APP, `FRP file name found: ${frpName}`); +// if (frpArch.every(item => frpName.includes(item))) { +// logInfo( +// LogModule.APP, +// `Architecture matches for FRP file: ${frpName}` +// ); +// const version = getVersionByAssetName(frpName); +// getVersionById(version.id, (err, existingVersion) => { +// if (!err && existingVersion) { +// logInfo( +// LogModule.APP, +// `Version already exists: ${JSON.stringify(existingVersion)}` +// ); +// event.reply("Download.importFrpFile.hook", { +// success: false, +// data: `导入失败,版本已存在` +// }); +// return; // 终止后续执行 +// } +// +// const frpcVersionPath = decompressFrp(frpName, filePath); +// logInfo( +// LogModule.APP, +// `Successfully decompressed FRP file: ${frpName} to path: ${frpcVersionPath}` +// ); +// version["frpcVersionPath"] = frpcVersionPath; +// insertVersion(version, (err, document) => { +// if (!err) { +// listVersion((err, doc) => { +// event.reply("Config.versions.hook", { err, data: doc }); +// version.download_completed = true; +// event.reply("Download.importFrpFile.hook", { +// success: true, +// data: `导入成功` +// }); +// }); +// } else { +// logError(LogModule.APP, `Error inserting version: ${err}`); +// event.reply("Download.importFrpFile.hook", { +// success: true, +// data: `导入失败,未知错误` +// }); +// } +// }); +// }); +// } else { +// logWarn( +// LogModule.APP, +// `Architecture does not match for FRP file: ${frpName}` +// ); +// event.reply("Download.importFrpFile.hook", { +// success: false, +// data: `导入失败,所选 frp 架构与操作系统不符` +// }); +// } +// } else { +// logWarn( +// LogModule.APP, +// `No matching FRP file name found for checksum: ${checksum}` +// ); +// event.reply("Download.importFrpFile.hook", { +// success: false, +// data: `导入失败,无法识别文件` +// }); +// } +// } +// } +// ); +// }; diff --git a/electron/api/update.ts b/electron/api/update.ts index 743aa73..485f9b8 100644 --- a/electron/api/update.ts +++ b/electron/api/update.ts @@ -1,81 +1,81 @@ -import {app, dialog, autoUpdater, BrowserWindow} from "electron"; - -const log = require('electron-log'); - - -export const initUpdaterApi = (win: BrowserWindow) => { - //更新测试打开 - Object.defineProperty(app, 'isPackaged', { - get() { - return true; - } - }); - const server = 'https://hazel-git-master-uiluck.vercel.app' - let packageName = null - const platform = process.platform; - const arch = process.arch; - switch (platform) { - case "darwin": - if (arch == "arm64") { - packageName = "darwin_arm64"; - } else { - packageName = "darwin"; - } - break; - case "win32": - packageName = "exe"; - break; - case "linux": - packageName = "AppImage"; - if (arch == "arm64") { - packageName = "AppImage_arm64"; - } else { - packageName = "AppImage"; - } - break; - } - const url = `${server}/update/${packageName}/${app.getVersion()}` - log.info(`开启自动更新 ${url}`); - autoUpdater.setFeedURL({url: url}) - - autoUpdater.on('checking-for-update', () => { - log.info("正在检查更新") - }) - - autoUpdater.on('update-available', (event, info) => { - log.info(`发现新版本`) - }) - - autoUpdater.on('update-not-available', () => { - log.info('没有可用的更新') - - }) - - autoUpdater.on('error', (err) => { - log.error(`更新错误:${err.message}`) - - }) - - autoUpdater.on('update-downloaded', () => { - dialog.showMessageBox({ - type: 'info', - title: '应用更新', - message: '发现新版本,是否更新?', - buttons: ['是', '否'] - }).then((buttonIndex) => { - if (buttonIndex.response == 0) { //选择是,则退出程序,安装新版本 - autoUpdater.quitAndInstall() - app.quit() - } - }) - }) - - // setInterval(() => { - // log.initialize("定时检查更新") - // // autoUpdater.checkForUpdates(); - // }, 60000) - autoUpdater.checkForUpdates(); - log.info("手动检查更新一次") - - -} +// import {app, dialog, autoUpdater, BrowserWindow} from "electron"; +// +// const log = require('electron-log'); +// +// +// export const initUpdaterApi = (win: BrowserWindow) => { +// //更新测试打开 +// Object.defineProperty(app, 'isPackaged', { +// get() { +// return true; +// } +// }); +// const server = 'https://hazel-git-master-uiluck.vercel.app' +// let packageName = null +// const platform = process.platform; +// const arch = process.arch; +// switch (platform) { +// case "darwin": +// if (arch == "arm64") { +// packageName = "darwin_arm64"; +// } else { +// packageName = "darwin"; +// } +// break; +// case "win32": +// packageName = "exe"; +// break; +// case "linux": +// packageName = "AppImage"; +// if (arch == "arm64") { +// packageName = "AppImage_arm64"; +// } else { +// packageName = "AppImage"; +// } +// break; +// } +// const url = `${server}/update/${packageName}/${app.getVersion()}` +// log.info(`开启自动更新 ${url}`); +// autoUpdater.setFeedURL({url: url}) +// +// autoUpdater.on('checking-for-update', () => { +// log.info("正在检查更新") +// }) +// +// autoUpdater.on('update-available', (event, info) => { +// log.info(`发现新版本`) +// }) +// +// autoUpdater.on('update-not-available', () => { +// log.info('没有可用的更新') +// +// }) +// +// autoUpdater.on('error', (err) => { +// log.error(`更新错误:${err.message}`) +// +// }) +// +// autoUpdater.on('update-downloaded', () => { +// dialog.showMessageBox({ +// type: 'info', +// title: '应用更新', +// message: '发现新版本,是否更新?', +// buttons: ['是', '否'] +// }).then((buttonIndex) => { +// if (buttonIndex.response == 0) { //选择是,则退出程序,安装新版本 +// autoUpdater.quitAndInstall() +// app.quit() +// } +// }) +// }) +// +// // setInterval(() => { +// // log.initialize("定时检查更新") +// // // autoUpdater.checkForUpdates(); +// // }, 60000) +// autoUpdater.checkForUpdates(); +// log.info("手动检查更新一次") +// +// +// } diff --git a/electron/controller/ConfigController.ts b/electron/controller/ConfigController.ts index 8cf9cfa..4c40a72 100644 --- a/electron/controller/ConfigController.ts +++ b/electron/controller/ConfigController.ts @@ -5,6 +5,7 @@ import PathUtils from "../utils/PathUtils"; import fs from "fs"; import FrpcProcessService from "../service/FrpcProcessService"; import SystemService from "../service/SystemService"; +import moment from "moment"; class ConfigController extends BaseController { private readonly _serverService: ServerService; @@ -70,8 +71,11 @@ class ConfigController extends BaseController { exportConfig(req: ControllerParam) { this._systemService.openDirectory().then(folder => { - this._serverService.genTomlConfig(folder.filePaths[0]).then(() => { - req.event.reply(req.channel, success()); + const path = `${folder.filePaths[0]}/frpc-${moment(new Date()).format( + "YYYYMMDDhhmmss" + )}.toml`; + this._serverService.genTomlConfig(path).then(() => { + req.event.reply(req.channel, success(path)); }); }); } diff --git a/electron/controller/ProxyController.ts b/electron/controller/ProxyController.ts index e55bca4..53ec86d 100644 --- a/electron/controller/ProxyController.ts +++ b/electron/controller/ProxyController.ts @@ -1,13 +1,13 @@ import BaseController from "./BaseController"; import ProxyService from "../service/ProxyService"; import { success } from "../utils/response"; -import ProxyDao from "../dao/ProxyDao"; +import ProxyRepository from "../repository/ProxyRepository"; class ProxyController extends BaseController { private readonly _proxyService: ProxyService; - private readonly _proxyDao: ProxyDao; + private readonly _proxyDao: ProxyRepository; - constructor(proxyService: ProxyService, proxyDao: ProxyDao) { + constructor(proxyService: ProxyService, proxyDao: ProxyRepository) { super(); this._proxyService = proxyService; this._proxyDao = proxyDao; diff --git a/electron/controller/VersionController.ts b/electron/controller/VersionController.ts index 415f6aa..6904228 100644 --- a/electron/controller/VersionController.ts +++ b/electron/controller/VersionController.ts @@ -1,13 +1,13 @@ import BaseController from "./BaseController"; import VersionService from "../service/VersionService"; import { fail, success } from "../utils/response"; -import VersionDao from "../dao/VersionDao"; +import VersionRepository from "../repository/VersionRepository"; class VersionController extends BaseController { private readonly _versionService: VersionService; - private readonly _versionDao: VersionDao; + private readonly _versionDao: VersionRepository; - constructor(versionService: VersionService, versionDao: VersionDao) { + constructor(versionService: VersionService, versionDao: VersionRepository) { super(); this._versionService = versionService; this._versionDao = versionDao; diff --git a/electron/core/BeanFactory.ts b/electron/core/BeanFactory.ts index f5fbb1c..01949ff 100644 --- a/electron/core/BeanFactory.ts +++ b/electron/core/BeanFactory.ts @@ -1,13 +1,23 @@ -/** - * todo DI - */ +import Logger from "./Logger"; + class BeanFactory { private static _beans: Map = new Map(); - private static registerBean(name: string, instance: any): void { - if (!this._beans.has(name)) { - this._beans.set(name, instance); + static registerBean(clazz: Function, beanName?: string): void { + if (!beanName) { + beanName = this.getBeanName(clazz.name); } + if (this.hasBean(beanName)) { + return; + } + const instance = new (clazz as any)(); + + this._beans.set(beanName, instance); + } + + public static setBean(name: string, bean: T): void { + this._beans.set(name, bean); + Logger.info(`register bean ${name} ${bean}`); } public static getBean(name: string): T { @@ -21,6 +31,14 @@ class BeanFactory { public static clear(): void { this._beans.clear(); } + + public static getBeanName(className: string) { + return className.charAt(0).toLowerCase() + className.slice(1); + } } + + + + export default BeanFactory; diff --git a/electron/core/GlobalConstant.ts b/electron/core/GlobalConstant.ts index fa08158..a803be9 100644 --- a/electron/core/GlobalConstant.ts +++ b/electron/core/GlobalConstant.ts @@ -1,6 +1,6 @@ +import { app } from "electron"; class GlobalConstant { - public static APP_NAME = "Frpc Desktop"; public static ZIP_EXT = ".zip"; public static GZ_EXT = ".gz"; public static TAR_GZ_EXT = ".tar.gz"; diff --git a/electron/core/IpcRouter.ts b/electron/core/IpcRouter.ts index 3d2e53c..f324eb1 100644 --- a/electron/core/IpcRouter.ts +++ b/electron/core/IpcRouter.ts @@ -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 = { SERVER: { @@ -133,119 +116,3 @@ export const listeners: Listeners = { channel: "frpcProcess:watchFrpcLog" } }; - -class IpcRouterConfigurate { - ipcRouters: Array; - private readonly _beans: Map = new Map(); - 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; diff --git a/electron/core/Logger.ts b/electron/core/Logger.ts new file mode 100644 index 0000000..fd4622b --- /dev/null +++ b/electron/core/Logger.ts @@ -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; \ No newline at end of file diff --git a/electron/core/annotation/Component.ts b/electron/core/annotation/Component.ts new file mode 100644 index 0000000..5db82d3 --- /dev/null +++ b/electron/core/annotation/Component.ts @@ -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); +// // }; +// // } diff --git a/electron/core/annotation/Resource.ts b/electron/core/annotation/Resource.ts new file mode 100644 index 0000000..dc56167 --- /dev/null +++ b/electron/core/annotation/Resource.ts @@ -0,0 +1,5 @@ +// export default function Resource(beanName?: string): PropertyDecorator { +// return function (target: Object, propertyKey: string | symbol) { +// console.log(target, propertyKey); +// }; +// } diff --git a/electron/main/index.ts b/electron/main/index.ts index 4985cb4..0078b93 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -10,14 +10,25 @@ import { } from "electron"; import { release } from "node:os"; import node_path, { join } from "node:path"; -import { initGitHubApi } from "../api/github"; -import { initConfigApi } from "../api/config"; -import { startFrpWorkerProcess, stopFrpcProcess } from "../api/frpc"; -import { initFileApi } from "../api/file"; -import { getConfig } from "../storage/config"; -import { initLog, logError, logInfo, LogModule } from "../utils/log"; -import { maskSensitiveData } from "../utils/desensitize"; -import IpcRouterConfigurate from "../core/IpcRouter"; +import BeanFactory from "../core/BeanFactory"; +import ServerRepository from "../repository/ServerRepository"; +import VersionRepository from "../repository/VersionRepository"; +import ProxyRepository from "../repository/ProxyRepository"; +import SystemService from "../service/SystemService"; +import ServerService from "../service/ServerService"; +import GitHubService from "../service/GitHubService"; +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 = 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") : process.env.DIST; -let win: BrowserWindow | null = null; -let tray = null; const preload = join(__dirname, "../preload/index.js"); const url = process.env.VITE_DEV_SERVER_URL; 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 -if (process.platform === "win32") app.setAppUserModelId(app.getName()); +class FrpcDesktopApp { + private _win: BrowserWindow | null = null; + private readonly _silentStart = false; + private _quitting = false; -if (!app.requestSingleInstanceLock()) { - app.quit(); - process.exit(0); -} - -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); + constructor() { + this.initializeBeans(); + this.initializeListeners(); + this.initializeRouters(); + this.initializeElectronApp(); } - // Test actively push message to the Electron-Renderer - win.webContents.on("did-finish-load", () => { - 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 = [ - { - 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}`); + initializeWindow() { + if (this._win) { 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) - .then(r => { - logInfo(LogModule.APP, `Window created successfully.`); - createTray(config); + this._win.webContents.on("did-finish-load", () => { + this._win?.webContents.send( + "main-process-message", + 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) { - logInfo( - LogModule.APP, - `Config retrieved: ${JSON.stringify( - maskSensitiveData(config, [ - "serverAddr", - "serverPort", - "authToken", - "user", - "metaToken" - ]) - )}` - ); + const that = this; + this._win.on("minimize", function (event) { + event.preventDefault(); + that._win.hide(); + }); - if (config.systemStartupConnect) { - startFrpWorkerProcess(config); + this._win.on("close", function (event) { + if (!that._quitting) { + event.preventDefault(); + that._win.hide(); + if (process.platform === "darwin") { + app.dock.hide(); + } + } + return false; + }); + } + + initializeTray() { + const that = this; + let menu: Array = [ + { + label: "显示主窗口", + click: function () { + that._win.show(); + if (process.platform === "darwin") { + app.dock.show().then(() => {}); } } - 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}` - ); + }, + { + label: "退出", + click: () => { + that._quitting = true; + // todo stop frpc process + app.quit(); } - }) - .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", () => { - logInfo(LogModule.APP, `Application is about to quit.`); - stopFrpcProcess(() => { - isQuiting = true; - }); -}); + initializeElectronApp() { + // Disable GPU Acceleration for Windows 7 + if (release().startsWith("6.1")) app.disableHardwareAcceleration(); -ipcMain.handle("open-win", (_, arg) => { - logInfo(LogModule.APP, `Opening new window with argument: ${arg}`); - const childWindow = new BrowserWindow({ - webPreferences: { - preload, - nodeIntegration: true, - contextIsolation: false + // Set application name for Windows 10+ notifications + if (process.platform === "win32") app.setAppUserModelId(app.getName()); + + if (!app.requestSingleInstanceLock()) { + app.quit(); + process.exit(0); } - }); + 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) { - childWindow.loadURL(`${url}#${arg}`); - logInfo(LogModule.APP, `Child window loaded URL: ${url}#${arg}`); - } else { - childWindow.loadFile(indexHtml, { hash: arg }); - logInfo( - LogModule.APP, - `Child window loaded file: ${indexHtml} with hash: ${arg}` + // getConfig((err, config) => { + // if (err) { + // logError(LogModule.APP, `Failed to get config: ${err.message}`); + // return; + // } + + // createWindow(config) + // .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(); \ No newline at end of file diff --git a/electron/dao/BaseDao.ts b/electron/repository/BaseRepository.ts similarity index 98% rename from electron/dao/BaseDao.ts rename to electron/repository/BaseRepository.ts index 5986faf..11aee5a 100644 --- a/electron/dao/BaseDao.ts +++ b/electron/repository/BaseRepository.ts @@ -20,7 +20,7 @@ import IdUtils from "../utils/IdUtils"; // } -class BaseDao { +class BaseRepository { protected readonly db: Datastore; constructor(dbName: string) { @@ -135,4 +135,4 @@ class BaseDao { } } -export default BaseDao; +export default BaseRepository; diff --git a/electron/dao/ProxyDao.ts b/electron/repository/ProxyRepository.ts similarity index 67% rename from electron/dao/ProxyDao.ts rename to electron/repository/ProxyRepository.ts index 80b7cda..cb12507 100644 --- a/electron/dao/ProxyDao.ts +++ b/electron/repository/ProxyRepository.ts @@ -1,6 +1,8 @@ -import BaseDao from "./BaseDao"; +import BaseRepository from "./BaseRepository"; +import Component from "../core/annotation/Component"; -class ProxyDao extends BaseDao { +// @Component() +class ProxyRepository extends BaseRepository { constructor() { super("proxy"); } @@ -23,4 +25,4 @@ class ProxyDao extends BaseDao { } } -export default ProxyDao; +export default ProxyRepository; diff --git a/electron/dao/ServerDao.ts b/electron/repository/ServerRepository.ts similarity index 57% rename from electron/dao/ServerDao.ts rename to electron/repository/ServerRepository.ts index 7c812cc..2e815be 100644 --- a/electron/dao/ServerDao.ts +++ b/electron/repository/ServerRepository.ts @@ -1,6 +1,8 @@ -import BaseDao from "./BaseDao"; +import BaseRepository from "./BaseRepository"; +import Component from "../core/annotation/Component"; -class ServerDao extends BaseDao { +// @Component() +class ServerRepository extends BaseRepository { constructor() { super("server"); } @@ -18,4 +20,4 @@ class ServerDao extends BaseDao { } } -export default ServerDao \ No newline at end of file +export default ServerRepository \ No newline at end of file diff --git a/electron/dao/VersionDao.ts b/electron/repository/VersionRepository.ts similarity index 70% rename from electron/dao/VersionDao.ts rename to electron/repository/VersionRepository.ts index 4b58c22..268692b 100644 --- a/electron/dao/VersionDao.ts +++ b/electron/repository/VersionRepository.ts @@ -1,6 +1,8 @@ -import BaseDao from "./BaseDao"; +import BaseRepository from "./BaseRepository"; +import Component from "../core/annotation/Component"; -class VersionDao extends BaseDao { +// @Component() +class VersionRepository extends BaseRepository { constructor() { super("version"); } @@ -18,7 +20,7 @@ class VersionDao extends BaseDao { } exists(githubReleaseId: number): Promise { - return new Promise(( resolve, reject) => { + return new Promise((resolve, reject) => { this.db.count({ githubReleaseId: githubReleaseId }, (err, count) => { if (err) { reject(err); @@ -30,4 +32,4 @@ class VersionDao extends BaseDao { } } -export default VersionDao; +export default VersionRepository; diff --git a/electron/service/BaseService.ts b/electron/service/BaseService.ts index 148c706..2d449c6 100644 --- a/electron/service/BaseService.ts +++ b/electron/service/BaseService.ts @@ -1,14 +1,14 @@ -import BaseDao from "../dao/BaseDao"; +import BaseRepository from "../repository/BaseRepository"; interface BaseServiceInterface { - // dao: BaseDao; + // dao: BaseRepository; } class BaseService implements BaseServiceInterface { - // dao: BaseDao; + // dao: BaseRepository; // - // constructor(dao: BaseDao) { + // constructor(dao: BaseRepository) { // this.dao = dao; // } } diff --git a/electron/service/FrpcProcessService.ts b/electron/service/FrpcProcessService.ts index 4f54b43..14e135e 100644 --- a/electron/service/FrpcProcessService.ts +++ b/electron/service/FrpcProcessService.ts @@ -1,18 +1,19 @@ import ServerService from "./ServerService"; -import VersionDao from "../dao/VersionDao"; +import VersionRepository from "../repository/VersionRepository"; import PathUtils from "../utils/PathUtils"; import GlobalConstant from "../core/GlobalConstant"; -import { Notification } from "electron"; +import { app, BrowserWindow, Notification } from "electron"; import { success } from "../utils/response"; import treeKill from "tree-kill"; +import BeanFactory from "../core/BeanFactory"; class FrpcProcessService { private readonly _serverService: ServerService; - private readonly _versionDao: VersionDao; + private readonly _versionDao: VersionRepository; private _frpcProcess: any; private _frpcProcessListener: any; - constructor(serverService: ServerService, versionDao: VersionDao) { + constructor(serverService: ServerService, versionDao: VersionRepository) { this._serverService = serverService; this._versionDao = versionDao; } @@ -81,7 +82,7 @@ class FrpcProcessService { console.log("running", running); if (!running) { new Notification({ - title: GlobalConstant.APP_NAME, + title: app.getName(), body: "Connection lost, please check the logs for details." }).show(); // logError( @@ -90,7 +91,8 @@ class FrpcProcessService { // ); // clearInterval(this._frpcProcessListener); } - listenerParam.win.webContents.send( + const win: BrowserWindow = BeanFactory.getBean("win"); + win.webContents.send( listenerParam.channel, success(running) ); diff --git a/electron/service/LogService.ts b/electron/service/LogService.ts index fb5b810..ec64b5f 100644 --- a/electron/service/LogService.ts +++ b/electron/service/LogService.ts @@ -2,6 +2,8 @@ import fs from "fs"; import { success } from "../utils/response"; import PathUtils from "../utils/PathUtils"; import SystemService from "./SystemService"; +import BeanFactory from "../core/BeanFactory"; +import { BrowserWindow } from "electron"; class LogService { private readonly _systemService: SystemService; @@ -11,8 +13,11 @@ class LogService { this._systemService = systemService; } - getFrpLogContent(): Promise { + async getFrpLogContent() { return new Promise((resolve, reject) => { + if (!fs.existsSync(this._logPath)) { + resolve(""); + } fs.readFile(this._logPath, "utf-8", (error, data) => { if (!error) { resolve(data); @@ -28,11 +33,12 @@ class LogService { setTimeout(() => this.watchFrpcLog(listenerParam), 1000); return; } - console.log('watchFrpcLog succcess'); + console.log("watchFrpcLog succcess"); fs.watch(this._logPath, (eventType, filename) => { if (eventType === "change") { console.log("change", eventType, listenerParam.channel); - listenerParam.win.webContents.send( + const win: BrowserWindow = BeanFactory.getBean("win"); + win.webContents.send( listenerParam.channel, success(true) ); diff --git a/electron/service/ProxyService.ts b/electron/service/ProxyService.ts index 58d8072..5361d3f 100644 --- a/electron/service/ProxyService.ts +++ b/electron/service/ProxyService.ts @@ -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"); class ProxyService { - private readonly _proxyDao: ProxyDao; - constructor(proxyDao: ProxyDao) { + private readonly _proxyDao: ProxyRepository; + + constructor(public proxyDao: ProxyRepository) { this._proxyDao = proxyDao; } diff --git a/electron/service/ServerService.ts b/electron/service/ServerService.ts index c475192..3b351d5 100644 --- a/electron/service/ServerService.ts +++ b/electron/service/ServerService.ts @@ -1,16 +1,16 @@ import BaseService from "./BaseService"; -import ServerDao from "../dao/ServerDao"; +import ServerRepository from "../repository/ServerRepository"; import TOML from "smol-toml"; import fs from "fs"; import PathUtils from "../utils/PathUtils"; -import ProxyDao from "../dao/ProxyDao"; +import ProxyRepository from "../repository/ProxyRepository"; class ServerService extends BaseService { - private readonly _serverDao: ServerDao; - private readonly _proxyDao: ProxyDao; + private readonly _serverDao: ServerRepository; + private readonly _proxyDao: ProxyRepository; private readonly _serverId: string = "1"; - constructor(serverDao: ServerDao, proxyDao: ProxyDao) { + constructor(serverDao: ServerRepository, proxyDao: ProxyRepository) { super(); this._serverDao = serverDao; this._proxyDao = proxyDao; diff --git a/electron/service/SystemService.ts b/electron/service/SystemService.ts index 61d9904..c45e820 100644 --- a/electron/service/SystemService.ts +++ b/electron/service/SystemService.ts @@ -4,6 +4,7 @@ import path from "path"; import fs from "fs"; import zlib from "zlib"; import admZip from "adm-zip"; +import Component from "../core/annotation/Component"; const tar = require("tar"); diff --git a/electron/service/VersionService.ts b/electron/service/VersionService.ts index c149ce7..b1d60e2 100644 --- a/electron/service/VersionService.ts +++ b/electron/service/VersionService.ts @@ -1,4 +1,4 @@ -import VersionDao from "../dao/VersionDao"; +import VersionRepository from "../repository/VersionRepository"; import BaseService from "./BaseService"; import GitHubService from "./GitHubService"; import frpReleasesJson from "../json/frp-releases.json"; @@ -14,14 +14,14 @@ import frpChecksums from "../json/frp_all_sha256_checksums.json"; import SystemService from "./SystemService"; class VersionService extends BaseService { - private readonly _versionDao: VersionDao; + private readonly _versionDao: VersionRepository; private readonly _systemService: SystemService; private readonly _gitHubService: GitHubService; private readonly _currFrpArch: Array; private _versions: Array = []; constructor( - versionDao: VersionDao, + versionDao: VersionRepository, systemService: SystemService, gitHubService: GitHubService ) { diff --git a/package.json b/package.json index 4a1bd54..bfdd73f 100644 --- a/package.json +++ b/package.json @@ -64,9 +64,9 @@ "sass": "^1.66.1", "tailwindcss": "^3.3.3", "tree-kill": "^1.2.2", - "typescript": "^5.1.6", - "vite": "^4.4.9", - "vite-plugin-electron": "^0.15.3", + "typescript": "5.7.3", + "vite": "^5.4.11", + "vite-plugin-electron": "^0.28.6", "vite-plugin-electron-renderer": "^0.14.5", "vue": "^3.3.4", "vue-router": "^4.2.4", @@ -82,10 +82,10 @@ "intro.js": "^8.0.0-beta.1", "isbinaryfile": "4.0.10", "js-base64": "^3.7.7", + "smol-toml": "^1.3.1", "snowflakify": "^1.0.5", "tar": "^6.2.0", "unused-filename": "^4.0.1", - "uuid": "^10.0.0", - "smol-toml": "^1.3.1" + "uuid": "^10.0.0" } } diff --git a/src/views/config/index.vue b/src/views/config/index.vue index 6de29d8..c7d8244 100644 --- a/src/views/config/index.vue +++ b/src/views/config/index.vue @@ -362,21 +362,22 @@ onMounted(() => { }); }); - on(ipcRouters.SERVER.exportConfig, () => { - // 礼花 - confetti({ - zIndex: 12002, - particleCount: 200, - spread: 70, - origin: { y: 0.6 } - }); - ElMessageBox.alert("🎉 恭喜你,导入成功 请重启软件", `提示`, { - closeOnClickModal: false, - showClose: false, - confirmButtonText: "立即重启" - }).then(() => { - send(ipcRouters.SYSTEM.relaunchApp); - }); + on(ipcRouters.SERVER.exportConfig, (data) => { + ElMessageBox.alert(`配置路径:${data}`, `🎉 导出成功`); + // // 礼花 + // confetti({ + // zIndex: 12002, + // particleCount: 200, + // spread: 70, + // origin: { y: 0.6 } + // }); + // ElMessageBox.alert("🎉 恭喜你,导入成功 请重启软件", `提示`, { + // closeOnClickModal: false, + // showClose: false, + // confirmButtonText: "立即重启" + // }).then(() => { + // send(ipcRouters.SYSTEM.relaunchApp); + // }); }); // ElMessageBox.alert(data, `提示`); on(ipcRouters.SYSTEM.openAppData, () => { @@ -514,7 +515,6 @@ onUnmounted(() => { removeRouterListeners(ipcRouters.SERVER.resetAllConfig); removeRouterListeners(ipcRouters.VERSION.getDownloadedVersions); removeRouterListeners(ipcRouters.SERVER.exportConfig); - // ipcRenderer.removeAllListeners("Config.clearAll.hook"); removeRouterListeners(ipcRouters.SYSTEM.openAppData); }); diff --git a/tsconfig.json b/tsconfig.json index 216c79d..51a4627 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,7 +22,7 @@ ] }, "types": [ -// "node", + // "node", "vite/client", "element-plus/global" ], diff --git a/types/core.d.ts b/types/core.d.ts index 3b9436d..b99d7d8 100644 --- a/types/core.d.ts +++ b/types/core.d.ts @@ -5,14 +5,14 @@ interface ApiResponse { } interface ControllerParam { - win: BrowserWindow; + // win: BrowserWindow; channel: string; event: Electron.IpcMainEvent; args: any; } interface ListenerParam { - win: BrowserWindow; + // win: BrowserWindow; channel: string; args: any[]; }