frpc-desktop/electron/api/frpc.ts

528 lines
11 KiB
TypeScript
Raw Normal View History

import { app, ipcMain, Notification } from "electron";
import { getConfig } from "../storage/config";
import { listProxy } from "../storage/proxy";
import { getVersionById } from "../storage/version";
2023-12-01 14:17:14 +08:00
import treeKill from "tree-kill";
2023-11-27 15:03:25 +08:00
const fs = require("fs");
const path = require("path");
const { exec, spawn } = require("child_process");
const log = require("electron-log");
2023-11-27 15:03:25 +08:00
export let frpcProcess = null;
const runningCmd = {
commandPath: null,
configPath: null
2023-11-27 15:03:25 +08:00
};
2023-12-01 15:16:16 +08:00
let frpcStatusListener = null;
2023-11-27 15:03:25 +08:00
/**
*
* @param versionId ID
* @param callback
*/
const getFrpcVersionWorkerPath = (
versionId: number,
callback: (workerPath: string) => void
2023-11-27 15:03:25 +08:00
) => {
getVersionById(versionId, (err2, version) => {
if (!err2) {
if (version) {
callback(version["frpcVersionPath"]);
}
}
});
2023-11-27 15:03:25 +08:00
};
2024-09-07 17:24:51 +08:00
const isRangePort = (m: Proxy) => {
return (
(m.localPort.indexOf("-") !== -1 || m.localPort.indexOf(",") !== -1) &&
(m.type === "tcp" || m.type === "udp")
);
};
2023-11-27 15:03:25 +08:00
/**
2024-07-17 14:21:31 +08:00
* toml配置文件
* @param config
* @param proxys
2023-11-27 15:03:25 +08:00
*/
2024-08-21 22:31:12 +08:00
export const genTomlConfig = (config: FrpConfig, proxys: Proxy[]) => {
const proxyToml = proxys.map(m => {
2024-09-07 17:24:51 +08:00
const rangePort = isRangePort(m);
let toml = `
2024-09-07 17:01:49 +08:00
${
rangePort
? `{{- range $_, $v := parseNumberRangePair "${m.localPort}" "${m.remotePort}" }}`
: ""
}
2024-08-17 00:43:23 +08:00
[[${m.type === "stcp" && m.stcpModel === "visitors" ? "visitors" : "proxies"}]]
2024-09-07 17:01:49 +08:00
${rangePort ? "" : `name = "${m.name}"\n`}
2023-11-27 15:03:25 +08:00
type = "${m.type}"
`;
2024-09-07 17:01:49 +08:00
switch (m.type) {
case "tcp":
case "udp":
2024-09-07 17:01:49 +08:00
if (rangePort) {
toml += `
name = "${m.name}-{{ $v.First }}"
localPort = {{ $v.First }}
remotePort = {{ $v.Second }}
`;
} else {
toml += `
2023-11-27 15:03:25 +08:00
localIP = "${m.localIp}"
2024-09-07 17:01:49 +08:00
localPort = ${m.localPort}
remotePort = ${m.remotePort}
2023-11-27 15:03:25 +08:00
`;
2024-09-07 17:01:49 +08:00
}
break;
case "http":
case "https":
toml += `
localIP = "${m.localIp}"
localPort = ${m.localPort}
customDomains=[${m.customDomains.map(m => `"${m}"`)}]
`;
break;
case "stcp":
if (m.stcpModel === "visitors") {
// 访问者
toml += `
serverName = "${m.serverName}"
bindAddr = "${m.bindAddr}"
bindPort = ${m.bindPort}
`;
} else if (m.stcpModel === "visited") {
// 被访问者
toml += `
localIP = "${m.localIp}"
localPort = ${m.localPort}`;
2024-07-17 14:21:31 +08:00
}
toml += `
secretKey="${m.secretKey}"
`;
2024-08-17 00:43:23 +08:00
break;
default:
break;
}
2023-11-27 15:03:25 +08:00
2024-09-07 17:01:49 +08:00
if (rangePort) {
toml += `{{- end }}`;
}
return toml;
});
const toml = `
2023-11-27 15:03:25 +08:00
serverAddr = "${config.serverAddr}"
serverPort = ${config.serverPort}
${
config.authMethod === "token"
? `
2024-08-05 22:31:32 +08:00
auth.method = "token"
2023-11-27 15:03:25 +08:00
auth.token = "${config.authToken}"
`
: ""
}
${
config.authMethod === "multiuser"
? `
2024-08-06 15:43:40 +08:00
user = "${config.user}"
metadatas.token = "${config.metaToken}"
`
: ""
}
${
config.transportHeartbeatInterval
? `
transport.heartbeatInterval = ${config.transportHeartbeatInterval}
`
: ""
}
${
config.transportHeartbeatTimeout
? `
transport.heartbeatTimeout = ${config.transportHeartbeatTimeout}
`
: ""
}
2024-08-05 22:31:32 +08:00
2023-11-27 15:03:25 +08:00
log.to = "frpc.log"
log.level = "${config.logLevel}"
log.maxDays = ${config.logMaxDays}
2023-11-27 15:03:25 +08:00
webServer.addr = "127.0.0.1"
webServer.port = 57400
2023-12-01 14:17:14 +08:00
transport.tls.enable = ${config.tlsConfigEnable}
${
2024-09-04 13:08:16 +08:00
config.tlsConfigEnable && config.tlsConfigCertFile
? `
2023-12-01 14:17:14 +08:00
transport.tls.certFile = "${config.tlsConfigCertFile}"
2024-09-04 13:08:16 +08:00
`
: ""
}
${
config.tlsConfigEnable && config.tlsConfigKeyFile
? `
2023-12-01 14:17:14 +08:00
transport.tls.keyFile = "${config.tlsConfigKeyFile}"
2024-09-04 13:08:16 +08:00
`
: ""
}
${
config.tlsConfigEnable && config.tlsConfigTrustedCaFile
? `
2023-12-01 14:17:14 +08:00
transport.tls.trustedCaFile = "${config.tlsConfigTrustedCaFile}"
2024-09-04 13:08:16 +08:00
`
: ""
}
${
config.tlsConfigEnable && config.tlsConfigServerName
? `
2023-12-01 14:17:14 +08:00
transport.tls.serverName = "${config.tlsConfigServerName}"
`
2024-09-04 13:08:16 +08:00
: ""
}
${
config.proxyConfigEnable
? `
2024-01-20 11:24:59 +08:00
transport.proxyURL = "${config.proxyConfigProxyUrl}"
`
: ""
}
2023-12-01 14:17:14 +08:00
${proxyToml.join("")}
2023-11-27 15:03:25 +08:00
`;
return toml;
};
2024-07-17 14:21:31 +08:00
/**
* ini配置
* @param config
* @param proxys
*/
2024-08-21 22:31:12 +08:00
export const genIniConfig = (config: FrpConfig, proxys: Proxy[]) => {
const proxyIni = proxys.map(m => {
2024-09-07 17:24:51 +08:00
const rangePort = isRangePort(m);
let ini = `
2024-09-07 17:24:51 +08:00
[${rangePort ? 'range:' : ''}${m.name}]
2024-07-17 14:21:31 +08:00
type = "${m.type}"
`;
switch (m.type) {
case "tcp":
case "udp":
2024-08-17 00:43:23 +08:00
ini += `
local_ip = "${m.localIp}"
local_port = ${m.localPort}
remote_port = ${m.remotePort}
`;
break;
case "http":
case "https":
2024-08-17 00:43:23 +08:00
ini += `
local_ip = "${m.localIp}"
local_port = ${m.localPort}
custom_domains=[${m.customDomains.map(m => `"${m}"`)}]
`;
break;
case "stcp":
if (m.stcpModel === "visitors") {
// 访问者
ini += `
role = visitor
server_name = "${m.serverName}"
bind_addr = "${m.bindAddr}"
bind_port = ${m.bindPort}
`;
} else if (m.stcpModel === "visited") {
// 被访问者
ini += `
local_ip = "${m.localIp}"
local_port = ${m.localPort}`;
}
ini += `
sk="${m.secretKey}"
`;
break;
default:
break;
}
2024-07-17 14:21:31 +08:00
return ini;
});
const ini = `
2024-07-17 14:21:31 +08:00
[common]
server_addr = ${config.serverAddr}
server_port = ${config.serverPort}
${
config.authMethod === "token"
? `
2024-08-05 22:31:32 +08:00
authentication_method = ${config.authMethod}
token = ${config.authToken}
`
: ""
}
${
config.authMethod === "multiuser"
? `
2024-08-05 22:31:32 +08:00
user = ${config.user}
meta_token = ${config.metaToken}
`
: ""
}
${
config.transportHeartbeatInterval
? `
heartbeat_interval = ${config.transportHeartbeatInterval}
`
: ""
}
${
config.transportHeartbeatTimeout
? `
heartbeat_timeout = ${config.transportHeartbeatTimeout}
`
: ""
}
2024-07-17 14:21:31 +08:00
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}
2024-09-04 13:08:16 +08:00
${
2024-09-04 13:08:16 +08:00
config.tlsConfigEnable && config.tlsConfigCertFile
? `
2024-07-17 14:21:31 +08:00
tls_cert_file = ${config.tlsConfigCertFile}
2024-09-04 13:08:16 +08:00
`
: ""
}
${
config.tlsConfigEnable && config.tlsConfigKeyFile
? `
2024-07-17 14:21:31 +08:00
tls_key_file = ${config.tlsConfigKeyFile}
2024-09-04 13:08:16 +08:00
`
: ""
}
${
config.tlsConfigEnable && config.tlsConfigTrustedCaFile
? `
2024-07-17 14:21:31 +08:00
tls_trusted_ca_file = ${config.tlsConfigTrustedCaFile}
2024-09-04 13:08:16 +08:00
`
: ""
}
${
config.tlsConfigEnable && config.tlsConfigServerName
? `
2024-07-17 14:21:31 +08:00
tls_server_name = ${config.tlsConfigServerName}
`
2024-09-04 13:08:16 +08:00
: ""
}
${
config.proxyConfigEnable
? `
2024-07-17 14:21:31 +08:00
http_proxy = "${config.proxyConfigProxyUrl}"
`
: ""
}
2024-07-17 14:21:31 +08:00
${proxyIni.join("")}
`;
return ini;
};
2024-07-17 14:21:31 +08:00
/**
*
*/
export const generateConfig = (
config: FrpConfig,
callback: (configPath: string) => void
2024-07-17 14:21:31 +08:00
) => {
listProxy((err3, proxys) => {
if (!err3) {
const { currentVersion } = config;
let filename = null;
let configContent = "";
2024-09-05 10:28:55 +08:00
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";
2024-09-05 10:28:55 +08:00
configContent = genIniConfig(config, filtered);
} else {
filename = "frp.toml";
2024-09-05 10:28:55 +08:00
configContent = genTomlConfig(config, filtered);
}
const configPath = path.join(app.getPath("userData"), filename);
log.info(`生成配置成功 配置路径:${configPath}`);
fs.writeFile(
configPath, // 配置文件目录
configContent, // 配置文件内容
{ flag: "w" },
err => {
if (!err) {
callback(filename);
}
2023-11-27 15:03:25 +08:00
}
);
}
});
2023-11-27 15:03:25 +08:00
};
/**
* 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();
2024-08-17 00:48:23 +08:00
log.debug(`监听frpc子进程状态${status} ${frpcStatusListener}`);
if (!status) {
new Notification({
title: "Frpc Desktop",
body: "连接已断开,请前往日志查看原因"
}).show();
clearInterval(frpcStatusListener);
}
}, 3000);
2023-11-27 15:03:25 +08:00
};
2023-12-01 15:16:16 +08:00
/**
* frpc配置
*/
2023-11-27 15:03:25 +08:00
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
});
});
}
}
});
}
2023-11-27 15:03:25 +08:00
};
2023-12-01 15:16:16 +08:00
/**
* frpc子进程
*/
2024-01-20 11:24:59 +08:00
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();
}
};
2023-12-01 14:17:14 +08:00
2023-12-01 15:16:16 +08:00
/**
* frpc子进程状态
*/
export const frpcProcessStatus = () => {
if (!frpcProcess) {
return false;
}
try {
// 发送信号给进程,如果进程存在,会正常返回
process.kill(frpcProcess.pid, 0);
return true;
} catch (error) {
// 进程不存在,抛出异常
return false;
}
};
2023-12-01 15:16:16 +08:00
2024-07-17 14:21:31 +08:00
/**
* frpc流程
* @param config
*/
export const startFrpWorkerProcess = async (config: FrpConfig) => {
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);
2024-07-17 14:21:31 +08:00
}
});
}
});
};
2023-12-01 15:16:16 +08:00
2023-11-27 15:03:25 +08:00
export const initFrpcApi = () => {
ipcMain.handle("frpc.running", async (event, args) => {
return frpcProcessStatus();
});
2023-11-27 15:03:25 +08:00
ipcMain.on("frpc.start", async (event, args) => {
getConfig((err1, config) => {
if (!err1) {
2024-08-22 14:31:19 +08:00
if (!config) {
event.reply(
"Home.frpc.start.error.hook",
"请先前往设置页面,修改配置后再启动"
);
return;
}
if (!config.currentVersion) {
event.reply(
"Home.frpc.start.error.hook",
"请先前往设置页面,修改配置后再启动"
);
2024-08-22 14:31:19 +08:00
return;
}
2024-08-22 14:31:19 +08:00
startFrpWorkerProcess(config);
}
});
});
ipcMain.on("frpc.stop", () => {
if (frpcProcess && !frpcProcess.killed) {
stopFrpcProcess(() => {});
}
});
2023-11-27 15:03:25 +08:00
};