import {app, ipcMain, Notification} from "electron"; import {Config, getConfig} from "../storage/config"; import {listProxy, Proxy} from "../storage/proxy"; import {getVersionById} from "../storage/version"; import treeKill from "tree-kill"; const fs = require("fs"); const path = require("path"); const {exec, spawn} = require("child_process"); const log = require('electron-log'); export let frpcProcess = null; const runningCmd = { commandPath: null, configPath: null }; let frpcStatusListener = null; /** * 获取选择版本的工作目录 * @param versionId 版本ID * @param callback */ const getFrpcVersionWorkerPath = ( versionId: string, callback: (workerPath: string) => void ) => { getVersionById(versionId, (err2, version) => { if (!err2) { if (version) { callback(version["frpcVersionPath"]); } } }); }; /** * 生成toml配置文件 * @param config * @param proxys */ const genTomlConfig = (config: Config, proxys: Proxy[]) => { const proxyToml = proxys.map(m => { let toml = ` [[proxies]] name = "${m.name}" type = "${m.type}" localIP = "${m.localIp}" localPort = ${m.localPort} `; switch (m.type) { case "tcp": case "udp": toml += `remotePort = ${m.remotePort}`; break; case "http": case "https": toml += `customDomains=[${m.customDomains.map(m => `"${m}"`)}]`; break; default: break; } 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}" ` : ""} ${config.transportHeartbeatInterval ? ` transport.heartbeatInterval = ${config.transportHeartbeatInterval} ` : ""} ${config.transportHeartbeatTimeout ? ` transport.heartbeatTimeout = ${config.transportHeartbeatTimeout} ` : ""} log.to = "frpc.log" log.level = "${config.logLevel}" log.maxDays = ${config.logMaxDays} webServer.addr = "127.0.0.1" webServer.port = 57400 transport.tls.enable = ${config.tlsConfigEnable} ${config.tlsConfigEnable ? ` transport.tls.certFile = "${config.tlsConfigCertFile}" transport.tls.keyFile = "${config.tlsConfigKeyFile}" transport.tls.trustedCaFile = "${config.tlsConfigTrustedCaFile}" transport.tls.serverName = "${config.tlsConfigServerName}" ` : ""} ${config.proxyConfigEnable ? ` transport.proxyURL = "${config.proxyConfigProxyUrl}" ` : ""} ${proxyToml.join("")} `; return toml; } /** * 生成ini配置 * @param config * @param proxys */ const genIniConfig = (config: Config, proxys: Proxy[]) => { const proxyIni = proxys.map(m => { let ini = ` [${m.name}] type = "${m.type}" local_ip = "${m.localIp}" local_port = ${m.localPort} `; switch (m.type) { case "tcp": case "udp": ini += `remote_port = ${m.remotePort}`; break; case "http": case "https": ini += `custom_domains=[${m.customDomains.map(m => `"${m}"`)}]`; 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} ` : ""} ${config.authMethod === 'multiuser' ? ` user = ${config.user} meta_token = ${config.metaToken} ` : ""} ${config.transportHeartbeatInterval ? ` heartbeat_interval = ${config.transportHeartbeatInterval} ` : ""} ${config.transportHeartbeatTimeout ? ` heartbeat_timeout = ${config.transportHeartbeatTimeout} ` : ""} log_file = "frpc.log" log_level = ${config.logLevel} log_max_days = ${config.logMaxDays} admin_addr = 127.0.0.1 admin_port = 57400 tls_enable = ${config.tlsConfigEnable} ${config.tlsConfigEnable ? ` tls_cert_file = ${config.tlsConfigCertFile} tls_key_file = ${config.tlsConfigKeyFile} tls_trusted_ca_file = ${config.tlsConfigTrustedCaFile} tls_server_name = ${config.tlsConfigServerName} ` : ""} ${config.proxyConfigEnable ? ` http_proxy = "${config.proxyConfigProxyUrl}" ` : ""} ${proxyIni.join("")} ` return ini; } /** * 生成配置文件 */ export const generateConfig = ( config: Config, callback: (configPath: string) => void ) => { listProxy((err3, proxys) => { if (!err3) { const {currentVersion} = config; let filename = null; let configContent = ""; if (currentVersion < 124395282) { // 版本小于v0.52.0 filename = "frp.ini"; configContent = genIniConfig(config, proxys) } else { filename = "frp.toml"; configContent = genTomlConfig(config, proxys) } const configPath = path.join(app.getPath("userData"), filename) log.info(`生成配置成功 配置路径:${configPath}`) fs.writeFile( configPath, // 配置文件目录 configContent, // 配置文件内容 {flag: "w"}, err => { if (!err) { callback(filename); } } ); } }); }; /** * 启动frpc子进程 * @param cwd * @param commandPath * @param configPath */ const startFrpcProcess = (commandPath: string, configPath: string) => { log.info(`启动frpc 目录:${app.getPath("userData")} 命令:${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 => { log.debug(`启动输出:${data}`) }); frpcProcess.stdout.on("error", data => { log.error(`启动错误:${data}`) stopFrpcProcess(() => { }) }); frpcStatusListener = setInterval(() => { const status = frpcProcessStatus() log.debug(`监听frpc子进程状态:${status}`) if (!status) { new Notification({ title: "Frpc Desktop", body: "连接已断开,请前往日志查看原因" }).show() clearInterval(frpcStatusListener) } }, 3000) }; /** * 重载frpc配置 */ export const reloadFrpcProcess = () => { if (frpcProcess && !frpcProcess.killed) { getConfig((err1, config) => { if (!err1) { if (config) { generateConfig(config, configPath => { const command = `${runningCmd.commandPath} reload -c ${configPath}`; log.info(`重载配置:${command}`) exec(command, { cwd: app.getPath("userData"), shell: true }); }); } } }); } }; /** * 停止frpc子进程 */ export const stopFrpcProcess = (callback?: () => void) => { if (frpcProcess) { treeKill(frpcProcess.pid, (error: Error) => { if (error) { log.error(`关闭frpc子进程失败 pid:${frpcProcess.pid} error:${error}`) callback() } else { log.info(`关闭frpc子进程成功`) frpcProcess = null clearInterval(frpcStatusListener) callback() } }) } else { callback() } } /** * 获取frpc子进程状态 */ export const frpcProcessStatus = () => { if (!frpcProcess) { return false; } try { // 发送信号给进程,如果进程存在,会正常返回 process.kill(frpcProcess.pid, 0); return true; } catch (error) { // 进程不存在,抛出异常 return false; } } /** * 启动frpc流程 * @param config */ export const startFrpWorkerProcess = async (config: Config) => { getFrpcVersionWorkerPath( config.currentVersion, (frpcVersionPath: string) => { if (frpcVersionPath) { generateConfig(config, configPath => { const platform = process.platform; if (platform === 'win32') { startFrpcProcess( path.join(frpcVersionPath, "frpc.exe"), configPath ); } else { startFrpcProcess( path.join(frpcVersionPath, "frpc"), configPath ); } }); } } ); } export const initFrpcApi = () => { ipcMain.handle("frpc.running", async (event, args) => { return frpcProcessStatus() }); ipcMain.on("frpc.start", async (event, args) => { getConfig((err1, config) => { if (!err1) { if (config) { startFrpWorkerProcess(config) } else { event.reply( "Home.frpc.start.error.hook", "请先前往设置页面,修改配置后再启动" ); } } }); }); ipcMain.on("frpc.stop", () => { if (frpcProcess && !frpcProcess.killed) { stopFrpcProcess(() => { }) } }); };