From 9f46ea781d8684c23dabe6888cb08614333ed93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=98=89=E4=BC=9F?= <8473136@qq.com> Date: Sun, 23 Feb 2025 21:07:44 +0800 Subject: [PATCH] :building_construction: implement proxy management features and refactor related services --- electron/api/local.ts | 108 --------- electron/api/proxy.ts | 115 --------- electron/controller/LaunchController.ts | 12 + electron/controller/ProxyController.ts | 48 +++- electron/controller/ServerController.ts | 13 +- electron/controller/VersionController.ts | 4 + electron/core/IpcRouter.ts | 54 ++++- electron/dao/ProxyDao.ts | 26 +++ electron/main/index.ts | 6 - electron/service/FileService.ts | 27 ++- ...rocessService.ts => FrpcProcessService.ts} | 13 +- electron/service/ProxyService.ts | 132 +++++++++++ electron/service/ServerService.ts | 43 +++- electron/service/VersionService.ts | 40 +++- electron/utils/FileUtils.ts | 6 + electron/utils/PathUtils.ts | 44 +++- package.json | 3 +- src/views/config/index.vue | 17 +- src/views/home/index.vue | 5 +- src/views/proxy/index.vue | 221 ++++++++---------- types/core.d.ts | 2 + types/frp.d.ts | 22 +- types/frpc-desktop.d.ts | 14 +- 23 files changed, 588 insertions(+), 387 deletions(-) delete mode 100644 electron/api/local.ts delete mode 100644 electron/api/proxy.ts create mode 100644 electron/dao/ProxyDao.ts rename electron/service/{FrpProcessService.ts => FrpcProcessService.ts} (85%) create mode 100644 electron/service/ProxyService.ts diff --git a/electron/api/local.ts b/electron/api/local.ts deleted file mode 100644 index 65de874..0000000 --- a/electron/api/local.ts +++ /dev/null @@ -1,108 +0,0 @@ -import {ipcMain} from "electron"; -import { logDebug, logError, logInfo, LogModule, logWarn } from "../utils/log"; - -const {exec, spawn} = require("child_process"); - -type LocalPort = { - protocol: string; - ip: string; - port: number; -} - -export const initLocalApi = () => { - const command = process.platform === 'win32' - ? 'netstat -a -n' - : 'netstat -an | grep LISTEN'; - - ipcMain.on("local.getLocalPorts", async (event, args) => { - logInfo(LogModule.APP, "Starting to retrieve local ports"); - // 执行命令 - exec(command, (error, stdout, stderr) => { - if (error) { - logError(LogModule.APP, `getLocalPorts - error: ${error.message}`); - return; - } - if (stderr) { - logWarn(LogModule.APP, `getLocalPorts - stderr: ${stderr}`); - return; - } - - logDebug(LogModule.APP, `Command output: ${stdout}`); - let ports = []; - if (stdout) { - if (process.platform === 'win32') { - // window - ports = stdout.split('\r\n') - .filter(f => f.indexOf('TCP') !== -1 || f.indexOf('UDP') !== -1) - .map(m => { - const cols = m.split(' ') - .filter(f => f != '') - const local = cols[1] - const s = local.lastIndexOf(":") - let localIP = local.slice(0, s); - let localPort = local.slice(s - local.length + 1); - const singe: LocalPort = { - protocol: cols[0], - ip: localIP, - port: localPort - } - - return singe; - }) - } else if (process.platform === 'darwin') { - // mac - ports = stdout.split('\n') - .filter(m => { - const cols = m.split(' ') - .filter(f => f != '') - const local = cols[3] - return local - }) - .map(m => { - const cols = m.split(' ') - .filter(f => f != '') - const local = cols[3] - const s = local.lastIndexOf(".") - let localIP = local.slice(0, s); - let localPort = local.slice(s - local.length + 1); - const singe: LocalPort = { - protocol: cols[0], - ip: localIP, - port: localPort - } - return singe; - }) - - } else if (process.platform === 'linux') { - ports = stdout.split('\n') - .filter(f => - f.indexOf('tcp') !== -1|| - f.indexOf('tcp6') !== -1|| - f.indexOf('udp') !== -1 || - f.indexOf('udp6') !== -1 - ).map(m => { - const cols = m.split(' ') - .filter(f => f != '') - const local = cols[3] - const s = local.lastIndexOf(":") - let localIP = local.slice(0, s); - let localPort = local.slice(s - local.length + 1); - const singe: LocalPort = { - protocol: cols[0], - ip: localIP, - port: localPort - } - return singe; - }) - } - - } - - ports.sort((a, b) => a.port - b.port); - - event.reply("local.getLocalPorts.hook", { - data: ports - }); - }); - }); -} diff --git a/electron/api/proxy.ts b/electron/api/proxy.ts deleted file mode 100644 index bed5d9b..0000000 --- a/electron/api/proxy.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { ipcMain } from "electron"; -import { - deleteProxyById, - getProxyById, - insertProxy, - listProxy, - updateProxyById, - updateProxyStatus -} from "../storage/proxy"; -import { reloadFrpcProcess } from "./frpc"; -import { logError, logInfo, LogModule, logWarn } from "../utils/log"; -export const initProxyApi = () => { - ipcMain.on("proxy.getProxys", async (event, args) => { - logInfo(LogModule.APP, "Requesting to get proxies."); - listProxy((err, documents) => { - if (err) { - logError(LogModule.APP, `Error retrieving proxies: ${err.message}`); - } else { - logInfo(LogModule.APP, "Proxies retrieved successfully."); - } - event.reply("Proxy.getProxys.hook", { - err: err, - data: documents - }); - }); - }); - - ipcMain.on("proxy.insertProxy", async (event, args) => { - delete args["_id"]; - logInfo(LogModule.APP, "Inserting a new proxy."); - insertProxy(args, (err, documents) => { - if (err) { - logError(LogModule.APP, `Error inserting proxy: ${err.message}`); - } else { - logInfo(LogModule.APP, "Proxy inserted successfully."); - reloadFrpcProcess(); - } - event.reply("Proxy.insertProxy.hook", { - err: err, - data: documents - }); - }); - }); - - ipcMain.on("proxy.deleteProxyById", async (event, args) => { - logInfo(LogModule.APP, `Deleting proxy with ID: ${args._id}`); - deleteProxyById(args, (err, documents) => { - if (err) { - logError(LogModule.APP, `Error deleting proxy: ${err.message}`); - } else { - logInfo(LogModule.APP, "Proxy deleted successfully."); - reloadFrpcProcess(); - } - event.reply("Proxy.deleteProxyById.hook", { - err: err, - data: documents - }); - }); - }); - - ipcMain.on("proxy.getProxyById", async (event, args) => { - logInfo(LogModule.APP, `Requesting proxy with ID: ${args._id}`); - getProxyById(args, (err, documents) => { - if (err) { - logError(LogModule.APP, `Error retrieving proxy: ${err.message}`); - } else { - logInfo(LogModule.APP, "Proxy retrieved successfully."); - } - event.reply("Proxy.getProxyById.hook", { - err: err, - data: documents - }); - }); - }); - - ipcMain.on("proxy.updateProxy", async (event, args) => { - if (!args._id) { - logWarn(LogModule.APP, "No proxy ID provided for update."); - return; - } - logInfo(LogModule.APP, `Updating proxy with ID: ${args._id}`); - updateProxyById(args, (err, documents) => { - if (err) { - logError(LogModule.APP, `Error updating proxy: ${err.message}`); - } else { - logInfo(LogModule.APP, "Proxy updated successfully."); - reloadFrpcProcess(); - } - event.reply("Proxy.updateProxy.hook", { - err: err, - data: documents - }); - }); - }); - - ipcMain.on("proxy.updateProxyStatus", async (event, args) => { - logInfo(LogModule.APP, `Updating status for proxy ID: ${args._id}`); - if (!args._id) { - logWarn(LogModule.APP, "No proxy ID provided for status update."); - return; - } - updateProxyStatus(args._id, args.status, (err, documents) => { - if (err) { - logError(LogModule.APP, `Error updating proxy status: ${err.message}`); - } else { - logInfo(LogModule.APP, "Proxy status updated successfully."); - reloadFrpcProcess(); - } - event.reply("Proxy.updateProxyStatus.hook", { - err: err, - data: documents - }); - }); - }); -}; diff --git a/electron/controller/LaunchController.ts b/electron/controller/LaunchController.ts index 912dd0b..b742216 100644 --- a/electron/controller/LaunchController.ts +++ b/electron/controller/LaunchController.ts @@ -1,5 +1,17 @@ import BaseController from "./BaseController"; +import FrpcProcessService from "../service/FrpcProcessService"; class LaunchController extends BaseController { + private readonly _frpcProcessService: FrpcProcessService; + constructor(frpcProcessService: FrpcProcessService) { + super(); + this._frpcProcessService = frpcProcessService; + } + + launch(req: ControllerParam) { + this._frpcProcessService.startFrpcProcess().then(r => {}); + } } + +export default LaunchController; \ No newline at end of file diff --git a/electron/controller/ProxyController.ts b/electron/controller/ProxyController.ts index 194d6dd..e55bca4 100644 --- a/electron/controller/ProxyController.ts +++ b/electron/controller/ProxyController.ts @@ -1,7 +1,53 @@ import BaseController from "./BaseController"; +import ProxyService from "../service/ProxyService"; +import { success } from "../utils/response"; +import ProxyDao from "../dao/ProxyDao"; class ProxyController extends BaseController { - constructor() { + private readonly _proxyService: ProxyService; + private readonly _proxyDao: ProxyDao; + + constructor(proxyService: ProxyService, proxyDao: ProxyDao) { super(); + this._proxyService = proxyService; + this._proxyDao = proxyDao; + } + + createProxy(req: ControllerParam) { + this._proxyService.insertProxy(req.args).then(data => { + req.event.reply(req.channel, success(data)); + }); + } + + modifyProxy(req: ControllerParam) { + this._proxyService.updateProxy(req.args).then(data => { + req.event.reply(req.channel, success(data)); + }); + } + + getAllProxies(req: ControllerParam) { + this._proxyDao.findAll().then(data => { + req.event.reply(req.channel, success(data)); + }); + } + + deleteProxy(req: ControllerParam) { + this._proxyService.deleteProxy(req.args).then(data => { + req.event.reply(req.channel, success(data)); + }); + } + + modifyProxyStatus(req: ControllerParam) { + this._proxyDao.updateProxyStatus(req.args.id, req.args.status).then(() => { + req.event.reply(req.channel, success()); + }); + } + + getLocalPorts(req: ControllerParam) { + this._proxyService.getLocalPorts().then(data => { + req.event.reply(req.channel, success(data)); + }); } } + +export default ProxyController; diff --git a/electron/controller/ServerController.ts b/electron/controller/ServerController.ts index eeafb3f..e920c59 100644 --- a/electron/controller/ServerController.ts +++ b/electron/controller/ServerController.ts @@ -1,19 +1,23 @@ import BaseController from "./BaseController"; import ServerService from "../service/ServerService"; import { success } from "../utils/response"; +import FileService from "../service/FileService"; +import PathUtils from "../utils/PathUtils"; class ServerController extends BaseController { private readonly _serverService: ServerService; + private readonly _fileService: FileService; - constructor(serverService: ServerService) { + constructor(serverService: ServerService, fileService: FileService) { super(); this._serverService = serverService; + this._fileService = fileService; } saveConfig(req: ControllerParam) { this._serverService.saveServerConfig(req.args).then(() => { req.event.reply(req.channel, success()); - }) + }); } getServerConfig(req: ControllerParam) { @@ -21,7 +25,12 @@ class ServerController extends BaseController { this._serverService.getServerConfig().then(data => { req.event.reply(req.channel, success(data)); }); + } + openAppData(req: ControllerParam) { + this._fileService.openLocalPath(PathUtils.getAppData()).then(data => { + req.event.reply(req.channel, success(data)); + }); } } diff --git a/electron/controller/VersionController.ts b/electron/controller/VersionController.ts index fef09a7..5c546d2 100644 --- a/electron/controller/VersionController.ts +++ b/electron/controller/VersionController.ts @@ -62,6 +62,10 @@ class VersionController extends BaseController { req.event.reply(req.channel, fail()); }); } + + importLocalFrpcVersion (){ + + } } export default VersionController; diff --git a/electron/core/IpcRouter.ts b/electron/core/IpcRouter.ts index 130d9d9..a5ab6a1 100644 --- a/electron/core/IpcRouter.ts +++ b/electron/core/IpcRouter.ts @@ -9,6 +9,11 @@ import VersionController from "../controller/VersionController"; import FileService from "../service/FileService"; 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"; export const ipcRouters: IpcRouters = { SERVER: { @@ -48,6 +53,38 @@ export const ipcRouters: IpcRouters = { path: "version/deleteDownloadedVersion", controller: "versionController.deleteDownloadedVersion" } + }, + LAUNCH: { + launch: { + path: "launch/launch", + controller: "launchController.launch" + } + }, + PROXY: { + createProxy: { + path: "proxy/createProxy", + controller: "proxyController.createProxy" + }, + modifyProxy: { + path: "proxy/modifyProxy", + controller: "proxyController.modifyProxy" + }, + deleteProxy: { + path: "proxy/deleteProxy", + controller: "proxyController.deleteProxy" + }, + getAllProxies: { + path: "proxy/getAllProxies", + controller: "proxyController.getAllProxies" + }, + modifyProxyStatus: { + path: "proxy/modifyProxyStatus", + controller: "proxyController.modifyProxyStatus" + }, + getLocalPorts: { + path: "proxy/getLocalPorts", + controller: "proxyController.getLocalPorts" + } } }; @@ -70,8 +107,9 @@ class IpcRouterConfigurate { private initializeBeans() { const serverDao = new ServerDao(); const versionDao = new VersionDao(); + const proxyDao = new ProxyDao(); const fileService = new FileService(); - const serverService = new ServerService(serverDao); + const serverService = new ServerService(serverDao, proxyDao); const gitHubService = new GitHubService(); const versionService = new VersionService( versionDao, @@ -79,19 +117,31 @@ class IpcRouterConfigurate { gitHubService ); const logService = new LogService(fileService); - const serverController = new ServerController(serverService); + const frpcProcessService = new FrpcProcessService( + serverService, + versionDao + ); + const proxyService = new ProxyService(proxyDao); + const serverController = new ServerController(serverService, fileService); const versionController = new VersionController(versionService, versionDao); const logController = new LogController(logService); + const launchController = new LaunchController(frpcProcessService); + const proxyController = new ProxyController(proxyService, proxyDao); this._beans.set("serverDao", serverDao); this._beans.set("versionDao", versionDao); + this._beans.set("proxyDao", proxyDao); this._beans.set("fileService", fileService); 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("serverController", serverController); this._beans.set("versionController", versionController); this._beans.set("logController", logController); + this._beans.set("launchController", launchController); + this._beans.set("proxyController", proxyController); } /** diff --git a/electron/dao/ProxyDao.ts b/electron/dao/ProxyDao.ts new file mode 100644 index 0000000..80b7cda --- /dev/null +++ b/electron/dao/ProxyDao.ts @@ -0,0 +1,26 @@ +import BaseDao from "./BaseDao"; + +class ProxyDao extends BaseDao { + constructor() { + super("proxy"); + } + + updateProxyStatus(id: string, status: number): Promise { + return new Promise((resolve, reject) => { + this.db.update( + { _id: id }, + { $set: { status: status } }, + {}, + (err, numberOfUpdated, upsert) => { + if (err) { + reject(err); + } else { + resolve(); + } + } + ); + }); + } +} + +export default ProxyDao; diff --git a/electron/main/index.ts b/electron/main/index.ts index aff992a..0a7efe5 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -12,7 +12,6 @@ import { release } from "node:os"; import node_path, { join } from "node:path"; import { initGitHubApi } from "../api/github"; import { initConfigApi } from "../api/config"; -import { initProxyApi } from "../api/proxy"; import { initFrpcApi, startFrpWorkerProcess, @@ -21,7 +20,6 @@ import { import { initFileApi } from "../api/file"; import { getConfig } from "../storage/config"; import { initCommonApi } from "../api/common"; -import { initLocalApi } from "../api/local"; import { initLog, logError, logInfo, LogModule } from "../utils/log"; import { maskSensitiveData } from "../utils/desensitize"; import IpcRouterConfigurate from "../core/IpcRouter"; @@ -201,8 +199,6 @@ app.whenReady().then(() => { initConfigApi(win); logInfo(LogModule.APP, `Config API initialized.`); - initProxyApi(); - logInfo(LogModule.APP, `Proxy API initialized.`); initFrpcApi(); logInfo(LogModule.APP, `FRPC API initialized.`); @@ -215,8 +211,6 @@ app.whenReady().then(() => { initCommonApi(); logInfo(LogModule.APP, `Common API initialized.`); - initLocalApi(); - logInfo(LogModule.APP, `Local API initialized.`); // initUpdaterApi(win); logInfo(LogModule.APP, `Updater API initialization skipped.`); diff --git a/electron/service/FileService.ts b/electron/service/FileService.ts index e92a95f..2fce3b4 100644 --- a/electron/service/FileService.ts +++ b/electron/service/FileService.ts @@ -1,10 +1,11 @@ -import { shell } from "electron"; +import { dialog, shell } from "electron"; import path from "path"; import fs from "fs"; import zlib from "zlib"; import admZip from "adm-zip"; import GlobalConstant from "../core/GlobalConstant"; + // import tar from "tar"; const tar = require("tar"); @@ -28,6 +29,18 @@ class FileService { }); } + openLocalPath(path: string) { + return new Promise((resolve, reject) => { + shell.openPath(path).then(errorMessage => { + if (errorMessage) { + resolve(false); + } else { + reject(true); + } + }); + }); + } + decompressZipFile(zipFilePath: string, targetPath: string) { if (!zipFilePath.endsWith(GlobalConstant.ZIP_EXT)) { throw new Error("The file is not a .zip file"); @@ -86,6 +99,18 @@ class FileService { // ); }); } + + selectLocalFile(name: string, path: any) { + dialog.showOpenDialogSync({ + properties: ["openFile"], + filters: [ + { + name: name, + extensions: path + } + ] + }); + } } export default FileService; diff --git a/electron/service/FrpProcessService.ts b/electron/service/FrpcProcessService.ts similarity index 85% rename from electron/service/FrpProcessService.ts rename to electron/service/FrpcProcessService.ts index 8fb0bc3..7c6a3cb 100644 --- a/electron/service/FrpProcessService.ts +++ b/electron/service/FrpcProcessService.ts @@ -1,13 +1,13 @@ import ServerService from "./ServerService"; import VersionDao from "../dao/VersionDao"; import PathUtils from "../utils/PathUtils"; -import { frpcProcess } from "../api/frpc"; import GlobalConstant from "../core/GlobalConstant"; import { Notification } from "electron"; +import { logDebug, LogModule } from "../utils/log"; const { exec, spawn } = require("child_process"); -class FrpProcessService { +class FrpcProcessService { private readonly _serverService: ServerService; private readonly _versionDao: VersionDao; private _frpcProcess: any; @@ -28,6 +28,7 @@ class FrpProcessService { config.frpcVersion ); // todo genConfigfile. + await this._serverService.genTomlConfig(); const configPath = ""; const command = `${PathUtils.getFrpcFilename()} -c ${configPath}`; this._frpcProcess = spawn(command, { @@ -35,11 +36,11 @@ class FrpProcessService { shell: true }); - frpcProcess.stdout.on("data", data => { - // logDebug(LogModule.FRP_CLIENT, `Frpc process output: ${data}`); + this._frpcProcess.stdout.on("data", data => { + logDebug(LogModule.FRP_CLIENT, `Frpc process output: ${data}`); }); - frpcProcess.stdout.on("error", data => { + this._frpcProcess.stdout.on("error", data => { // logError(LogModule.FRP_CLIENT, `Frpc process error: ${data}`); // stopFrpcProcess(() => {}); this.stopFrpcProcess(); @@ -71,4 +72,4 @@ class FrpProcessService { } } -export default FrpProcessService; +export default FrpcProcessService; diff --git a/electron/service/ProxyService.ts b/electron/service/ProxyService.ts new file mode 100644 index 0000000..58d8072 --- /dev/null +++ b/electron/service/ProxyService.ts @@ -0,0 +1,132 @@ +import ProxyDao from "../dao/ProxyDao"; + +const { exec, spawn } = require("child_process"); + +class ProxyService { + private readonly _proxyDao: ProxyDao; + + constructor(proxyDao: ProxyDao) { + this._proxyDao = proxyDao; + } + + async insertProxy(proxy: FrpcProxy) { + return await this._proxyDao.insert(proxy); + } + + async updateProxy(proxy: FrpcProxy) { + return await this._proxyDao.updateById(proxy._id, proxy); + } + + async deleteProxy(proxyId: string) { + return await this._proxyDao.deleteById(proxyId); + } + + async getLocalPorts(): Promise> { + const command = + process.platform === "win32" + ? "netstat -a -n" + : "netstat -an | grep LISTEN"; + return new Promise((resolve, reject) => { + exec(command, (error, stdout, stderr) => { + if (error) { + reject(error); + } + if (stderr) { + reject(stderr); + } + let ports: Array = []; + if (stdout) { + if (process.platform === "win32") { + // window + ports = stdout + .split("\r\n") + .filter(f => f.indexOf("TCP") !== -1 || f.indexOf("UDP") !== -1) + .map(m => { + const cols = m.split(" ").filter(f => f != ""); + const local = cols[1]; + const s = local.lastIndexOf(":"); + let localIP = local.slice(0, s); + let localPort = local.slice(s - local.length + 1); + const singe: LocalPort = { + protocol: cols[0], + ip: localIP, + port: localPort + }; + + return singe; + }); + } else if (process.platform === "darwin") { + // mac + ports = stdout + .split("\n") + .filter(m => { + const cols = m.split(" ").filter(f => f != ""); + const local = cols[3]; + return local; + }) + .map(m => { + const cols = m.split(" ").filter(f => f != ""); + const local = cols[3]; + const s = local.lastIndexOf("."); + let localIP = local.slice(0, s); + let localPort = local.slice(s - local.length + 1); + const singe: LocalPort = { + protocol: cols[0], + ip: localIP, + port: localPort + }; + return singe; + }); + } else if (process.platform === "linux") { + ports = stdout + .split("\n") + .filter( + f => + f.indexOf("tcp") !== -1 || + f.indexOf("tcp6") !== -1 || + f.indexOf("udp") !== -1 || + f.indexOf("udp6") !== -1 + ) + .map(m => { + const cols = m.split(" ").filter(f => f != ""); + const local = cols[3]; + const s = local.lastIndexOf(":"); + let localIP = local.slice(0, s); + let localPort = local.slice(s - local.length + 1); + const singe: LocalPort = { + protocol: cols[0], + ip: localIP, + port: localPort + }; + return singe; + }); + } + } + + ports.sort((a, b) => a.port - b.port); + + resolve(ports); + }); + // exec(command, (error, stdout, stderr) => { + // if (error) { + // logError(LogModule.APP, `getLocalPorts - error: ${error.message}`); + // return; + // } + // if (stderr) { + // logWarn(LogModule.APP, `getLocalPorts - stderr: ${stderr}`); + // return; + // } + // + // logDebug(LogModule.APP, `Command output: ${stdout}`); + // let ports = []; + + // + // event.reply("local.getLocalPorts.hook", { + // data: ports + // }); + // }); + }); + } +} + +export default ProxyService; diff --git a/electron/service/ServerService.ts b/electron/service/ServerService.ts index 698a1dc..6a89992 100644 --- a/electron/service/ServerService.ts +++ b/electron/service/ServerService.ts @@ -1,33 +1,68 @@ import BaseService from "./BaseService"; import ServerDao from "../dao/ServerDao"; +import TOML from "smol-toml"; +import fs from "fs"; +import PathUtils from "../utils/PathUtils"; +import ProxyDao from "../dao/ProxyDao"; class ServerService extends BaseService { private readonly _serverDao: ServerDao; - constructor(serverDao: ServerDao) { + private readonly _proxyDao: ProxyDao; + private readonly _serverId: string = "1"; + + constructor(serverDao: ServerDao, proxyDao: ProxyDao) { super(); this._serverDao = serverDao; + this._proxyDao = proxyDao; } async saveServerConfig( frpcServer: FrpcDesktopServer ): Promise { - return await this._serverDao.updateById("1", frpcServer); + frpcServer._id = this._serverId; + return await this._serverDao.updateById(this._serverId, frpcServer); } async getServerConfig(): Promise { - return await this._serverDao.findById("1"); + return await this._serverDao.findById(this._serverId); } hasServerConfig(): Promise { return new Promise((resolve, reject) => { this._serverDao - .exists("1") + .exists(this._serverId) .then(r => { resolve(r); }) .catch(err => reject(err)); }); } + + async genTomlConfig() { + const server = await this.getServerConfig(); + const proxies = await this._proxyDao.findAll(); + const enabledProxies = proxies + .filter(f => f.status === 1) + .map(proxy => { + const { _id, status, ...frpProxyConfig } = proxy; + return frpProxyConfig + }); + const { frpcVersion, _id, system, ...commonConfig } = server; + const frpcConfig = { ...commonConfig }; + frpcConfig.log.to = PathUtils.getFrpcLogFilePath(); + const toml = TOML.stringify({ ...frpcConfig, enabledProxies }); + fs.writeFile( + PathUtils.getTomlConfigFilePath(), // 配置文件目录 + toml, // 配置文件内容 + { flag: "w" }, + err => { + if (err) { + } else { + // callback(filename); + } + } + ); + } } export default ServerService; diff --git a/electron/service/VersionService.ts b/electron/service/VersionService.ts index 49b6484..7a76147 100644 --- a/electron/service/VersionService.ts +++ b/electron/service/VersionService.ts @@ -4,20 +4,21 @@ import GitHubService from "./GitHubService"; import FileService from "./FileService"; import frpReleasesJson from "../json/frp-releases.json"; import { download } from "electron-dl"; -import { BrowserWindow } from "electron"; +import { BrowserWindow, dialog } from "electron"; import GlobalConstant from "../core/GlobalConstant"; import path from "path"; import fs from "fs"; import SecureUtils from "../utils/SecureUtils"; import PathUtils from "../utils/PathUtils"; import FileUtils from "../utils/FileUtils"; +import frpChecksums from "../json/frp_all_sha256_checksums.json"; class VersionService extends BaseService { private readonly _versionDao: VersionDao; private readonly _fileService: FileService; private readonly _gitHubService: GitHubService; private readonly _currFrpArch: Array; - private versions: Array = []; + private _versions: Array = []; constructor( versionDao: VersionDao, @@ -34,7 +35,7 @@ class VersionService extends BaseService { downloadFrpVersion(githubReleaseId: number, onProgress: Function) { return new Promise((resolve, reject) => { - const version = this.versions.find( + const version = this._versions.find( f => f.githubReleaseId === githubReleaseId ); if (!version) { @@ -133,8 +134,8 @@ class VersionService extends BaseService { .then(async (releases: Array) => { const versions: Array = await this.githubRelease2FrpcVersion(releases); - // const versions: Array = (this.versions = versions); - this.versions = versions; + // const _versions: Array = (this._versions = _versions); + this._versions = versions; resolve(versions); }) .catch(err => reject(err)); @@ -158,6 +159,10 @@ class VersionService extends BaseService { ): Promise> { const allVersions = await this._versionDao.findAll(); return releases + .filter(release => { + // only support toml version. + return release.id > 124395282; + }) .filter(release => { return this.findCurrentArchitectureAsset(release.assets); }) @@ -197,6 +202,31 @@ class VersionService extends BaseService { } return false; } + + async importLocalFrpcVersion(win: BrowserWindow) { + const result = await dialog.showOpenDialog(win, { + properties: ["openFile"], + filters: [ + { name: "Frpc", extensions: ["tar.gz", "zip"] } // 允许选择的文件类型,分开后缀以确保可以选择 + ] + }); + if (result.canceled) { + return; + } else { + const filePath = result.filePaths[0]; + const checksum = FileUtils.calculateFileChecksum(filePath); + const frpName = frpChecksums[checksum]; + if (frpName) { + if (this._currFrpArch.every(item => frpName.includes(item))) { + const version = this.getFrpVersionByAssetName(frpName); + } + } + } + } + + getFrpVersionByAssetName(assetName: string) { + return this._versions.find(f => f.assetName === assetName); + } } export default VersionService; diff --git a/electron/utils/FileUtils.ts b/electron/utils/FileUtils.ts index 25b060b..ac53bf9 100644 --- a/electron/utils/FileUtils.ts +++ b/electron/utils/FileUtils.ts @@ -18,6 +18,12 @@ class FileUtils { hash.update(fileBuffer); return hash.digest("hex"); } + + public static mkdir(path: string) { + if (!fs.existsSync(path)) { + fs.mkdirSync(path, { recursive: true, mode: 0o777 }); + } + } } export default FileUtils; diff --git a/electron/utils/PathUtils.ts b/electron/utils/PathUtils.ts index 141d911..c1feea9 100644 --- a/electron/utils/PathUtils.ts +++ b/electron/utils/PathUtils.ts @@ -2,29 +2,61 @@ import SecureUtils from "./SecureUtils"; import { app } from "electron"; import path from "path"; +import FileUtils from "./FileUtils"; class PathUtils { public static getDownloadStoragePath() { - return path.join( - PathUtils.getAppData(), - SecureUtils.calculateMD5("download") - ); + const result = path.join(PathUtils.getAppData(), "download"); + FileUtils.mkdir(result); + return result; } public static getVersionStoragePath() { - return path.join( + const result = path.join( PathUtils.getAppData(), SecureUtils.calculateMD5("frpc") ); + FileUtils.mkdir(result); + return result; + } + + public static getConfigStoragePath() { + const result = path.join( + PathUtils.getAppData(), + // SecureUtils.calculateMD5("config") + "config" + ); + FileUtils.mkdir(result); + return result; } public static getFrpcFilename() { - return SecureUtils.calculateMD5("frpc") + return SecureUtils.calculateMD5("frpc"); } public static getAppData() { return app.getPath("userData"); } + + public static getTomlConfigFilePath() { + return path.join( + PathUtils.getConfigStoragePath(), + SecureUtils.calculateMD5("frpc") + ".toml" + ); + } + + public static getFrpcLogStoragePath() { + const result = path.join(PathUtils.getAppData(), "log"); + FileUtils.mkdir(result); + return result; + } + + public static getFrpcLogFilePath() { + return path.join( + PathUtils.getFrpcLogStoragePath(), + SecureUtils.calculateMD5("frpc-log") + ".log" + ); + } } export default PathUtils; diff --git a/package.json b/package.json index 44d7f3e..4a1bd54 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "snowflakify": "^1.0.5", "tar": "^6.2.0", "unused-filename": "^4.0.1", - "uuid": "^10.0.0" + "uuid": "^10.0.0", + "smol-toml": "^1.3.1" } } diff --git a/src/views/config/index.vue b/src/views/config/index.vue index 062c089..7199b62 100644 --- a/src/views/config/index.vue +++ b/src/views/config/index.vue @@ -200,15 +200,12 @@ const handleSubmit = useDebounceFn(() => { if (valid) { loading.value = 1; const data = clone(formData.value); - ipcRenderer.send("server/saveConfig", data); + send(ipcRouters.SERVER.saveConfig, data); + // ipcRenderer.send("server/saveConfig", data); } }); }, 300); -const handleLoadVersions = () => { - ipcRenderer.send("config.versions"); -}; - const handleAuthMethodChange = e => { if (e === "multiuser") { ElMessageBox.alert( @@ -242,12 +239,14 @@ const handleLoadSavedConfig = () => { send(ipcRouters.SERVER.getServerConfig); }; + onMounted(() => { handleLoadDownloadedVersion(); handleLoadSavedConfig(); on(ipcRouters.SERVER.getServerConfig, data => { console.log("data", data); + formData.value = data; loading.value--; }); @@ -256,6 +255,14 @@ onMounted(() => { versions.value = data; // checkAndResetVersion(); }); + + on(ipcRouters.SERVER.saveConfig, data => { + ElMessage({ + type: "success", + message: "保存成功" + }); + loading.value--; + }); // ipcRenderer.send("config.getConfig"); // handleLoadVersions(); // ipcRenderer.on("Config.getConfig.hook", (event, args) => { diff --git a/src/views/home/index.vue b/src/views/home/index.vue index 08be70a..3963c18 100644 --- a/src/views/home/index.vue +++ b/src/views/home/index.vue @@ -5,6 +5,8 @@ import { ipcRenderer } from "electron"; import { ElMessageBox } from "element-plus"; import router from "@/router"; import { useDebounceFn, useIntervalFn } from "@vueuse/core"; +import { send } from "@/utils/ipcUtils"; +import { ipcRouters } from "../../../electron/core/IpcRouter"; defineComponent({ name: "Home" @@ -13,7 +15,8 @@ defineComponent({ const running = ref(false); const handleStartFrpc = () => { - ipcRenderer.send("frpc.start"); + // ipcRenderer.send("frpc.start"); + send(ipcRouters.LAUNCH.launch); }; const handleStopFrpc = () => { diff --git a/src/views/proxy/index.vue b/src/views/proxy/index.vue index bdf0b52..c0f80af 100644 --- a/src/views/proxy/index.vue +++ b/src/views/proxy/index.vue @@ -15,6 +15,8 @@ import { useClipboard, useDebounceFn } from "@vueuse/core"; import IconifyIconOffline from "@/components/IconifyIcon/src/iconifyIconOffline"; import commonIps from "./commonIp.json"; import path from "path"; +import { on, removeRouterListeners, send } from "@/utils/ipcUtils"; +import { ipcRouters } from "../../../electron/core/IpcRouter"; defineComponent({ name: "Proxy" @@ -23,7 +25,7 @@ defineComponent({ /** * 代理列表 */ -const proxys = ref>([]); +const proxys = ref>([]); /** * loading */ @@ -44,11 +46,13 @@ const edit = ref({ visible: false }); -const defaultForm = ref({ +const defaultForm: FrpcProxy = { _id: "", + hostHeaderRewrite: "", + locations: [], name: "", type: "http", - localIp: "", + localIP: "", localPort: "8080", remotePort: "8080", customDomains: [""], @@ -57,7 +61,6 @@ const defaultForm = ref({ secretKey: "", bindAddr: "", bindPort: null, - status: true, subdomain: "", basicAuth: false, httpUser: "", @@ -67,13 +70,14 @@ const defaultForm = ref({ https2http: false, https2httpCaFile: "", https2httpKeyFile: "", - keepTunnelOpen: false -}); + keepTunnelOpen: false, + status: 1 +}; /** * 表单内容 */ -const editForm = ref(defaultForm.value); +const editForm = ref(defaultForm); /** * 代理类型 @@ -104,7 +108,7 @@ const editFormRules = reactive({ // } ], type: [{ required: true, message: "请选择类型", trigger: "blur" }], - localIp: [ + localIP: [ { required: true, message: "请输入内网地址", trigger: "blur" }, { pattern: /^[\w-]+(\.[\w-]+)+$/, @@ -247,7 +251,7 @@ const handleRangePort = () => { */ const handleSubmit = async () => { if (!editFormRef.value) return; - await editFormRef.value.validate(valid => { + editFormRef.value.validate(valid => { if (valid) { if (handleRangePort()) { const lc = handleGetPortCount(editForm.value.localPort); @@ -278,9 +282,9 @@ const handleSubmit = async () => { loading.value.form = 1; const data = clone(editForm.value); if (data._id) { - ipcRenderer.send("proxy.updateProxy", data); + send(ipcRouters.PROXY.createProxy, data); } else { - ipcRenderer.send("proxy.insertProxy", data); + send(ipcRouters.PROXY.modifyProxy, data); } } }); @@ -305,97 +309,25 @@ const handleDeleteDomain = (index: number) => { * 加载代理 */ const handleLoadProxys = () => { - ipcRenderer.send("proxy.getProxys"); + send(ipcRouters.PROXY.getAllProxies); }; /** * 删除代理 * @param proxy */ -const handleDeleteProxy = (proxy: Proxy) => { - ipcRenderer.send("proxy.deleteProxyById", proxy._id); +const handleDeleteProxy = (proxy: FrpcProxy) => { + send(ipcRouters.PROXY.deleteProxy, proxy._id); + // ipcRenderer.send("proxy.deleteProxyById", proxy._id); }; /** * 重置表单 */ const handleResetForm = () => { - editForm.value = defaultForm.value; + editForm.value = defaultForm; }; -/** - * 初始化回调 - */ -const handleInitHook = () => { - const InsertOrUpdateHook = (message: string, args: any) => { - loading.value.form--; - const { err } = args; - if (!err) { - ElMessage({ - type: "success", - message: message - }); - handleResetForm(); - handleLoadProxys(); - edit.value.visible = false; - } - }; - - ipcRenderer.on("Proxy.insertProxy.hook", (event, args) => { - InsertOrUpdateHook("新增成功", args); - }); - ipcRenderer.on("Proxy.updateProxy.hook", (event, args) => { - InsertOrUpdateHook("修改成功", args); - }); - - ipcRenderer.on("Proxy.updateProxyStatus.hook", (event, args) => { - if (args.data > 0) { - handleLoadProxys(); - } - console.log("更新结果", args); - }); - - ipcRenderer.on("local.getLocalPorts.hook", (event, args) => { - loading.value.localPorts--; - localPorts.value = args.data; - console.log("内网端口", localPorts.value); - }); - // ipcRenderer.on("Proxy.updateProxy.hook", (event, args) => { - // loading.value.form--; - // const { err } = args; - // if (!err) { - // ElMessage({ - // type: "success", - // message: "修改成功" - // }); - // handleResetForm(); - // handleLoadProxys(); - // edit.value.visible = false; - // } - // }); - ipcRenderer.on("Proxy.getProxys.hook", (event, args) => { - loading.value.list--; - const { err, data } = args; - if (!err) { - data.forEach(f => { - if (f.status === null || f.status === undefined) { - f.status = true; - } - }); - proxys.value = data; - } - }); - ipcRenderer.on("Proxy.deleteProxyById.hook", (event, args) => { - const { err, data } = args; - if (!err) { - handleLoadProxys(); - ElMessage({ - type: "success", - message: "删除成功" - }); - } - }); -}; const handleOpenInsert = () => { edit.value = { title: "新增代理", @@ -403,10 +335,10 @@ const handleOpenInsert = () => { }; }; -const handleOpenUpdate = (proxy: Proxy) => { +const handleOpenUpdate = (proxy: FrpcProxy) => { editForm.value = clone(proxy); if (!editForm.value.fallbackTimeoutMs) { - editForm.value.fallbackTimeoutMs = defaultForm.value.fallbackTimeoutMs; + editForm.value.fallbackTimeoutMs = defaultForm.fallbackTimeoutMs; } edit.value = { title: "修改代理", @@ -414,17 +346,17 @@ const handleOpenUpdate = (proxy: Proxy) => { }; }; -const handleReversalUpdate = (proxy: Proxy) => { - console.log("更新", proxy); - ipcRenderer.send("proxy.updateProxyStatus", { - _id: proxy._id, - status: !proxy.status +const handleReversalUpdate = (proxy: FrpcProxy) => { + send(ipcRouters.PROXY.modifyProxyStatus, { + id: proxy._id, + status: proxy.status === 1 ? 0 : 1 }); }; const handleLoadLocalPorts = () => { loading.value.localPorts = 1; - ipcRenderer.send("local.getLocalPorts"); + // ipcRenderer.send("local.getLocalPorts"); + send(ipcRouters.PROXY.getLocalPorts); }; const handleSelectLocalPort = useDebounceFn((port: number) => { @@ -441,7 +373,7 @@ const handleOpenLocalPortDialog = () => { handleLoadLocalPorts(); }; -const allowCopyAccessAddress = (proxy: Proxy) => { +const allowCopyAccessAddress = (proxy: FrpcProxy) => { if ( (proxy.type === "http" || proxy.type === "https") && (proxy.customDomains.length < 1 || !proxy.customDomains[0]) @@ -460,7 +392,7 @@ const allowCopyAccessAddress = (proxy: Proxy) => { return true; }; -const handleCopyAccessAddress = (proxy: Proxy) => { +const handleCopyAccessAddress = (proxy: FrpcProxy) => { if ( (proxy.type === "http" || proxy.type === "https") && (proxy.customDomains.length < 1 || !proxy.customDomains[0]) @@ -584,26 +516,79 @@ const handleSelectFile = (type: number, ext: string[]) => { }; onMounted(() => { - handleInitHook(); handleLoadProxys(); - ipcRenderer.send("config.getConfig"); - ipcRenderer.on("Config.getConfig.hook", (event, args) => { - const { err, data } = args; - if (!err) { - if (data) { - frpcConfig.value = data; - } - } + + on(ipcRouters.PROXY.getAllProxies, data => { + console.log("allProxies", data); + loading.value.list--; + proxys.value = data; }); + + const insertOrUpdateHook = (message: string) => { + loading.value.form--; + // const { err } = args; + // if (!err) { + ElMessage({ + type: "success", + message: message + }); + handleResetForm(); + handleLoadProxys(); + edit.value.visible = false; + // } + }; + + on(ipcRouters.PROXY.createProxy, data => { + console.log("data", data); + insertOrUpdateHook("新增成功"); + }); + + on(ipcRouters.PROXY.modifyProxy, data => { + console.log("data", data); + insertOrUpdateHook("修改成功"); + }); + + on(ipcRouters.PROXY.deleteProxy, () => { + handleLoadProxys(); + ElMessage({ + type: "success", + message: "删除成功" + }); + }); + + on(ipcRouters.PROXY.modifyProxyStatus, () => { + ElMessage({ + type: "success", + message: "修改成功" + }); + // handleResetForm(); + handleLoadProxys(); + // edit.value.visible = false; + }); + + on(ipcRouters.PROXY.getLocalPorts, data => { + loading.value.localPorts--; + localPorts.value = data; + }); + + // ipcRenderer.send("config.getConfig"); + // ipcRenderer.on("Config.getConfig.hook", (event, args) => { + // const { err, data } = args; + // if (!err) { + // if (data) { + // frpcConfig.value = data; + // } + // } + // }); }); onUnmounted(() => { - ipcRenderer.removeAllListeners("Proxy.insertProxy.hook"); - ipcRenderer.removeAllListeners("Proxy.updateProxy.hook"); - ipcRenderer.removeAllListeners("Proxy.updateProxyStatus.hook"); - ipcRenderer.removeAllListeners("Proxy.deleteProxyById.hook"); - ipcRenderer.removeAllListeners("Proxy.getProxys.hook"); - ipcRenderer.removeAllListeners("local.getLocalPorts.hook"); + removeRouterListeners(ipcRouters.PROXY.createProxy); + removeRouterListeners(ipcRouters.PROXY.modifyProxy); + removeRouterListeners(ipcRouters.PROXY.deleteProxy); + removeRouterListeners(ipcRouters.PROXY.getAllProxies); + removeRouterListeners(ipcRouters.PROXY.modifyProxyStatus); + removeRouterListeners(ipcRouters.PROXY.getLocalPorts); });