frpc-desktop/electron/api/config.ts

338 lines
12 KiB
TypeScript
Raw Normal View History

2025-01-06 16:14:52 +08:00
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";
2024-08-21 22:31:12 +08:00
import path from "path";
import fs from "fs";
2025-01-08 12:01:00 +08:00
import { logDebug, logError, logInfo, LogModule, logWarn } from "../utils/log";
2023-11-27 15:03:25 +08:00
2024-08-22 14:19:19 +08:00
const toml = require("@iarna/toml");
2025-01-06 16:14:52 +08:00
const { v4: uuidv4 } = require("uuid");
2024-08-05 23:36:33 +08:00
2024-08-22 14:19:19 +08:00
export const initConfigApi = win => {
2025-01-06 16:14:52 +08:00
ipcMain.on("config.saveConfig", async (event, args) => {
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "Attempting to save configuration.");
2025-01-06 16:14:52 +08:00
saveConfig(args, (err, numberOfUpdated, upsert) => {
if (!err) {
const start = args.systemSelfStart || false;
2025-01-08 12:01:00 +08:00
logDebug(LogModule.APP, "Startup status set to: " + start);
2025-01-06 16:14:52 +08:00
app.setLoginItemSettings({
openAtLogin: start, //win
openAsHidden: start //macOs
});
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "Configuration saved successfully.");
} else {
logError(LogModule.APP, `Error saving configuration: ${err}`);
2025-01-06 16:14:52 +08:00
}
event.reply("Config.saveConfig.hook", {
err: err,
numberOfUpdated: numberOfUpdated,
upsert: upsert
});
2023-11-27 15:03:25 +08:00
});
2025-01-06 16:14:52 +08:00
});
2023-11-27 15:03:25 +08:00
2025-01-06 16:14:52 +08:00
ipcMain.on("config.getConfig", async (event, args) => {
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "Requesting configuration.");
2025-01-06 16:14:52 +08:00
getConfig((err, doc) => {
2025-01-08 12:01:00 +08:00
if (err) {
logError(LogModule.APP, `Error retrieving configuration: ${err}`);
}
2025-01-06 16:14:52 +08:00
event.reply("Config.getConfig.hook", {
err: err,
data: doc
});
2023-11-27 15:03:25 +08:00
});
2025-01-06 16:14:52 +08:00
});
2023-11-27 15:03:25 +08:00
2025-01-06 16:14:52 +08:00
ipcMain.on("config.versions", event => {
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "Requesting version information.");
2025-01-06 16:14:52 +08:00
listVersion((err, doc) => {
2025-01-08 12:01:00 +08:00
if (err) {
logError(LogModule.APP, `Error retrieving version information: ${err}`);
}
2025-01-06 16:14:52 +08:00
event.reply("Config.versions.hook", {
err: err,
data: doc
});
2024-08-21 22:31:12 +08:00
});
2025-01-06 16:14:52 +08:00
});
2024-08-21 22:31:12 +08:00
2025-01-06 16:14:52 +08:00
ipcMain.on("config.hasConfig", event => {
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "Checking if configuration exists.");
2025-01-06 16:14:52 +08:00
getConfig((err, doc) => {
2025-01-08 12:01:00 +08:00
if (err) {
logError(LogModule.APP, `Error checking configuration: ${err}`);
}
2025-01-06 16:14:52 +08:00
event.reply("Config.getConfig.hook", {
err: err,
data: doc
});
2023-11-27 15:03:25 +08:00
});
2025-01-06 16:14:52 +08:00
});
2023-11-27 15:03:25 +08:00
2025-01-06 16:14:52 +08:00
ipcMain.on("config.exportConfig", async (event, args) => {
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "Attempting to export configuration.");
2025-01-06 16:14:52 +08:00
const result = await dialog.showOpenDialog({
properties: ["openDirectory"]
});
const outputDirectory = result.filePaths[0];
if (!outputDirectory) {
2025-01-08 12:01:00 +08:00
logWarn(LogModule.APP, "Export canceled by user.");
2025-01-06 16:14:52 +08:00
return;
}
logInfo(
LogModule.APP,
2025-01-08 12:01:00 +08:00
`Exporting configuration to directory ${outputDirectory} with type: ${args}`
);
2025-01-06 16:14:52 +08:00
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);
2024-08-21 22:31:12 +08:00
}
2025-01-06 16:14:52 +08:00
const configPath = path.join(
outputDirectory,
`frpc-desktop.${args}`
);
fs.writeFile(
configPath, // 配置文件目录
configContent, // 配置文件内容
{ flag: "w" },
err => {
2025-01-08 12:01:00 +08:00
if (err) {
logError(
LogModule.APP,
`Error writing configuration file: ${err}`
);
2025-01-06 16:14:52 +08:00
event.reply("config.exportConfig.hook", {
data: "导出错误",
err: err
});
2025-01-08 12:01:00 +08:00
} else {
logInfo(
LogModule.APP,
"Configuration exported successfully."
);
event.reply("Config.exportConfig.hook", {
data: {
configPath: configPath
}
});
2025-01-06 16:14:52 +08:00
}
}
);
2025-01-08 12:01:00 +08:00
} else {
logError(LogModule.APP, `Error listing proxies: ${err2}`);
2025-01-06 16:14:52 +08:00
}
});
2025-01-08 12:01:00 +08:00
} else {
logError(LogModule.APP, `Error retrieving configuration: ${err1}`);
2025-01-06 16:14:52 +08:00
}
2023-11-27 15:03:25 +08:00
});
2025-01-06 16:14:52 +08:00
});
2024-08-22 14:19:19 +08:00
2025-01-06 16:14:52 +08:00
const parseTomlConfig = (tomlPath: string) => {
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, `Parsing TOML configuration from ${tomlPath}`);
2025-01-06 16:14:52 +08:00
const importConfigPath = tomlPath;
const tomlData = fs.readFileSync(importConfigPath, "utf-8");
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "Configuration content read successfully.");
2025-01-06 16:14:52 +08:00
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
};
2025-01-06 16:14:52 +08:00
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,
2025-01-09 12:10:55 +08:00
fallbackTimeoutMs: m?.fallbackTimeoutMs || 500,
keepTunnelOpen: m?.keepTunnelOpen || false,
https2http: m?.https2http || false,
https2httpCaFile: m?.https2httpCaFile || "",
https2httpKeyFile: m?.https2httpKeyFile || ""
2025-01-06 16:14:52 +08:00
};
return rm;
});
frpcProxys = [...frpcProxys, ...frpcProxys1];
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "Parsed proxies from configuration.");
2025-01-06 16:14:52 +08:00
}
// 解析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,
2025-01-09 12:10:55 +08:00
fallbackTimeoutMs: m?.fallbackTimeoutMs || 500,
keepTunnelOpen: m?.keepTunnelOpen || false,
https2http: m?.https2http || false,
https2httpCaFile: m?.https2httpCaFile || "",
https2httpKeyFile: m?.https2httpKeyFile || ""
2025-01-06 16:14:52 +08:00
};
return rm;
});
frpcProxys = [...frpcProxys, ...frpcProxys2];
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "Parsed visitors from configuration.");
2025-01-06 16:14:52 +08:00
}
if (targetConfig) {
clearConfig(() => {
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "Clearing existing configuration.");
2025-01-06 16:14:52 +08:00
saveConfig(targetConfig);
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "New configuration saved.");
2025-01-06 16:14:52 +08:00
});
}
if (frpcProxys && frpcProxys.length > 0) {
clearProxy(() => {
frpcProxys.forEach(f => {
insertProxy(f, err => {
2025-01-08 12:01:00 +08:00
if (err) {
logError(LogModule.APP, `Error inserting proxy: ${err}`);
} else {
logInfo(LogModule.APP, `Inserted proxy: ${JSON.stringify(f)}`);
}
2025-01-06 16:14:52 +08:00
});
2024-08-22 14:19:19 +08:00
});
2025-01-06 16:14:52 +08:00
});
}
};
2025-01-06 16:14:52 +08:00
ipcMain.on("config.importConfig", async (event, args) => {
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "Attempting to import configuration.");
2025-01-06 16:14:52 +08:00
const result = await dialog.showOpenDialog(win, {
properties: ["openFile"],
filters: [
{ name: "FrpcConfig Files", extensions: ["toml", "ini"] } // 允许选择的文件类型
]
});
if (result.canceled) {
2025-01-08 12:01:00 +08:00
logWarn(LogModule.APP, "Import canceled by user.");
2025-01-06 16:14:52 +08:00
return;
} else {
const filePath = result.filePaths[0];
const fileExtension = path.extname(filePath); // 获取文件后缀名
logWarn(
LogModule.APP,
`Importing file ${filePath} with extension ${fileExtension}`
);
2025-01-06 16:14:52 +08:00
if (fileExtension === ".toml") {
parseTomlConfig(filePath);
event.reply("Config.importConfig.hook", {
success: true
});
} else {
2025-01-08 12:01:00 +08:00
logError(
LogModule.APP,
`Import failed, unsupported file format: ${fileExtension}`
);
2025-01-06 16:14:52 +08:00
event.reply("Config.importConfig.hook", {
success: false,
data: `导入失败,暂不支持 ${fileExtension} 格式文件`
2024-08-22 14:19:19 +08:00
});
2025-01-06 16:14:52 +08:00
}
}
});
ipcMain.on("config.clearAll", async (event, args) => {
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "Clearing all configurations.");
2025-01-06 16:14:52 +08:00
stopFrpcProcess(() => {
clearConfig();
clearProxy();
clearVersion();
event.reply("Config.clearAll.hook", {});
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "All configurations cleared.");
});
2025-01-06 16:14:52 +08:00
});
2024-08-22 14:19:19 +08:00
2025-01-06 16:14:52 +08:00
ipcMain.on("config.openDataFolder", async (event, args) => {
const userDataPath = app.getPath("userData");
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "Attempting to open data folder.");
2025-01-06 16:14:52 +08:00
shell.openPath(userDataPath).then(errorMessage => {
if (errorMessage) {
2025-01-08 12:01:00 +08:00
logError(LogModule.APP, `Failed to open data folder: ${errorMessage}`);
2025-01-06 16:14:52 +08:00
event.reply("Config.openDataFolder.hook", false);
} else {
2025-01-08 12:01:00 +08:00
logInfo(LogModule.APP, "Data folder opened successfully.");
2025-01-06 16:14:52 +08:00
event.reply("Config.openDataFolder.hook", true);
}
2024-08-22 14:19:19 +08:00
});
2025-01-06 16:14:52 +08:00
});
2023-11-27 15:03:25 +08:00
};