Compare commits
31 Commits
main
...
develop213
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2238afa420 | ||
![]() |
50adc33aef | ||
![]() |
e6992f4e4c | ||
![]() |
dfbd9c93bd | ||
![]() |
62151d613e | ||
![]() |
20f1fe8318 | ||
![]() |
f0d8c20f4d | ||
![]() |
f77605a73d | ||
![]() |
ce709fe5e7 | ||
![]() |
7515726a71 | ||
![]() |
1882260096 | ||
![]() |
91afd0f457 | ||
![]() |
efd68fb453 | ||
![]() |
fb2846c0b5 | ||
![]() |
cd5122c6cf | ||
![]() |
7da8bf025b | ||
![]() |
7a58500a92 | ||
![]() |
711af4a31a | ||
![]() |
64c9623019 | ||
![]() |
3373e30331 | ||
![]() |
20a8208240 | ||
![]() |
b5fd0c6747 | ||
![]() |
9946b50d5d | ||
![]() |
ff8b01c360 | ||
![]() |
6d9f9269b7 | ||
![]() |
9f46ea781d | ||
![]() |
2edbbcb871 | ||
![]() |
8d56faeb80 | ||
![]() |
3ef92a8af9 | ||
![]() |
183a86d10b | ||
![]() |
5451f5bc70 |
5
.github/workflows/webpack.yml
vendored
5
.github/workflows/webpack.yml
vendored
@ -24,5 +24,6 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
npm install
|
||||
npx run release
|
||||
npm i -g pnpm
|
||||
pnpm install
|
||||
pnpm build:electron
|
||||
|
@ -1,27 +0,0 @@
|
||||
import { app, ipcMain, shell } from "electron";
|
||||
import { logError, logInfo, LogModule, logWarn } from "../utils/log";
|
||||
|
||||
export const initCommonApi = () => {
|
||||
ipcMain.on("common.openUrl", async (event, args) => {
|
||||
if (args) {
|
||||
logInfo(LogModule.APP, `Attempting to open URL: ${args}`);
|
||||
try {
|
||||
await shell.openExternal(args);
|
||||
logInfo(LogModule.APP, `Successfully opened URL: ${args}`);
|
||||
} catch (error) {
|
||||
logError(
|
||||
LogModule.APP,
|
||||
`Failed to open URL: ${args}. Error: ${error.message}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
logWarn(LogModule.APP, "No URL provided to open.");
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on("common.relaunch", () => {
|
||||
logInfo(LogModule.APP, "Application is relaunching.");
|
||||
app.relaunch();
|
||||
app.quit();
|
||||
});
|
||||
};
|
@ -1,337 +0,0 @@
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
@ -1,21 +0,0 @@
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
@ -1,722 +0,0 @@
|
||||
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.");
|
||||
}
|
||||
});
|
||||
};
|
@ -1,598 +0,0 @@
|
||||
import electron, {
|
||||
app,
|
||||
dialog,
|
||||
BrowserWindow,
|
||||
ipcMain,
|
||||
net,
|
||||
shell
|
||||
} from "electron";
|
||||
import {
|
||||
deleteVersionById,
|
||||
getVersionById,
|
||||
insertVersion,
|
||||
listVersion
|
||||
} from "../storage/version";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const zlib = require("zlib");
|
||||
const { download } = require("electron-dl");
|
||||
const AdmZip = require("adm-zip");
|
||||
import frpReleasesJson from "../json/frp-releases.json";
|
||||
import frpChecksums from "../json/frp_all_sha256_checksums.json";
|
||||
import { logInfo, logError, LogModule, logDebug, logWarn } from "../utils/log";
|
||||
import { calculateFileChecksum, formatBytes } from "../utils/file";
|
||||
import { el } from "element-plus/es/locale";
|
||||
|
||||
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", error => {
|
||||
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: `导入失败,无法识别文件`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
@ -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
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
import { app, ipcMain, shell } from "electron";
|
||||
import { logInfo, logError, LogModule } from "../utils/log";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
export const initLoggerApi = () => {
|
||||
const logPath = path.join(app.getPath("userData"), "frpc.log");
|
||||
|
||||
const readLogger = (callback: (content: string) => void) => {
|
||||
fs.readFile(logPath, "utf-8", (error, data) => {
|
||||
if (!error) {
|
||||
logInfo(LogModule.APP, "Log file read successfully.");
|
||||
callback(data);
|
||||
} else {
|
||||
logError(LogModule.APP, `Error reading log file: ${error.message}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ipcMain.on("logger.getLog", async (event, args) => {
|
||||
logInfo(LogModule.APP, "Received request to get log.");
|
||||
readLogger(content => {
|
||||
event.reply("Logger.getLog.hook", content);
|
||||
logInfo(LogModule.APP, "Log data sent to client.");
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on("logger.update", (event, args) => {
|
||||
logInfo(LogModule.APP, "Watching log file for changes.");
|
||||
fs.watch(logPath, (eventType, filename) => {
|
||||
if (eventType === "change") {
|
||||
logInfo(LogModule.APP, "Log file changed, reading new content.");
|
||||
readLogger(content => {
|
||||
event.reply("Logger.update.hook", content);
|
||||
logInfo(LogModule.APP, "Updated log data sent to client.");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on("logger.openLog", (event, args) => {
|
||||
logInfo(LogModule.APP, "Attempting to open log file.");
|
||||
shell.openPath(logPath).then((errorMessage) => {
|
||||
if (errorMessage) {
|
||||
logError(LogModule.APP, `Failed to open Logger: ${errorMessage}`);
|
||||
event.reply("Logger.openLog.hook", false);
|
||||
} else {
|
||||
logInfo(LogModule.APP, "Logger opened successfully.");
|
||||
event.reply("Logger.openLog.hook", true);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
@ -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
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
@ -1,81 +0,0 @@
|
||||
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("手动检查更新一次")
|
||||
|
||||
|
||||
}
|
21
electron/controller/BaseController.ts
Normal file
21
electron/controller/BaseController.ts
Normal file
@ -0,0 +1,21 @@
|
||||
class BaseController {
|
||||
// success<T>(data: any, message?: string) {
|
||||
// const resp: ApiResponse<T> = {
|
||||
// success: true,
|
||||
// data: data,
|
||||
// message: message || "successful."
|
||||
// };
|
||||
// return resp;
|
||||
// }
|
||||
//
|
||||
// fail(message?: string) {
|
||||
// const resp: ApiResponse<any> = {
|
||||
// success: false,
|
||||
// data: null,
|
||||
// message: message || "internal error."
|
||||
// };
|
||||
// return resp;
|
||||
// }
|
||||
}
|
||||
|
||||
export default BaseController;
|
175
electron/controller/ConfigController.ts
Normal file
175
electron/controller/ConfigController.ts
Normal file
@ -0,0 +1,175 @@
|
||||
import BaseController from "./BaseController";
|
||||
import ServerService from "../service/ServerService";
|
||||
import PathUtils from "../utils/PathUtils";
|
||||
import fs from "fs";
|
||||
import FrpcProcessService from "../service/FrpcProcessService";
|
||||
import SystemService from "../service/SystemService";
|
||||
import moment from "moment";
|
||||
import ResponseUtils from "../utils/ResponseUtils";
|
||||
import { BrowserWindow, dialog } from "electron";
|
||||
import Logger from "../core/Logger";
|
||||
import BeanFactory from "../core/BeanFactory";
|
||||
|
||||
class ConfigController extends BaseController {
|
||||
private readonly _serverService: ServerService;
|
||||
private readonly _systemService: SystemService;
|
||||
private readonly _frpcProcessService: FrpcProcessService;
|
||||
|
||||
constructor(
|
||||
serverService: ServerService,
|
||||
systemService: SystemService,
|
||||
frpcProcessService: FrpcProcessService
|
||||
) {
|
||||
super();
|
||||
this._serverService = serverService;
|
||||
this._systemService = systemService;
|
||||
this._frpcProcessService = frpcProcessService;
|
||||
}
|
||||
|
||||
saveConfig(req: ControllerParam) {
|
||||
this._serverService
|
||||
.saveServerConfig(req.args)
|
||||
.then(() => {
|
||||
req.event.reply(req.channel, ResponseUtils.success());
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("ConfigController.saveConfig", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
getServerConfig(req: ControllerParam) {
|
||||
this._serverService
|
||||
.getServerConfig()
|
||||
.then(data => {
|
||||
req.event.reply(req.channel, ResponseUtils.success(data));
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("ConfigController.getServerConfig", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
openAppData(req: ControllerParam) {
|
||||
this._systemService
|
||||
.openLocalPath(PathUtils.getAppData())
|
||||
.then(data => {
|
||||
req.event.reply(req.channel, ResponseUtils.success(data));
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("ConfigController.openAppData", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
resetAllConfig(req: ControllerParam) {
|
||||
// await this._serverDao.truncate();
|
||||
// await this._proxyDao.truncate();
|
||||
// await this._versionDao.truncate();
|
||||
this._frpcProcessService
|
||||
.stopFrpcProcess()
|
||||
.then(() => {
|
||||
fs.rmSync(PathUtils.getDataBaseStoragePath(), {
|
||||
recursive: true,
|
||||
force: true
|
||||
});
|
||||
|
||||
fs.rmSync(PathUtils.getDownloadStoragePath(), {
|
||||
recursive: true,
|
||||
force: true
|
||||
});
|
||||
|
||||
fs.rmSync(PathUtils.getVersionStoragePath(), {
|
||||
recursive: true,
|
||||
force: true
|
||||
});
|
||||
|
||||
fs.rmSync(PathUtils.getFrpcLogStoragePath(), {
|
||||
recursive: true,
|
||||
force: true
|
||||
});
|
||||
req.event.reply(req.channel, ResponseUtils.success());
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("ConfigController.resetAllConfig", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
exportConfig(req: ControllerParam) {
|
||||
dialog
|
||||
.showOpenDialog({
|
||||
properties: ["openDirectory"]
|
||||
})
|
||||
.then(result => {
|
||||
if (result.canceled) {
|
||||
req.event.reply(
|
||||
req.channel,
|
||||
ResponseUtils.success({
|
||||
canceled: true,
|
||||
path: ""
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const path = `${result.filePaths[0]}/frpc-${moment(new Date()).format(
|
||||
"YYYYMMDDhhmmss"
|
||||
)}.toml`;
|
||||
this._serverService.genTomlConfig(path).then(() => {
|
||||
req.event.reply(
|
||||
req.channel,
|
||||
ResponseUtils.success({
|
||||
canceled: false,
|
||||
path: path
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("ConfigController.exportConfig", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
importTomlConfig(req: ControllerParam) {
|
||||
const win: BrowserWindow = BeanFactory.getBean("win");
|
||||
dialog
|
||||
.showOpenDialog(win, {
|
||||
properties: ["openFile"],
|
||||
filters: [{ name: "Frpc Toml ConfigFile", extensions: ["toml"] }]
|
||||
})
|
||||
.then(result => {
|
||||
if (result.canceled) {
|
||||
req.event.reply(
|
||||
req.channel,
|
||||
ResponseUtils.success({
|
||||
canceled: true,
|
||||
path: ""
|
||||
})
|
||||
);
|
||||
} else {
|
||||
req.event.reply(
|
||||
req.channel,
|
||||
ResponseUtils.success({
|
||||
canceled: false,
|
||||
path: ""
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
// if (result.canceled) {
|
||||
// } else {
|
||||
// }
|
||||
// this._serverService
|
||||
// .importTomlConfig()
|
||||
// .then(() => {
|
||||
// req.event.reply(req.channel, ResponseUtils.success());
|
||||
// })
|
||||
// .catch((err: Error) => {
|
||||
// Logger.error("ConfigController.importTomlConfig", err);
|
||||
// req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
export default ConfigController;
|
44
electron/controller/LaunchController.ts
Normal file
44
electron/controller/LaunchController.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import BaseController from "./BaseController";
|
||||
import FrpcProcessService from "../service/FrpcProcessService";
|
||||
import ResponseUtils from "../utils/ResponseUtils";
|
||||
import Logger from "../core/Logger";
|
||||
|
||||
class LaunchController extends BaseController {
|
||||
private readonly _frpcProcessService: FrpcProcessService;
|
||||
|
||||
constructor(frpcProcessService: FrpcProcessService) {
|
||||
super();
|
||||
this._frpcProcessService = frpcProcessService;
|
||||
}
|
||||
|
||||
launch(req: ControllerParam) {
|
||||
this._frpcProcessService
|
||||
.startFrpcProcess()
|
||||
.then(r => {
|
||||
req.event.reply(req.channel, ResponseUtils.success());
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("LaunchController.launch", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
terminate(req: ControllerParam) {
|
||||
this._frpcProcessService
|
||||
.stopFrpcProcess()
|
||||
.then(r => {
|
||||
req.event.reply(req.channel, ResponseUtils.success());
|
||||
})
|
||||
.catch(err => {
|
||||
Logger.error("LaunchController.terminate", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
getStatus(req: ControllerParam) {
|
||||
const running = this._frpcProcessService.isRunning();
|
||||
req.event.reply(req.channel, ResponseUtils.success(running));
|
||||
}
|
||||
}
|
||||
|
||||
export default LaunchController;
|
50
electron/controller/LogController.ts
Normal file
50
electron/controller/LogController.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import BaseController from "./BaseController";
|
||||
import LogService from "../service/LogService";
|
||||
import ResponseUtils from "../utils/ResponseUtils";
|
||||
import Logger from "../core/Logger";
|
||||
|
||||
class LogController extends BaseController {
|
||||
private readonly _logService: LogService;
|
||||
|
||||
constructor(logService: LogService) {
|
||||
super();
|
||||
this._logService = logService;
|
||||
}
|
||||
|
||||
getFrpLogContent(req: ControllerParam) {
|
||||
this._logService
|
||||
.getFrpLogContent()
|
||||
.then(data => {
|
||||
req.event.reply(req.channel, ResponseUtils.success(data));
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("LogController.getFrpLogContent", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
// watchFrpcLogContent(req: ControllerRequest) {
|
||||
// this._logService.watchFrpcLog().then(data => {
|
||||
// console.log('reply watch', data);
|
||||
// req.event.reply(req.reply, this.ResponseUtils.success(data));
|
||||
// });
|
||||
// }
|
||||
|
||||
openFrpcLogFile(req: ControllerParam) {
|
||||
this._logService
|
||||
.openFrpcLogFile()
|
||||
.then(data => {
|
||||
if (data) {
|
||||
ResponseUtils.success();
|
||||
} else {
|
||||
// ResponseUtils.fail();
|
||||
}
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("LogController.openFrpcLogFile", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default LogController;
|
90
electron/controller/ProxyController.ts
Normal file
90
electron/controller/ProxyController.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import BaseController from "./BaseController";
|
||||
import ProxyService from "../service/ProxyService";
|
||||
import ResponseUtils from "../utils/ResponseUtils";
|
||||
import ProxyRepository from "../repository/ProxyRepository";
|
||||
import Logger from "../core/Logger";
|
||||
|
||||
class ProxyController extends BaseController {
|
||||
private readonly _proxyService: ProxyService;
|
||||
private readonly _proxyDao: ProxyRepository;
|
||||
|
||||
constructor(proxyService: ProxyService, proxyDao: ProxyRepository) {
|
||||
super();
|
||||
this._proxyService = proxyService;
|
||||
this._proxyDao = proxyDao;
|
||||
}
|
||||
|
||||
createProxy(req: ControllerParam) {
|
||||
this._proxyService
|
||||
.insertProxy(req.args)
|
||||
.then(data => {
|
||||
req.event.reply(req.channel, ResponseUtils.success(data));
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("ProxyController.createProxy", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
modifyProxy(req: ControllerParam) {
|
||||
this._proxyService
|
||||
.updateProxy(req.args)
|
||||
.then(data => {
|
||||
req.event.reply(req.channel, ResponseUtils.success(data));
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("ProxyController.modifyProxy", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
getAllProxies(req: ControllerParam) {
|
||||
this._proxyDao
|
||||
.findAll()
|
||||
.then(data => {
|
||||
req.event.reply(req.channel, ResponseUtils.success(data));
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("ProxyController.getAllProxies", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
deleteProxy(req: ControllerParam) {
|
||||
this._proxyService
|
||||
.deleteProxy(req.args)
|
||||
.then(data => {
|
||||
req.event.reply(req.channel, ResponseUtils.success(data));
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("ProxyController.deleteProxy", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
modifyProxyStatus(req: ControllerParam) {
|
||||
this._proxyDao
|
||||
.updateProxyStatus(req.args.id, req.args.status)
|
||||
.then(() => {
|
||||
req.event.reply(req.channel, ResponseUtils.success());
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("ProxyController.modifyProxyStatus", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
getLocalPorts(req: ControllerParam) {
|
||||
this._proxyService
|
||||
.getLocalPorts()
|
||||
.then(data => {
|
||||
req.event.reply(req.channel, ResponseUtils.success(data));
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("ProxyController.getLocalPorts", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default ProxyController;
|
90
electron/controller/SystemController.ts
Normal file
90
electron/controller/SystemController.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import SystemService from "../service/SystemService";
|
||||
import ResponseUtils from "../utils/ResponseUtils";
|
||||
import PathUtils from "../utils/PathUtils";
|
||||
import { BrowserWindow, dialog } from "electron";
|
||||
import BeanFactory from "../core/BeanFactory";
|
||||
import Logger from "../core/Logger";
|
||||
|
||||
class SystemController {
|
||||
private readonly _systemService: SystemService;
|
||||
|
||||
constructor(systemService: SystemService) {
|
||||
this._systemService = systemService;
|
||||
}
|
||||
|
||||
openUrl(req: ControllerParam) {
|
||||
this._systemService
|
||||
.openUrl(req.args.url)
|
||||
.then(() => {
|
||||
req.event.reply(req.channel, ResponseUtils.success());
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("SystemController.openUrl", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
relaunchApp(req: ControllerParam) {
|
||||
this._systemService
|
||||
.relaunch()
|
||||
.then(() => {
|
||||
req.event.reply(req.channel, ResponseUtils.success());
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("SystemController.relaunchApp", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
openAppData(req: ControllerParam) {
|
||||
this._systemService
|
||||
.openLocalPath(PathUtils.getAppData())
|
||||
.then(() => {
|
||||
req.event.reply(req.channel, ResponseUtils.success());
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("SystemController.openAppData", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
selectLocalFile(req: ControllerParam) {
|
||||
const { name, extensions } = req.args;
|
||||
if (!extensions || extensions.length === 0) {
|
||||
return;
|
||||
// req.event.reply(req.channel, ResponseUtils.fail("可选择扩展名不能为空"));
|
||||
}
|
||||
const win: BrowserWindow = BeanFactory.getBean("win");
|
||||
dialog
|
||||
.showOpenDialog(win, {
|
||||
properties: ["openFile"],
|
||||
filters: [{ name: name, extensions: extensions }]
|
||||
})
|
||||
.then(result => {
|
||||
if (result.canceled) {
|
||||
// todo canceled
|
||||
req.event.reply(
|
||||
req.channel,
|
||||
ResponseUtils.success({
|
||||
canceled: true,
|
||||
path: ""
|
||||
})
|
||||
);
|
||||
} else {
|
||||
req.event.reply(
|
||||
req.channel,
|
||||
ResponseUtils.success({
|
||||
canceled: false,
|
||||
path: result.filePaths[0]
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("SystemController.selectLocalFile", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default SystemController;
|
141
electron/controller/VersionController.ts
Normal file
141
electron/controller/VersionController.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import BaseController from "./BaseController";
|
||||
import VersionService from "../service/VersionService";
|
||||
import ResponseUtils from "../utils/ResponseUtils";
|
||||
import VersionRepository from "../repository/VersionRepository";
|
||||
import Logger from "../core/Logger";
|
||||
import { BrowserWindow, dialog } from "electron";
|
||||
import BeanFactory from "../core/BeanFactory";
|
||||
|
||||
class VersionController extends BaseController {
|
||||
private readonly _versionService: VersionService;
|
||||
private readonly _versionDao: VersionRepository;
|
||||
|
||||
constructor(versionService: VersionService, versionDao: VersionRepository) {
|
||||
super();
|
||||
this._versionService = versionService;
|
||||
this._versionDao = versionDao;
|
||||
}
|
||||
|
||||
getVersions(req: ControllerParam) {
|
||||
this._versionService
|
||||
.getFrpVersionsByGitHub()
|
||||
.then(data => {
|
||||
req.event.reply(req.channel, ResponseUtils.success(data));
|
||||
})
|
||||
.catch(err => {
|
||||
Logger.error("VersionController.getVersions", err);
|
||||
this._versionService.getFrpVersionByLocalJson().then(localData => {
|
||||
req.event.reply(req.channel, ResponseUtils.success(localData));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getDownloadedVersions(req: ControllerParam) {
|
||||
this._versionDao
|
||||
.findAll()
|
||||
.then(data => {
|
||||
req.event.reply(req.channel, ResponseUtils.success(data));
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("VersionController.getDownloadedVersions", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
downloadFrpVersion(req: ControllerParam) {
|
||||
this._versionService
|
||||
.downloadFrpVersion(req.args.githubReleaseId, progress => {
|
||||
req.event.reply(
|
||||
req.channel,
|
||||
ResponseUtils.success({
|
||||
percent: progress.percent,
|
||||
githubReleaseId: req.args.githubReleaseId,
|
||||
completed: progress.percent >= 1
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(r => {
|
||||
req.event.reply(
|
||||
req.channel,
|
||||
ResponseUtils.success({
|
||||
percent: 1,
|
||||
githubReleaseId: req.args.githubReleaseId,
|
||||
completed: true
|
||||
})
|
||||
);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("VersionController.downloadFrpVersion", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
deleteDownloadedVersion(req: ControllerParam) {
|
||||
this._versionService
|
||||
.deleteFrpVersion(req.args.githubReleaseId)
|
||||
.then(() => {
|
||||
req.event.reply(req.channel, ResponseUtils.success());
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("VersionController.deleteDownloadedVersion", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
|
||||
importLocalFrpcVersion(req: ControllerParam) {
|
||||
const win: BrowserWindow = BeanFactory.getBean("win");
|
||||
dialog
|
||||
.showOpenDialog(win, {
|
||||
properties: ["openFile"],
|
||||
filters: [
|
||||
{ name: "Frpc", extensions: ["tar.gz", "zip"] } // 允许选择的文件类型,分开后缀以确保可以选择
|
||||
]
|
||||
})
|
||||
.then(result => {
|
||||
if (result.canceled) {
|
||||
req.event.reply(
|
||||
req.channel,
|
||||
ResponseUtils.success({
|
||||
canceled: true
|
||||
})
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
const filePath = result.filePaths[0];
|
||||
this._versionService
|
||||
.importLocalFrpcVersion(filePath)
|
||||
.then(data => {
|
||||
req.event.reply(
|
||||
req.channel,
|
||||
ResponseUtils.success({
|
||||
canceled: false
|
||||
})
|
||||
);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
Logger.error("VersionController.importLocalFrpcVersion", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
Logger.error("VersionController.importLocalFrpcVersion", err);
|
||||
req.event.reply(req.channel, ResponseUtils.fail(err));
|
||||
});
|
||||
|
||||
// const win: BrowserWindow = BeanFactory.getBean("win");
|
||||
// const result = await dialog.showOpenDialog(win, {
|
||||
// properties: ["openFile"],
|
||||
// filters: [
|
||||
// { name: "Frpc", extensions: ["tar.gz", "zip"] } // 允许选择的文件类型,分开后缀以确保可以选择
|
||||
// ]
|
||||
// });
|
||||
// if (result.canceled) {
|
||||
//
|
||||
// }else {
|
||||
//
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
export default VersionController;
|
47
electron/core/BeanFactory.ts
Normal file
47
electron/core/BeanFactory.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import Logger from "./Logger";
|
||||
|
||||
class BeanFactory {
|
||||
private static _beans: Map<string, any> = new Map<string, any>();
|
||||
|
||||
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<T>(name: string, bean: T): void {
|
||||
this._beans.set(name, bean);
|
||||
Logger.info(
|
||||
`${this.name}.${arguments[0]}`,
|
||||
`register bean ${name} ${bean}`
|
||||
);
|
||||
// Logger.info(`register bean ${name} ${bean}`);
|
||||
}
|
||||
|
||||
public static getBean<T>(name: string): T {
|
||||
return this._beans.get(name);
|
||||
}
|
||||
|
||||
public static hasBean(name: string): boolean {
|
||||
return this._beans.has(name);
|
||||
}
|
||||
|
||||
public static clear(): void {
|
||||
this._beans.clear();
|
||||
}
|
||||
|
||||
public static getBeanName(className: string) {
|
||||
return className.charAt(0).toLowerCase() + className.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export default BeanFactory;
|
31
electron/core/BusinessError.ts
Normal file
31
electron/core/BusinessError.ts
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
enum ResponseCode {
|
||||
SUCCESS = "A1000;successful.",
|
||||
INTERNAL_ERROR = "B1000;internal error.",
|
||||
NOT_CONFIG = "B1001;未配置",
|
||||
VERSION_EXISTS = "B1002;导入失败,版本已存在",
|
||||
VERSION_ARGS_ERROR = "B1003;所选 frp 架构与操作系统不符",
|
||||
UNKNOWN_VERSION = "B1004;无法识别文件"
|
||||
}
|
||||
|
||||
class BusinessError extends Error {
|
||||
private readonly _bizCode: string;
|
||||
// constructor(bizCode: string, message: string) {
|
||||
// super(message);
|
||||
// this.bizCode = bizCode;
|
||||
// this.name = "BusinessError";
|
||||
// }
|
||||
|
||||
constructor(bizErrorEnum: ResponseCode) {
|
||||
const [bizCode, message] = bizErrorEnum.split(";");
|
||||
super(message);
|
||||
this._bizCode = bizCode;
|
||||
this.name = "BusinessError";
|
||||
}
|
||||
|
||||
get bizCode(): string {
|
||||
return this._bizCode;
|
||||
}
|
||||
}
|
||||
|
||||
export { BusinessError, ResponseCode };
|
24
electron/core/GlobalConstant.ts
Normal file
24
electron/core/GlobalConstant.ts
Normal file
@ -0,0 +1,24 @@
|
||||
|
||||
class GlobalConstant {
|
||||
public static ZIP_EXT = ".zip";
|
||||
public static TOML_EXT = ".toml";
|
||||
public static GZ_EXT = ".gz";
|
||||
public static TAR_GZ_EXT = ".tar.gz";
|
||||
public static LOCAL_IP = "127.0.0.1";
|
||||
public static FRPC_LOGIN_FAIL_EXIT = false;
|
||||
|
||||
public static FRP_ARCH_VERSION_MAPPING = {
|
||||
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"]
|
||||
};
|
||||
|
||||
public static FRPC_PROCESS_STATUS_CHECK_INTERVAL = 3000;
|
||||
}
|
||||
|
||||
export default GlobalConstant;
|
126
electron/core/IpcRouter.ts
Normal file
126
electron/core/IpcRouter.ts
Normal file
@ -0,0 +1,126 @@
|
||||
|
||||
export const ipcRouters: IpcRouters = {
|
||||
SERVER: {
|
||||
saveConfig: {
|
||||
path: "server/saveConfig",
|
||||
controller: "configController.saveConfig"
|
||||
},
|
||||
getServerConfig: {
|
||||
path: "server/getServerConfig",
|
||||
controller: "configController.getServerConfig"
|
||||
},
|
||||
resetAllConfig: {
|
||||
path: "server/resetAllConfig",
|
||||
controller: "configController.resetAllConfig"
|
||||
},
|
||||
exportConfig: {
|
||||
path: "server/exportConfig",
|
||||
controller: "configController.exportConfig"
|
||||
},
|
||||
importTomlConfig: {
|
||||
path: "server/importTomlConfig",
|
||||
controller: "configController.importTomlConfig"
|
||||
}
|
||||
},
|
||||
LOG: {
|
||||
getFrpLogContent: {
|
||||
path: "log/getFrpLogContent",
|
||||
controller: "logController.getFrpLogContent"
|
||||
},
|
||||
openFrpcLogFile: {
|
||||
path: "log/openFrpcLogFile",
|
||||
controller: "logController.openFrpcLogFile"
|
||||
}
|
||||
},
|
||||
VERSION: {
|
||||
getVersions: {
|
||||
path: "version/getVersions",
|
||||
controller: "versionController.getVersions"
|
||||
},
|
||||
downloadVersion: {
|
||||
path: "version/downloadVersion",
|
||||
controller: "versionController.downloadFrpVersion"
|
||||
},
|
||||
getDownloadedVersions: {
|
||||
path: "version/getDownloadedVersions",
|
||||
controller: "versionController.getDownloadedVersions"
|
||||
},
|
||||
deleteDownloadedVersion: {
|
||||
path: "version/deleteDownloadedVersion",
|
||||
controller: "versionController.deleteDownloadedVersion"
|
||||
},
|
||||
importLocalFrpcVersion: {
|
||||
path: "version/importLocalFrpcVersion",
|
||||
controller: "versionController.importLocalFrpcVersion"
|
||||
}
|
||||
},
|
||||
LAUNCH: {
|
||||
launch: {
|
||||
path: "launch/launch",
|
||||
controller: "launchController.launch"
|
||||
},
|
||||
terminate: {
|
||||
path: "launch/terminate",
|
||||
controller: "launchController.terminate"
|
||||
},
|
||||
getStatus: {
|
||||
path: "launch/getStatus",
|
||||
controller: "launchController.getStatus"
|
||||
}
|
||||
},
|
||||
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"
|
||||
}
|
||||
},
|
||||
SYSTEM: {
|
||||
openUrl: {
|
||||
path: "system/openUrl",
|
||||
controller: "systemController.openUrl"
|
||||
},
|
||||
relaunchApp: {
|
||||
path: "system/relaunchApp",
|
||||
controller: "systemController.relaunchApp"
|
||||
},
|
||||
openAppData: {
|
||||
path: "system/openAppData",
|
||||
controller: "systemController.openAppData"
|
||||
},
|
||||
selectLocalFile: {
|
||||
path: "system/selectLocalFile",
|
||||
controller: "systemController.selectLocalFile"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const listeners: Listeners = {
|
||||
watchFrpcLog: {
|
||||
listenerMethod: "logService.watchFrpcLog",
|
||||
channel: "log:watchFrpcLog"
|
||||
},
|
||||
watchFrpcProcess: {
|
||||
listenerMethod: "frpcProcessService.watchFrpcProcess",
|
||||
channel: "frpcProcess:watchFrpcLog"
|
||||
}
|
||||
};
|
26
electron/core/Logger.ts
Normal file
26
electron/core/Logger.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import log from "electron-log";
|
||||
|
||||
class Logger {
|
||||
static {
|
||||
log.transports.file.level = "debug";
|
||||
log.transports.console.level = "debug";
|
||||
}
|
||||
|
||||
public static info(module: string, msg: string) {
|
||||
log.info(`[${module}] ${msg}`);
|
||||
}
|
||||
|
||||
public static debug(module: string, msg: string) {
|
||||
log.debug(`[${module}] ${msg}`);
|
||||
}
|
||||
|
||||
public static warn(module: string, msg: string) {
|
||||
log.warn(`[${module}] ${msg}`);
|
||||
}
|
||||
|
||||
public static error(module: string, error: Error) {
|
||||
log.error(`[${module}] ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default Logger;
|
25
electron/core/annotation/Component.ts
Normal file
25
electron/core/annotation/Component.ts
Normal file
@ -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);
|
||||
// // };
|
||||
// // }
|
8
electron/core/annotation/Logger.ts
Normal file
8
electron/core/annotation/Logger.ts
Normal file
@ -0,0 +1,8 @@
|
||||
// export default function Logger(module?: string): ClassDecorator {
|
||||
// return function (target: Object) {
|
||||
// Object.defineProperty(target, "logger", {
|
||||
// value: new (require("../LoggerFactory").default)(module),
|
||||
// writable: true
|
||||
// });
|
||||
// };
|
||||
// }
|
5
electron/core/annotation/Resource.ts
Normal file
5
electron/core/annotation/Resource.ts
Normal file
@ -0,0 +1,5 @@
|
||||
// export default function Resource(beanName?: string): PropertyDecorator {
|
||||
// return function (target: Object, propertyKey: string | symbol) {
|
||||
// console.log(target, propertyKey);
|
||||
// };
|
||||
// }
|
11
electron/electron-env.d.ts
vendored
11
electron/electron-env.d.ts
vendored
@ -1,11 +0,0 @@
|
||||
/// <reference types="vite-plugin-electron/electron-env" />
|
||||
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
VSCODE_DEBUG?: 'true'
|
||||
DIST_ELECTRON: string
|
||||
DIST: string
|
||||
/** /dist/ or /public/ */
|
||||
VITE_PUBLIC: string
|
||||
}
|
||||
}
|
@ -10,21 +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 { initProxyApi } from "../api/proxy";
|
||||
import {
|
||||
initFrpcApi,
|
||||
startFrpWorkerProcess,
|
||||
stopFrpcProcess
|
||||
} from "../api/frpc";
|
||||
import { initLoggerApi } from "../api/logger";
|
||||
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 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");
|
||||
@ -32,273 +36,386 @@ 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<MenuItemConstructorOptions | MenuItem> = [
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
// Initialize APIs
|
||||
try {
|
||||
initGitHubApi(win);
|
||||
logInfo(LogModule.APP, `GitHub API initialized.`);
|
||||
|
||||
initConfigApi(win);
|
||||
logInfo(LogModule.APP, `Config API initialized.`);
|
||||
|
||||
initProxyApi();
|
||||
logInfo(LogModule.APP, `Proxy API initialized.`);
|
||||
|
||||
initFrpcApi();
|
||||
logInfo(LogModule.APP, `FRPC API initialized.`);
|
||||
|
||||
initLoggerApi();
|
||||
logInfo(LogModule.APP, `Logger API initialized.`);
|
||||
|
||||
initFileApi();
|
||||
logInfo(LogModule.APP, `File API initialized.`);
|
||||
|
||||
initCommonApi();
|
||||
logInfo(LogModule.APP, `Common API initialized.`);
|
||||
|
||||
initLocalApi();
|
||||
logInfo(LogModule.APP, `Local 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.`);
|
||||
win = null;
|
||||
if (process.platform !== "darwin") {
|
||||
stopFrpcProcess(() => {
|
||||
logInfo(LogModule.APP, `FRPC process stopped. Quitting application.`);
|
||||
app.quit();
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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.`);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.on("before-quit", () => {
|
||||
logInfo(LogModule.APP, `Application is about to quit.`);
|
||||
stopFrpcProcess(() => {
|
||||
isQuiting = true;
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle("open-win", (_, arg) => {
|
||||
logInfo(LogModule.APP, `Opening new window with argument: ${arg}`);
|
||||
const childWindow = new BrowserWindow({
|
||||
webPreferences: {
|
||||
preload,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false
|
||||
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(() => {});
|
||||
}
|
||||
});
|
||||
|
||||
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}`
|
||||
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);
|
||||
|
||||
const that = this;
|
||||
this._win.on("minimize", function (event) {
|
||||
event.preventDefault();
|
||||
that._win.hide();
|
||||
});
|
||||
|
||||
this._win.on("close", function (event) {
|
||||
if (!that._quitting) {
|
||||
event.preventDefault();
|
||||
that._win.hide();
|
||||
if (process.platform === "darwin") {
|
||||
app.dock.hide();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
Logger.info(
|
||||
`FrpcDesktopApp.initializeWindow`,
|
||||
`Window initialized.`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
initializeTray() {
|
||||
const that = this;
|
||||
let menu: Array<MenuItemConstructorOptions | MenuItem> = [
|
||||
{
|
||||
label: "显示主窗口",
|
||||
click: function () {
|
||||
that._win.show();
|
||||
if (process.platform === "darwin") {
|
||||
app.dock.show().then(() => {});
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "退出",
|
||||
click: () => {
|
||||
that._quitting = true;
|
||||
// todo stop frpc process
|
||||
app.quit();
|
||||
}
|
||||
}
|
||||
];
|
||||
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();
|
||||
});
|
||||
Logger.info(
|
||||
`FrpcDesktopApp.initializeTray`,
|
||||
`Tray initialized.`
|
||||
);
|
||||
}
|
||||
|
||||
initializeElectronApp() {
|
||||
// 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());
|
||||
|
||||
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()}.`
|
||||
// );
|
||||
|
||||
// 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;
|
||||
});
|
||||
|
||||
Logger.info(
|
||||
`FrpcDesktopApp.initializeElectronApp`,
|
||||
`ElectronApp initialized.`
|
||||
);
|
||||
}
|
||||
|
||||
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.getBean("frpcProcessService"),
|
||||
)
|
||||
);
|
||||
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"))
|
||||
);
|
||||
Logger.info(
|
||||
`FrpcDesktopApp.initializeBeans`,
|
||||
`Beans initialized.`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
`FrpcDesktopApp.initializeListeners`,
|
||||
`Listeners initialized.`
|
||||
);
|
||||
// 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);
|
||||
Logger.debug(
|
||||
`ipcRouter`,
|
||||
`path: ${router.path} + req: (channel: ${
|
||||
req.channel
|
||||
}, args: ${JSON.stringify(
|
||||
req.args
|
||||
)}) => bean: ${beanName}.${method}`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Logger.info(
|
||||
`FrpcDesktopApp.initializeRouters`,
|
||||
`Routers initialized.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
new FrpcDesktopApp();
|
138
electron/repository/BaseRepository.ts
Normal file
138
electron/repository/BaseRepository.ts
Normal file
@ -0,0 +1,138 @@
|
||||
import Datastore from "nedb";
|
||||
import path from "path";
|
||||
import PathUtils from "../utils/PathUtils";
|
||||
import IdUtils from "../utils/IdUtils";
|
||||
|
||||
// interface BaseDaoInterface<T> {
|
||||
// db: Datastore;
|
||||
//
|
||||
// insert(t: T): Promise<T>;
|
||||
//
|
||||
// //
|
||||
// updateById(id: string, t: T): Promise<T>;
|
||||
//
|
||||
// //
|
||||
// // deleteById(id: string): void;
|
||||
// //
|
||||
// // findAll(): T[];
|
||||
//
|
||||
// findById(id: string): Promise<T>;
|
||||
// }
|
||||
|
||||
|
||||
class BaseRepository<T> {
|
||||
protected readonly db: Datastore;
|
||||
|
||||
constructor(dbName: string) {
|
||||
const dbFilename = path.join(
|
||||
PathUtils.getDataBaseStoragePath(),
|
||||
`${dbName}-v2.db`
|
||||
);
|
||||
this.db = new Datastore({
|
||||
autoload: true,
|
||||
filename: dbFilename
|
||||
});
|
||||
// todo log
|
||||
}
|
||||
|
||||
protected genId(): string {
|
||||
return IdUtils.genUUID();
|
||||
}
|
||||
|
||||
// async insert(t: T): Promise<T> {
|
||||
// return new Promise<T>((resolve, reject) => {
|
||||
// resolve(t);
|
||||
// });
|
||||
// }
|
||||
//
|
||||
insert(t: T): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
t["_id"] = this.genId();
|
||||
this.db.insert(t, (err, document) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve(document);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateById(id: string, t: T): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this.db.update(
|
||||
{ _id: id },
|
||||
t,
|
||||
{ upsert: true },
|
||||
(err, numberOfUpdated, upsert) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
t["_id"] = id;
|
||||
resolve(t);
|
||||
// this.findById(id)
|
||||
// .then(data => {
|
||||
// resolve(t);
|
||||
// })
|
||||
// .catch(err2 => {
|
||||
// reject(err2);
|
||||
// });
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
deleteById(id: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.db.remove({ _id: id }, err => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
//
|
||||
// findAll(): T[] {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
findById(id: string): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this.db.findOne({ _id: id }, (err, document) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(document);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
findAll(): Promise<Array<T>> {
|
||||
return new Promise<Array<T>>((resolve, reject) => {
|
||||
this.db.find({}, (err, document) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(document);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
truncate() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.db.remove({}, { multi: true }, (err, n) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseRepository;
|
28
electron/repository/ProxyRepository.ts
Normal file
28
electron/repository/ProxyRepository.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import BaseRepository from "./BaseRepository";
|
||||
import Component from "../core/annotation/Component";
|
||||
|
||||
// @Component()
|
||||
class ProxyRepository extends BaseRepository<FrpcProxy> {
|
||||
constructor() {
|
||||
super("proxy");
|
||||
}
|
||||
|
||||
updateProxyStatus(id: string, status: number): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.db.update(
|
||||
{ _id: id },
|
||||
{ $set: { status: status } },
|
||||
{},
|
||||
(err, numberOfUpdated, upsert) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default ProxyRepository;
|
23
electron/repository/ServerRepository.ts
Normal file
23
electron/repository/ServerRepository.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import BaseRepository from "./BaseRepository";
|
||||
import Component from "../core/annotation/Component";
|
||||
|
||||
// @Component()
|
||||
class ServerRepository extends BaseRepository<OpenSourceFrpcDesktopServer> {
|
||||
constructor() {
|
||||
super("server");
|
||||
}
|
||||
|
||||
exists(id: string): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.count({ _id: id }, (err, count) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(count > 0);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default ServerRepository
|
35
electron/repository/VersionRepository.ts
Normal file
35
electron/repository/VersionRepository.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import BaseRepository from "./BaseRepository";
|
||||
import Component from "../core/annotation/Component";
|
||||
|
||||
// @Component()
|
||||
class VersionRepository extends BaseRepository<FrpcVersion> {
|
||||
constructor() {
|
||||
super("version");
|
||||
}
|
||||
|
||||
findByGithubReleaseId(githubReleaseId: number): Promise<FrpcVersion> {
|
||||
return new Promise<FrpcVersion>((resolve, reject) => {
|
||||
this.db.findOne({ githubReleaseId: githubReleaseId }, (err, document) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(document);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
exists(githubReleaseId: number): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.count({ githubReleaseId: githubReleaseId }, (err, count) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(count > 0);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default VersionRepository;
|
16
electron/service/BaseService.ts
Normal file
16
electron/service/BaseService.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import BaseRepository from "../repository/BaseRepository";
|
||||
|
||||
|
||||
interface BaseServiceInterface<T> {
|
||||
// dao: BaseRepository<T>;
|
||||
}
|
||||
|
||||
class BaseService<T> implements BaseServiceInterface<T> {
|
||||
// dao: BaseRepository<T>;
|
||||
//
|
||||
// constructor(dao: BaseRepository<T>) {
|
||||
// this.dao = dao;
|
||||
// }
|
||||
}
|
||||
|
||||
export default BaseService;
|
164
electron/service/FrpcProcessService.ts
Normal file
164
electron/service/FrpcProcessService.ts
Normal file
@ -0,0 +1,164 @@
|
||||
import ServerService from "./ServerService";
|
||||
import VersionRepository from "../repository/VersionRepository";
|
||||
import PathUtils from "../utils/PathUtils";
|
||||
import GlobalConstant from "../core/GlobalConstant";
|
||||
import { app, BrowserWindow, Notification } from "electron";
|
||||
import treeKill from "tree-kill";
|
||||
import BeanFactory from "../core/BeanFactory";
|
||||
import ResponseUtils from "../utils/ResponseUtils";
|
||||
import { BusinessError, ResponseCode } from "../core/BusinessError";
|
||||
import Logger from "../core/Logger";
|
||||
|
||||
class FrpcProcessService {
|
||||
private readonly _serverService: ServerService;
|
||||
private readonly _versionDao: VersionRepository;
|
||||
private _frpcProcess: any;
|
||||
private _frpcProcessListener: any;
|
||||
private _frpcLastStartTime: number = -1;
|
||||
|
||||
constructor(serverService: ServerService, versionDao: VersionRepository) {
|
||||
this._serverService = serverService;
|
||||
this._versionDao = versionDao;
|
||||
}
|
||||
|
||||
isRunning(): boolean {
|
||||
if (!this._frpcProcess) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Logger.debug(
|
||||
`FrpcProcessService.isRunning`,
|
||||
`pid: ${this._frpcProcess.pid}`
|
||||
);
|
||||
process.kill(this._frpcProcess.pid, 0);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async startFrpcProcess() {
|
||||
if (this.isRunning()) {
|
||||
return;
|
||||
}
|
||||
const config = await this._serverService.getServerConfig();
|
||||
if (!config) {
|
||||
throw new BusinessError(ResponseCode.NOT_CONFIG);
|
||||
}
|
||||
const version = await this._versionDao.findByGithubReleaseId(
|
||||
config.frpcVersion
|
||||
);
|
||||
const configPath = PathUtils.getTomlConfigFilePath();
|
||||
await this._serverService.genTomlConfig(configPath);
|
||||
const command = `./${PathUtils.getFrpcFilename()} -c "${configPath}"`;
|
||||
this._frpcProcess = require("child_process").spawn(command, {
|
||||
cwd: version.localPath,
|
||||
shell: true
|
||||
});
|
||||
this._frpcLastStartTime = Date.now();
|
||||
Logger.debug(
|
||||
`FrpcProcessService.startFrpcProcess`,
|
||||
`start command: ${command}`
|
||||
);
|
||||
this._frpcProcess.stdout.on("data", data => {
|
||||
Logger.debug(`FrpcProcessService.startFrpcProcess`, `stdout: ${data}`);
|
||||
});
|
||||
|
||||
this._frpcProcess.stderr.on("data", data => {
|
||||
Logger.debug(`FrpcProcessService.startFrpcProcess`, `stderr: ${data}`);
|
||||
});
|
||||
}
|
||||
|
||||
async stopFrpcProcess() {
|
||||
if (this._frpcProcess && this.isRunning()) {
|
||||
Logger.debug(
|
||||
`FrpcProcessService.stopFrpcProcess`,
|
||||
`pid: ${this._frpcProcess.pid}`
|
||||
);
|
||||
treeKill(this._frpcProcess.pid, (error: Error) => {
|
||||
if (error) {
|
||||
throw error;
|
||||
} else {
|
||||
this._frpcProcess = null;
|
||||
this._frpcLastStartTime = -1;
|
||||
|
||||
// clearInterval(this._frpcProcessListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async reloadFrpcProcess() {
|
||||
if (!this.isRunning()) {
|
||||
return;
|
||||
}
|
||||
const config = await this._serverService.getServerConfig();
|
||||
if (!config) {
|
||||
throw new BusinessError(ResponseCode.NOT_CONFIG);
|
||||
}
|
||||
const version = await this._versionDao.findByGithubReleaseId(
|
||||
config.frpcVersion
|
||||
);
|
||||
const configPath = PathUtils.getTomlConfigFilePath();
|
||||
await this._serverService.genTomlConfig(configPath);
|
||||
const command = `./${PathUtils.getFrpcFilename()} reload -c "${configPath}"`;
|
||||
require("child_process").exec(
|
||||
command,
|
||||
{
|
||||
cwd: version.localPath,
|
||||
shell: true
|
||||
},
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
Logger.error(`FrpcProcessService.reloadFrpcProcess`, error);
|
||||
return;
|
||||
}
|
||||
if (stderr) {
|
||||
Logger.debug(
|
||||
`FrpcProcessService.reloadFrpcProcess`,
|
||||
`stderr: ${stderr}`
|
||||
);
|
||||
}
|
||||
Logger.debug(
|
||||
`FrpcProcessService.reloadFrpcProcess`,
|
||||
`stderr: ${stdout}`
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
watchFrpcProcess(listenerParam: ListenerParam) {
|
||||
this._frpcProcessListener = setInterval(() => {
|
||||
const running = this.isRunning();
|
||||
// todo return status to view.
|
||||
// logDebug(
|
||||
// LogModule.FRP_CLIENT,
|
||||
// `Monitoring frpc process status: ${status}, Listener ID: ${frpcStatusListener}`
|
||||
// );
|
||||
Logger.debug(
|
||||
`FrpcProcessService.watchFrpcProcess`,
|
||||
`running: ${running}`
|
||||
);
|
||||
if (!running) {
|
||||
if (this._frpcLastStartTime !== -1) {
|
||||
new Notification({
|
||||
title: app.getName(),
|
||||
body: "Connection lost, please check the logs for details."
|
||||
}).show();
|
||||
}
|
||||
// logError(
|
||||
// LogModule.FRP_CLIENT,
|
||||
// "Frpc process status check failed. Connection lost."
|
||||
// );
|
||||
// clearInterval(this._frpcProcessListener);
|
||||
}
|
||||
const win: BrowserWindow = BeanFactory.getBean("win");
|
||||
win.webContents.send(
|
||||
listenerParam.channel,
|
||||
ResponseUtils.success(running)
|
||||
);
|
||||
}, GlobalConstant.FRPC_PROCESS_STATUS_CHECK_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
export default FrpcProcessService;
|
64
electron/service/GitHubService.ts
Normal file
64
electron/service/GitHubService.ts
Normal file
@ -0,0 +1,64 @@
|
||||
class GitHubService {
|
||||
constructor() {}
|
||||
|
||||
getGithubRepoAllReleases(githubRepo: string): Promise<Array<GithubRelease>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { net } = require("electron");
|
||||
|
||||
const request = net.request({
|
||||
method: "get",
|
||||
url: `https://api.github.com/repos/${githubRepo}/releases?page=1&per_page=1000`
|
||||
});
|
||||
|
||||
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) {
|
||||
this.parseGitHubVersion(responseData.toString())
|
||||
.then(data => {
|
||||
resolve(data);
|
||||
})
|
||||
.catch(err => reject(err));
|
||||
// logInfo(
|
||||
// LogModule.GITHUB,
|
||||
// "Successfully retrieved GitHub release data."
|
||||
// );
|
||||
} else {
|
||||
// logWarn(
|
||||
// LogModule.GITHUB,
|
||||
// "Failed to retrieve data, using local JSON instead. Status code: " +
|
||||
// response.statusCode
|
||||
// );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
request.on("error", error => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
request.end();
|
||||
});
|
||||
}
|
||||
|
||||
parseGitHubVersion(
|
||||
githubReleaseJsonStr: string
|
||||
): Promise<Array<GithubRelease>> {
|
||||
return new Promise<Array<GithubRelease>>(resolve => {
|
||||
const githubReleases: Array<GithubRelease> =
|
||||
JSON.parse(githubReleaseJsonStr);
|
||||
resolve(githubReleases);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default GitHubService;
|
67
electron/service/LogService.ts
Normal file
67
electron/service/LogService.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import fs from "fs";
|
||||
import PathUtils from "../utils/PathUtils";
|
||||
import SystemService from "./SystemService";
|
||||
import BeanFactory from "../core/BeanFactory";
|
||||
import { BrowserWindow } from "electron";
|
||||
import ResponseUtils from "../utils/ResponseUtils";
|
||||
|
||||
class LogService {
|
||||
private readonly _systemService: SystemService;
|
||||
private readonly _logPath: string = PathUtils.getFrpcLogFilePath();
|
||||
|
||||
constructor(systemService: SystemService) {
|
||||
this._systemService = systemService;
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
watchFrpcLog(listenerParam: ListenerParam) {
|
||||
if (!fs.existsSync(this._logPath)) {
|
||||
setTimeout(() => this.watchFrpcLog(listenerParam), 1000);
|
||||
return;
|
||||
}
|
||||
console.log("watchFrpcLog succcess");
|
||||
fs.watch(this._logPath, (eventType, filename) => {
|
||||
if (eventType === "change") {
|
||||
console.log("change", eventType, listenerParam.channel);
|
||||
const win: BrowserWindow = BeanFactory.getBean("win");
|
||||
win.webContents.send(
|
||||
listenerParam.channel,
|
||||
ResponseUtils.success(true)
|
||||
);
|
||||
} else {
|
||||
}
|
||||
});
|
||||
// return new Promise<boolean>((resolve, reject) => {
|
||||
//
|
||||
// });
|
||||
}
|
||||
|
||||
openFrpcLogFile(): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
this._systemService
|
||||
.openLocalFile(this._logPath)
|
||||
.then(result => {
|
||||
resolve(result);
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default LogService;
|
141
electron/service/ProxyService.ts
Normal file
141
electron/service/ProxyService.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import ProxyRepository from "../repository/ProxyRepository";
|
||||
import FrpcProcessService from "./FrpcProcessService";
|
||||
|
||||
class ProxyService {
|
||||
private readonly _proxyDao: ProxyRepository;
|
||||
private readonly _frpcProcessService: FrpcProcessService;
|
||||
|
||||
constructor(
|
||||
public proxyDao: ProxyRepository,
|
||||
frpcProcessService: FrpcProcessService
|
||||
) {
|
||||
this._proxyDao = proxyDao;
|
||||
this._frpcProcessService = frpcProcessService;
|
||||
}
|
||||
|
||||
async insertProxy(proxy: FrpcProxy) {
|
||||
const proxy2 = await this._proxyDao.insert(proxy);
|
||||
await this._frpcProcessService.reloadFrpcProcess();
|
||||
return proxy2;
|
||||
}
|
||||
|
||||
async updateProxy(proxy: FrpcProxy) {
|
||||
const proxy2 = await this._proxyDao.updateById(proxy._id, proxy);
|
||||
await this._frpcProcessService.reloadFrpcProcess();
|
||||
return proxy2;
|
||||
}
|
||||
|
||||
async deleteProxy(proxyId: string) {
|
||||
await this._proxyDao.deleteById(proxyId);
|
||||
await this._frpcProcessService.reloadFrpcProcess();
|
||||
}
|
||||
|
||||
async getLocalPorts(): Promise<Array<LocalPort>> {
|
||||
const command =
|
||||
process.platform === "win32"
|
||||
? "netstat -a -n"
|
||||
: "netstat -an | grep LISTEN";
|
||||
return new Promise((resolve, reject) => {
|
||||
require("child_process").exec(command, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
if (stderr) {
|
||||
reject(stderr);
|
||||
}
|
||||
let ports: Array<LocalPort> = [];
|
||||
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;
|
229
electron/service/ServerService.ts
Normal file
229
electron/service/ServerService.ts
Normal file
@ -0,0 +1,229 @@
|
||||
import BaseService from "./BaseService";
|
||||
import ServerRepository from "../repository/ServerRepository";
|
||||
import fs from "fs";
|
||||
import PathUtils from "../utils/PathUtils";
|
||||
import ProxyRepository from "../repository/ProxyRepository";
|
||||
import { BrowserWindow, dialog } from "electron";
|
||||
import BeanFactory from "../core/BeanFactory";
|
||||
import path from "path";
|
||||
import GlobalConstant from "../core/GlobalConstant";
|
||||
import TOML from "smol-toml";
|
||||
|
||||
class ServerService extends BaseService<OpenSourceFrpcDesktopServer> {
|
||||
private readonly _serverDao: ServerRepository;
|
||||
private readonly _proxyDao: ProxyRepository;
|
||||
// private readonly _systemService: SystemService;
|
||||
private readonly _serverId: string = "1";
|
||||
|
||||
constructor(
|
||||
serverDao: ServerRepository,
|
||||
proxyDao: ProxyRepository
|
||||
// systemService: SystemService
|
||||
) {
|
||||
super();
|
||||
this._serverDao = serverDao;
|
||||
this._proxyDao = proxyDao;
|
||||
// this._systemService = systemService;
|
||||
}
|
||||
|
||||
async saveServerConfig(
|
||||
frpcServer: OpenSourceFrpcDesktopServer
|
||||
): Promise<OpenSourceFrpcDesktopServer> {
|
||||
frpcServer._id = this._serverId;
|
||||
return await this._serverDao.updateById(this._serverId, frpcServer);
|
||||
}
|
||||
|
||||
async getServerConfig(): Promise<OpenSourceFrpcDesktopServer> {
|
||||
return await this._serverDao.findById(this._serverId);
|
||||
}
|
||||
|
||||
hasServerConfig(): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._serverDao
|
||||
.exists(this._serverId)
|
||||
.then(r => {
|
||||
resolve(r);
|
||||
})
|
||||
.catch(err => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
private isRagePort(proxy: FrpcProxy) {
|
||||
return (
|
||||
["tcp", "udp"].indexOf(proxy.type) >= 0 &&
|
||||
(String(proxy.localPort).indexOf("-") !== -1 ||
|
||||
String(proxy.localPort).indexOf(",") !== -1)
|
||||
);
|
||||
}
|
||||
|
||||
private isVisitors(proxy: FrpcProxy) {
|
||||
return (
|
||||
["stcp", "sudp", "xtcp"].indexOf(proxy.type) >= 0 &&
|
||||
proxy.visitorsModel === "visitors"
|
||||
);
|
||||
}
|
||||
|
||||
private isEnableProxy(proxy: FrpcProxy) {
|
||||
return proxy.status === 1;
|
||||
}
|
||||
|
||||
private isHttps2http(proxy: FrpcProxy) {
|
||||
return proxy.https2http;
|
||||
}
|
||||
|
||||
async genTomlConfig(outputPath: string) {
|
||||
if (!outputPath) {
|
||||
return;
|
||||
}
|
||||
const server = await this.getServerConfig();
|
||||
const proxies = await this._proxyDao.findAll();
|
||||
|
||||
const enabledRangePortProxies = proxies
|
||||
.filter(f => this.isEnableProxy(f))
|
||||
.filter(f => !this.isVisitors(f))
|
||||
.filter(f => this.isRagePort(f))
|
||||
.map(proxy => {
|
||||
return `
|
||||
{{- range $_, $v := parseNumberRangePair "${proxy.localPort}" "${proxy.remotePort}" }}
|
||||
[[proxies]]
|
||||
|
||||
type = "${proxy.type}"
|
||||
name = "${proxy.name}-{{ $v.First }}"
|
||||
localPort = {{ $v.First }}
|
||||
remotePort = {{ $v.Second }}
|
||||
{{- end }}
|
||||
`;
|
||||
});
|
||||
|
||||
const enabledProxies = proxies
|
||||
.filter(f => this.isEnableProxy(f))
|
||||
.filter(f => !this.isVisitors(f))
|
||||
.filter(f => !this.isRagePort(f))
|
||||
.map(proxy => {
|
||||
if (proxy.type === "tcp" || proxy.type === "udp") {
|
||||
const localPort = parseInt(proxy.localPort);
|
||||
const remotePort = parseInt(proxy.remotePort);
|
||||
return {
|
||||
name: proxy.name,
|
||||
type: proxy.type,
|
||||
localIP: proxy.localIP,
|
||||
localPort: localPort,
|
||||
remotePort: remotePort
|
||||
};
|
||||
} else if (proxy.type === "http" || proxy.type === "https") {
|
||||
if (this.isHttps2http(proxy) && proxy.type === "https") {
|
||||
return {
|
||||
name: proxy.name,
|
||||
type: proxy.type,
|
||||
customDomains: proxy.customDomains,
|
||||
subdomain: proxy.subdomain,
|
||||
...(proxy.https2http
|
||||
? {
|
||||
plugin: {
|
||||
type: "https2http",
|
||||
localAddr: `${proxy.localIP}:${proxy.localPort}`,
|
||||
crtPath: proxy.https2httpCaFile,
|
||||
keyPath: proxy.https2httpKeyFile
|
||||
}
|
||||
}
|
||||
: {})
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
name: proxy.name,
|
||||
type: proxy.type,
|
||||
localIP: proxy.localIP,
|
||||
localPort: parseInt(proxy.localPort),
|
||||
customDomains: proxy.customDomains,
|
||||
subdomain: proxy.subdomain,
|
||||
...(proxy.basicAuth
|
||||
? { httpUser: proxy.httpUser, httpPassword: proxy.httpPassword }
|
||||
: {})
|
||||
};
|
||||
}
|
||||
} else if (
|
||||
proxy.type === "stcp" ||
|
||||
proxy.type === "xtcp" ||
|
||||
proxy.type === "sudp"
|
||||
) {
|
||||
return {
|
||||
name: proxy.name,
|
||||
type: proxy.type,
|
||||
localIP: proxy.localIP,
|
||||
localPort: parseInt(proxy.localPort),
|
||||
secretKey: proxy.secretKey
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const enableVisitors = proxies
|
||||
.filter(f => this.isEnableProxy(f))
|
||||
.filter(f => this.isVisitors(f))
|
||||
.map(proxy => {
|
||||
if (proxy.type === "xtcp") {
|
||||
return {
|
||||
name: proxy.name,
|
||||
type: proxy.type,
|
||||
// serverUser: proxy.serverUser,
|
||||
serverName: proxy.serverName,
|
||||
secretKey: proxy.secretKey,
|
||||
bindAddr: proxy.bindAddr,
|
||||
bindPort: proxy.bindPort,
|
||||
keepTunnelOpen: proxy.keepTunnelOpen,
|
||||
fallbackTo: proxy.fallbackTo,
|
||||
fallbackTimeoutMs: proxy.fallbackTimeoutMs
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
name: proxy.name,
|
||||
type: proxy.type,
|
||||
serverName: proxy.serverName,
|
||||
secretKey: proxy.secretKey,
|
||||
bindAddr: proxy.bindAddr,
|
||||
bindPort: proxy.bindPort
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const { frpcVersion, _id, system, ...commonConfig } = server;
|
||||
const frpcConfig = { ...commonConfig };
|
||||
frpcConfig.log.to = PathUtils.getFrpcLogFilePath();
|
||||
frpcConfig.loginFailExit = GlobalConstant.FRPC_LOGIN_FAIL_EXIT;
|
||||
frpcConfig.webServer.addr = GlobalConstant.LOCAL_IP;
|
||||
|
||||
let toml = TOML.stringify({
|
||||
...frpcConfig,
|
||||
...(enabledProxies.length > 0 ? { proxies: enabledProxies } : {}),
|
||||
...(enableVisitors.length > 0 ? { visitors: enableVisitors } : {})
|
||||
});
|
||||
|
||||
enabledRangePortProxies.forEach(f => {
|
||||
toml += `
|
||||
${f}`;
|
||||
});
|
||||
|
||||
fs.writeFileSync(outputPath, toml, { flag: "w" });
|
||||
}
|
||||
|
||||
async importTomlConfig() {
|
||||
const win: BrowserWindow = BeanFactory.getBean("win");
|
||||
const result = await dialog.showOpenDialog(win, {
|
||||
properties: ["openFile"],
|
||||
filters: [{ name: "Frpc Toml ConfigFile", extensions: ["toml"] }]
|
||||
});
|
||||
if (result.canceled) {
|
||||
} else {
|
||||
const filePath = result.filePaths[0];
|
||||
const fileExtension = path.extname(filePath);
|
||||
if (fileExtension === GlobalConstant.TOML_EXT) {
|
||||
const tomlData = fs.readFileSync(filePath, "utf-8");
|
||||
const sourceConfig = TOML.parse(tomlData);
|
||||
} else {
|
||||
throw new Error(`导入失败,暂不支持 ${fileExtension} 格式文件`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ServerService;
|
112
electron/service/SystemService.ts
Normal file
112
electron/service/SystemService.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { app, shell } from "electron";
|
||||
import GlobalConstant from "../core/GlobalConstant";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import zlib from "zlib";
|
||||
import admZip from "adm-zip";
|
||||
|
||||
const tar = require("tar");
|
||||
|
||||
class SystemService {
|
||||
async openUrl(url: string) {
|
||||
if (url) {
|
||||
await shell.openExternal(url);
|
||||
}
|
||||
}
|
||||
|
||||
async relaunch() {
|
||||
await app.relaunch();
|
||||
app.quit();
|
||||
}
|
||||
|
||||
openLocalFile(filePath: string) {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
shell
|
||||
.openPath(filePath)
|
||||
.then(errorMessage => {
|
||||
if (errorMessage) {
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
openLocalPath(path: string) {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
shell.openPath(path).then(errorMessage => {
|
||||
if (errorMessage) {
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
decompressZipFile(zipFilePath: string, targetPath: string) {
|
||||
if (!zipFilePath.endsWith(GlobalConstant.ZIP_EXT)) {
|
||||
throw new Error("The file is not a .zip file");
|
||||
}
|
||||
if (!fs.existsSync(zipFilePath)) {
|
||||
throw new Error("The file does not exist");
|
||||
}
|
||||
// const zipBasename = path.basename(zipFilePath, GlobalConstant.ZIP_EXT);
|
||||
const targetFolder = path.join(targetPath, targetPath);
|
||||
if (!fs.existsSync) {
|
||||
// not exists. do mkdir
|
||||
fs.mkdirSync(targetFolder, {
|
||||
recursive: true
|
||||
});
|
||||
}
|
||||
// starting unzip.
|
||||
const zip = new admZip(zipFilePath);
|
||||
zip.extractAllTo(targetPath, true); // true: cover exists file.
|
||||
// todo 2025-02-21 return targetPath.
|
||||
// const frpcPath = path.join("frp", path.basename(zipFilePath, zipExt));
|
||||
}
|
||||
|
||||
decompressTarGzFile(tarGzPath: string, targetPath: string, finish: Function) {
|
||||
// const targetFolder = path.join(targetPath, targetPath);
|
||||
const unzip = zlib.createGunzip();
|
||||
const readStream = fs.createReadStream(tarGzPath);
|
||||
if (!fs.existsSync(targetPath)) {
|
||||
fs.mkdirSync(targetPath, { recursive: true, mode: 0o777 });
|
||||
}
|
||||
|
||||
readStream
|
||||
.pipe(unzip)
|
||||
.on("error", err => {
|
||||
// logError(LogModule.APP, `Error during gunzip: ${err.message}`);
|
||||
})
|
||||
.pipe(
|
||||
tar
|
||||
.extract({
|
||||
cwd: targetPath,
|
||||
strip: 1,
|
||||
filter: filePath => path.basename(filePath) === "frpc"
|
||||
})
|
||||
.on("error", err => {
|
||||
// logError(
|
||||
// LogModule.APP,
|
||||
// `Error extracting tar file: ${err.message}`
|
||||
// );
|
||||
})
|
||||
)
|
||||
.on("finish", () => {
|
||||
finish();
|
||||
// const frpcPath = path.join("frp", path.basename(tarGzPath, ".tar.gz"));
|
||||
// logInfo(
|
||||
// LogModule.APP,
|
||||
// `Extraction completed. Extracted directory: ${frpcPath}`
|
||||
// );
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default SystemService;
|
239
electron/service/VersionService.ts
Normal file
239
electron/service/VersionService.ts
Normal file
@ -0,0 +1,239 @@
|
||||
import VersionRepository from "../repository/VersionRepository";
|
||||
import BaseService from "./BaseService";
|
||||
import GitHubService from "./GitHubService";
|
||||
import frpReleasesJson from "../json/frp-releases.json";
|
||||
import { download } from "electron-dl";
|
||||
import { BrowserWindow } 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";
|
||||
import SystemService from "./SystemService";
|
||||
import { BusinessError, ResponseCode } from "../core/BusinessError";
|
||||
|
||||
class VersionService extends BaseService<FrpcVersion> {
|
||||
private readonly _versionDao: VersionRepository;
|
||||
private readonly _systemService: SystemService;
|
||||
private readonly _gitHubService: GitHubService;
|
||||
private readonly _currFrpArch: Array<string>;
|
||||
private _versions: Array<FrpcVersion> = [];
|
||||
|
||||
constructor(
|
||||
versionDao: VersionRepository,
|
||||
systemService: SystemService,
|
||||
gitHubService: GitHubService
|
||||
) {
|
||||
super();
|
||||
this._versionDao = versionDao;
|
||||
this._gitHubService = gitHubService;
|
||||
this._systemService = systemService;
|
||||
const nodeVersion = `${process.platform}_${process.arch}`;
|
||||
this._currFrpArch = GlobalConstant.FRP_ARCH_VERSION_MAPPING[nodeVersion];
|
||||
}
|
||||
|
||||
downloadFrpVersion(githubReleaseId: number, onProgress: Function) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const version = this._versions.find(
|
||||
f => f.githubReleaseId === githubReleaseId
|
||||
);
|
||||
if (!version) {
|
||||
reject(new Error("version not found"));
|
||||
}
|
||||
const url = version.browserDownloadUrl;
|
||||
const downloadedFilePath = path.join(
|
||||
PathUtils.getDownloadStoragePath(),
|
||||
`${version.assetName}`
|
||||
);
|
||||
|
||||
const versionFilePath = path.join(
|
||||
PathUtils.getVersionStoragePath(),
|
||||
SecureUtils.calculateMD5(version.name)
|
||||
);
|
||||
|
||||
if (fs.existsSync(versionFilePath)) {
|
||||
fs.rmSync(versionFilePath, { recursive: true, force: true });
|
||||
}
|
||||
// const targetPath = path.resolve();
|
||||
download(BrowserWindow.getFocusedWindow(), url, {
|
||||
filename: `${version.assetName}`,
|
||||
directory: PathUtils.getDownloadStoragePath(),
|
||||
onProgress: progress => {
|
||||
onProgress(progress);
|
||||
},
|
||||
onCompleted: () => {
|
||||
this.decompressFrp(version, downloadedFilePath)
|
||||
.then(data => {
|
||||
resolve(data);
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async deleteFrpVersion(githubReleaseId: number) {
|
||||
if (!githubReleaseId) {
|
||||
return;
|
||||
}
|
||||
const version = await this._versionDao.findByGithubReleaseId(
|
||||
githubReleaseId
|
||||
);
|
||||
if (this.frpcVersionExists(version)) {
|
||||
fs.rmSync(version.localPath, { recursive: true, force: true });
|
||||
await this._versionDao.deleteById(version._id);
|
||||
}
|
||||
}
|
||||
|
||||
async getFrpVersionsByGitHub(): Promise<Array<FrpcVersion>> {
|
||||
return new Promise<Array<FrpcVersion>>((resolve, reject) => {
|
||||
this._gitHubService
|
||||
.getGithubRepoAllReleases("fatedier/frp")
|
||||
.then(async (releases: Array<GithubRelease>) => {
|
||||
const versions: Array<FrpcVersion> =
|
||||
await this.githubRelease2FrpcVersion(releases);
|
||||
// const _versions: Array<FrpcVersion> = (this._versions = _versions);
|
||||
this._versions = versions;
|
||||
resolve(versions);
|
||||
})
|
||||
.catch(err => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
async getFrpVersionByLocalJson(): Promise<Array<FrpcVersion>> {
|
||||
return this.githubRelease2FrpcVersion(frpReleasesJson);
|
||||
}
|
||||
|
||||
getFrpVersion() {}
|
||||
|
||||
private findCurrentArchitectureAsset(assets: Array<GithubAsset>) {
|
||||
return assets.find((af: GithubAsset) => {
|
||||
return this._currFrpArch.every(item => af.name.includes(item));
|
||||
});
|
||||
}
|
||||
|
||||
private async githubRelease2FrpcVersion(
|
||||
releases: Array<GithubRelease>
|
||||
): Promise<Array<FrpcVersion>> {
|
||||
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);
|
||||
})
|
||||
.map(m => {
|
||||
const asset = this.findCurrentArchitectureAsset(m.assets);
|
||||
const download_count = m.assets.reduce(
|
||||
(sum, item) => sum + item.download_count,
|
||||
0
|
||||
);
|
||||
|
||||
const currVersion = allVersions.find(ff => ff.githubReleaseId === m.id);
|
||||
const v: FrpcVersion = {
|
||||
_id: "",
|
||||
githubReleaseId: m.id,
|
||||
githubAssetId: asset.id,
|
||||
githubCreatedAt: asset.created_at,
|
||||
name: m.name,
|
||||
assetName: asset.name,
|
||||
versionDownloadCount: download_count,
|
||||
assetDownloadCount: asset.download_count,
|
||||
browserDownloadUrl: asset.browser_download_url,
|
||||
downloaded: this.frpcVersionExists(currVersion),
|
||||
localPath: currVersion && currVersion.localPath,
|
||||
size: FileUtils.formatBytes(asset.size)
|
||||
};
|
||||
return v;
|
||||
});
|
||||
}
|
||||
|
||||
private frpcVersionExists(version: FrpcVersion): boolean {
|
||||
// const version = await this._versionDao.findByGithubReleaseId(
|
||||
// githubReleaseId
|
||||
// );
|
||||
|
||||
if (version) {
|
||||
return fs.existsSync(version.localPath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async importLocalFrpcVersion(filePath: string) {
|
||||
const checksum = FileUtils.calculateFileChecksum(filePath);
|
||||
const frpName = frpChecksums[checksum];
|
||||
if (frpName) {
|
||||
if (this._currFrpArch.every(item => frpName.includes(item))) {
|
||||
const version = this.getFrpVersionByAssetName(frpName);
|
||||
const existsVersion = await this._versionDao.findByGithubReleaseId(
|
||||
version.githubReleaseId
|
||||
);
|
||||
if (existsVersion) {
|
||||
throw new BusinessError(ResponseCode.VERSION_EXISTS);
|
||||
}
|
||||
return this.decompressFrp(version, filePath);
|
||||
} else {
|
||||
throw new BusinessError(ResponseCode.VERSION_ARGS_ERROR);
|
||||
}
|
||||
} else {
|
||||
throw new BusinessError(ResponseCode.UNKNOWN_VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
getFrpVersionByAssetName(assetName: string) {
|
||||
return this._versions.find(f => f.assetName === assetName);
|
||||
}
|
||||
|
||||
async decompressFrp(version: FrpcVersion, compressedPath: string) {
|
||||
const versionFilePath = path.join(
|
||||
PathUtils.getVersionStoragePath(),
|
||||
SecureUtils.calculateMD5(version.name)
|
||||
);
|
||||
const ext = path.extname(version.assetName);
|
||||
if (ext === GlobalConstant.ZIP_EXT) {
|
||||
this._systemService.decompressZipFile(compressedPath, versionFilePath);
|
||||
// todo delete frps and other file.
|
||||
} else if (
|
||||
ext === GlobalConstant.GZ_EXT &&
|
||||
version.assetName.includes(GlobalConstant.TAR_GZ_EXT)
|
||||
) {
|
||||
this._systemService.decompressTarGzFile(
|
||||
compressedPath,
|
||||
versionFilePath,
|
||||
() => {
|
||||
// rename frpc.
|
||||
const frpcFilePath = path.join(versionFilePath, "frpc");
|
||||
if (fs.existsSync(frpcFilePath)) {
|
||||
const newFrpcFilePath = path.join(
|
||||
versionFilePath,
|
||||
PathUtils.getFrpcFilename()
|
||||
);
|
||||
fs.renameSync(frpcFilePath, newFrpcFilePath);
|
||||
}
|
||||
// delete downloaded file.
|
||||
// todo has bug.
|
||||
const downloadedFile = path.join(
|
||||
PathUtils.getDownloadStoragePath(),
|
||||
version.assetName
|
||||
);
|
||||
if (fs.existsSync(downloadedFile)) {
|
||||
fs.rmSync(downloadedFile, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// todo 2025-02-23 delete downloaded file.
|
||||
version.localPath = versionFilePath;
|
||||
version.downloaded = true;
|
||||
return await this._versionDao.insert(version);
|
||||
}
|
||||
}
|
||||
|
||||
export default VersionService;
|
@ -1,90 +0,0 @@
|
||||
import Datastore from "nedb";
|
||||
import path from "path";
|
||||
import { app } from "electron";
|
||||
|
||||
import { logInfo, logError, LogModule, logDebug } from "../utils/log";
|
||||
import { maskSensitiveData } from "../utils/desensitize";
|
||||
|
||||
const configDB = new Datastore({
|
||||
autoload: true,
|
||||
filename: path.join(app.getPath("userData"), "config.db")
|
||||
});
|
||||
/**
|
||||
* 保存
|
||||
*/
|
||||
export const saveConfig = (
|
||||
document: FrpConfig,
|
||||
cb?: (err: Error | null, numberOfUpdated: number, upsert: boolean) => void
|
||||
) => {
|
||||
document["_id"] = "1";
|
||||
logDebug(
|
||||
LogModule.DB,
|
||||
`Saving configuration to the database. ${JSON.stringify(
|
||||
maskSensitiveData(document, [
|
||||
"serverAddr",
|
||||
"serverPort",
|
||||
"authToken",
|
||||
"user",
|
||||
"metaToken"
|
||||
])
|
||||
)}`
|
||||
);
|
||||
configDB.update(
|
||||
{ _id: "1" },
|
||||
document,
|
||||
{ upsert: true },
|
||||
(err, numberOfUpdated, upsert) => {
|
||||
if (err) {
|
||||
logError(
|
||||
LogModule.DB,
|
||||
`Error saving configuration: ${err.message}`
|
||||
);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Configuration saved successfully. Updated: ${numberOfUpdated}, Upsert: ${upsert}`
|
||||
); // 添加成功日志
|
||||
}
|
||||
if (cb) cb(err, numberOfUpdated, upsert);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 查找
|
||||
* @param cb
|
||||
*/
|
||||
export const getConfig = (
|
||||
cb: (err: Error | null, document: FrpConfig) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, "Retrieving configuration from the database."); // 添加信息日志
|
||||
configDB.findOne({ _id: "1" }, (err, document) => {
|
||||
if (err) {
|
||||
logError(
|
||||
LogModule.DB,
|
||||
`Error retrieving configuration: ${err.message}`
|
||||
); // 添加错误日志
|
||||
} else {
|
||||
logInfo(LogModule.DB, "Configuration retrieved successfully."); // 添加成功日志
|
||||
}
|
||||
cb(err, document);
|
||||
});
|
||||
};
|
||||
|
||||
export const clearConfig = (cb?: (err: Error | null, n: number) => void) => {
|
||||
logInfo(LogModule.DB, "Clearing all configurations from the database."); // 添加信息日志
|
||||
configDB.remove({}, { multi: true }, (err, n) => {
|
||||
if (err) {
|
||||
logError(
|
||||
LogModule.DB,
|
||||
`Error clearing configurations: ${err.message}`
|
||||
); // 添加错误日志
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Successfully cleared configurations. Number of documents removed: ${n}`
|
||||
); // 添加成功日志
|
||||
}
|
||||
if (cb) cb(err, n);
|
||||
});
|
||||
};
|
@ -1,143 +0,0 @@
|
||||
import Datastore from "nedb";
|
||||
import path from "path";
|
||||
import { app } from "electron";
|
||||
|
||||
import { logInfo, logError, LogModule, logDebug } from "../utils/log";
|
||||
|
||||
const proxyDB = new Datastore({
|
||||
autoload: true,
|
||||
filename: path.join(app.getPath("userData"), "proxy.db")
|
||||
});
|
||||
|
||||
export const insertProxy = (
|
||||
proxy: Proxy,
|
||||
cb?: (err: Error | null, document: Proxy) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Inserting proxy: ${JSON.stringify(proxy)}`);
|
||||
proxyDB.insert(proxy, (err, document) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error inserting proxy: ${err.message}`);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Proxy inserted successfully: ${JSON.stringify(document)}`
|
||||
);
|
||||
}
|
||||
if (cb) cb(err, document);
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteProxyById = (
|
||||
_id: string,
|
||||
cb?: (err: Error | null, n: number) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Deleting proxy with ID: ${_id}`);
|
||||
proxyDB.remove({ _id: _id }, (err, n) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error deleting proxy: ${err.message}`);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Proxy deleted successfully. Number of documents removed: ${n}`
|
||||
);
|
||||
}
|
||||
if (cb) cb(err, n);
|
||||
});
|
||||
};
|
||||
|
||||
export const updateProxyById = (
|
||||
proxy: Proxy,
|
||||
cb?: (err: Error | null, numberOfUpdated: number, upsert: boolean) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Updating proxy: ${JSON.stringify(proxy)}`);
|
||||
proxyDB.update(
|
||||
{ _id: proxy._id },
|
||||
proxy,
|
||||
{},
|
||||
(err, numberOfUpdated, upsert) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error updating proxy: ${err.message}`);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Proxy updated successfully. Updated: ${numberOfUpdated}, Upsert: ${upsert}`
|
||||
);
|
||||
}
|
||||
if (cb) cb(err, numberOfUpdated, upsert);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const listProxy = (
|
||||
callback: (err: Error | null, documents: Proxy[]) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Listing all proxies`);
|
||||
proxyDB.find({}, (err, documents) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error listing proxies: ${err.message}`);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Proxies listed successfully. Count: ${documents.length}`
|
||||
);
|
||||
}
|
||||
callback(err, documents);
|
||||
});
|
||||
};
|
||||
|
||||
export const getProxyById = (
|
||||
id: string,
|
||||
callback: (err: Error | null, document: Proxy) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Getting proxy by ID: ${id}`);
|
||||
proxyDB.findOne({ _id: id }, (err, document) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error getting proxy by ID: ${err.message}`);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Proxy retrieved successfully: ${JSON.stringify(document)}`
|
||||
);
|
||||
}
|
||||
callback(err, document);
|
||||
});
|
||||
};
|
||||
|
||||
export const clearProxy = (cb?: (err: Error | null, n: number) => void) => {
|
||||
logInfo(LogModule.DB, `Clearing all proxies`);
|
||||
proxyDB.remove({}, { multi: true }, (err, n) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error clearing proxies: ${err.message}`);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Proxies cleared successfully. Number of documents removed: ${n}`
|
||||
);
|
||||
}
|
||||
if (cb) cb(err, n);
|
||||
});
|
||||
};
|
||||
|
||||
export const updateProxyStatus = (
|
||||
id: string,
|
||||
st: boolean,
|
||||
cb?: (err: Error | null, numberOfUpdated: number, upsert: boolean) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Updating proxy status for ID: ${id} to ${st}`);
|
||||
proxyDB.update(
|
||||
{ _id: id },
|
||||
{ $set: { status: st } },
|
||||
{},
|
||||
(err, numberOfUpdated, upsert) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error updating proxy status: ${err.message}`);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Proxy status updated successfully. Updated: ${numberOfUpdated}, Upsert: ${upsert}`
|
||||
);
|
||||
}
|
||||
if (cb) cb(err, numberOfUpdated, upsert);
|
||||
}
|
||||
);
|
||||
};
|
@ -1,90 +0,0 @@
|
||||
import Datastore from "nedb";
|
||||
import path from "path";
|
||||
import { app } from "electron";
|
||||
|
||||
import { logInfo, logError, LogModule, logDebug } from "../utils/log";
|
||||
|
||||
const versionDB = new Datastore({
|
||||
autoload: true,
|
||||
filename: path.join(app.getPath("userData"), "version.db")
|
||||
});
|
||||
|
||||
/**
|
||||
* Insert version
|
||||
* @param version
|
||||
* @param cb
|
||||
*/
|
||||
export const insertVersion = (
|
||||
version: FrpVersion,
|
||||
cb?: (err: Error | null, document: any) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Inserting version: ${JSON.stringify(version)}`);
|
||||
versionDB.insert(version, (err, document) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error inserting version: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.DB, `Version inserted successfully: ${JSON.stringify(document)}`);
|
||||
}
|
||||
if (cb) cb(err, document);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* List versions
|
||||
* @param cb
|
||||
*/
|
||||
export const listVersion = (
|
||||
callback: (err: Error | null, documents: FrpVersion[]) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, "Listing all versions.");
|
||||
versionDB.find({}, (err, documents) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error listing versions: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.DB, `Successfully listed versions: ${documents.length} found.`);
|
||||
}
|
||||
callback(err, documents);
|
||||
});
|
||||
};
|
||||
|
||||
export const getVersionById = (
|
||||
id: number,
|
||||
callback: (err: Error | null, document: FrpVersion) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Retrieving version by ID: ${id}`);
|
||||
versionDB.findOne({ id: id }, (err, document) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error retrieving version by ID: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.DB, `Version retrieved successfully: ${JSON.stringify(document)}`);
|
||||
}
|
||||
callback(err, document);
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteVersionById = (
|
||||
id: string,
|
||||
callback: (err: Error | null, document: any) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Deleting version: ${id}`);
|
||||
versionDB.remove({ id: id }, (err, document) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error deleting version: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.DB, `Version deleted successfully: ${id}`);
|
||||
}
|
||||
callback(err, document);
|
||||
});
|
||||
};
|
||||
|
||||
export const clearVersion = (cb?: (err: Error | null, n: number) => void) => {
|
||||
logInfo(LogModule.DB, "Clearing all versions from the database.");
|
||||
versionDB.remove({}, { multi: true }, (err, n) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error clearing versions: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.DB, `Successfully cleared versions. Number of documents removed: ${n}`);
|
||||
}
|
||||
if (cb) cb(err, n);
|
||||
});
|
||||
};
|
29
electron/utils/FileUtils.ts
Normal file
29
electron/utils/FileUtils.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { createHash } from "crypto";
|
||||
import fs from "fs";
|
||||
|
||||
class FileUtils {
|
||||
public static formatBytes(bytes: number, decimals: number = 2): string {
|
||||
if (bytes === 0) return "0 Bytes";
|
||||
const k = 1024; // 1 KB = 1024 Bytes
|
||||
const dm = decimals < 0 ? 0 : decimals; // Ensure decimal places are not less than 0
|
||||
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k)); // Calculate unit index
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; // Return formatted string
|
||||
}
|
||||
|
||||
public static calculateFileChecksum(filePath: string) {
|
||||
const fileBuffer = fs.readFileSync(filePath);
|
||||
const hash = createHash("sha256");
|
||||
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;
|
21
electron/utils/IdUtils.ts
Normal file
21
electron/utils/IdUtils.ts
Normal file
@ -0,0 +1,21 @@
|
||||
class IdUtils {
|
||||
public static genUUID() {
|
||||
let uuidValue = "",
|
||||
k,
|
||||
randomValue;
|
||||
for (k = 0; k < 32; k++) {
|
||||
randomValue = (Math.random() * 16) | 0;
|
||||
|
||||
if (k == 8 || k == 12 || k == 16 || k == 20) {
|
||||
uuidValue += "-";
|
||||
}
|
||||
uuidValue += (
|
||||
k == 12 ? 4 : k == 16 ? (randomValue & 3) | 8 : randomValue
|
||||
).toString(16);
|
||||
}
|
||||
return uuidValue;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default IdUtils;
|
68
electron/utils/PathUtils.ts
Normal file
68
electron/utils/PathUtils.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import SecureUtils from "./SecureUtils";
|
||||
|
||||
import { app } from "electron";
|
||||
import path from "path";
|
||||
import FileUtils from "./FileUtils";
|
||||
|
||||
class PathUtils {
|
||||
public static getDownloadStoragePath() {
|
||||
const result = path.join(PathUtils.getAppData(), "download");
|
||||
FileUtils.mkdir(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static getVersionStoragePath() {
|
||||
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");
|
||||
}
|
||||
|
||||
public static getAppData() {
|
||||
return app.getPath("userData");
|
||||
}
|
||||
|
||||
public static getDataBaseStoragePath() {
|
||||
const result = path.join(PathUtils.getAppData(), "db");
|
||||
FileUtils.mkdir(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
40
electron/utils/ResponseUtils.ts
Normal file
40
electron/utils/ResponseUtils.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { BusinessError, ResponseCode } from "../core/BusinessError";
|
||||
|
||||
class ResponseUtils {
|
||||
public static success<T>(data?: any, message?: string) {
|
||||
const [bizCode, message2] = ResponseCode.SUCCESS.split(";");
|
||||
const resp: ApiResponse<T> = {
|
||||
bizCode: bizCode,
|
||||
data: data,
|
||||
message: message || message2
|
||||
};
|
||||
return resp;
|
||||
}
|
||||
|
||||
// public static fail(bizCode?: string, message?: string) {
|
||||
// const resp: ApiResponse<any> = {
|
||||
// success: false,
|
||||
// bizCode: bizCode,
|
||||
// data: null,
|
||||
// message: message || "internal error."
|
||||
// };
|
||||
// return resp;
|
||||
// }
|
||||
|
||||
public static fail(err: Error) {
|
||||
if (!(err instanceof BusinessError)) {
|
||||
err = new BusinessError(ResponseCode.INTERNAL_ERROR);
|
||||
}
|
||||
const bizCode = (err as BusinessError).bizCode;
|
||||
const message = (err as BusinessError).message;
|
||||
|
||||
const resp: ApiResponse<any> = {
|
||||
bizCode: bizCode,
|
||||
data: null,
|
||||
message: message
|
||||
};
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
|
||||
export default ResponseUtils;
|
11
electron/utils/SecureUtils.ts
Normal file
11
electron/utils/SecureUtils.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { createHash } from "crypto";
|
||||
|
||||
class SecureUtils {
|
||||
public static calculateMD5(str: string): string {
|
||||
const hash = createHash("md5");
|
||||
hash.update(str);
|
||||
return hash.digest("hex");
|
||||
}
|
||||
}
|
||||
|
||||
export default SecureUtils;
|
@ -1,12 +0,0 @@
|
||||
export const maskSensitiveData = (
|
||||
obj: Record<string, any>,
|
||||
keysToMask: string[]
|
||||
) => {
|
||||
const maskedObj = JSON.parse(JSON.stringify(obj));
|
||||
keysToMask.forEach(key => {
|
||||
if (maskedObj.hasOwnProperty(key)) {
|
||||
maskedObj[key] = "***";
|
||||
}
|
||||
});
|
||||
return maskedObj;
|
||||
};
|
@ -1,19 +0,0 @@
|
||||
import { createHash } from "crypto";
|
||||
|
||||
export const formatBytes = (bytes: number, decimals: number = 2): string => {
|
||||
if (bytes === 0) return "0 Bytes";
|
||||
const k = 1024; // 1 KB = 1024 Bytes
|
||||
const dm = decimals < 0 ? 0 : decimals; // Ensure decimal places are not less than 0
|
||||
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k)); // Calculate unit index
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; // Return formatted string
|
||||
};
|
||||
|
||||
export const calculateFileChecksum = (filePath: string): string => {
|
||||
const fs = require("fs");
|
||||
const fileBuffer = fs.readFileSync(filePath);
|
||||
const hash = createHash("sha256");
|
||||
hash.update(fileBuffer);
|
||||
return hash.digest("hex");
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
import log from "electron-log";
|
||||
// 定义模块枚举
|
||||
export enum LogModule {
|
||||
APP = "app",
|
||||
FRP_CLIENT = "frpc client",
|
||||
GITHUB = "github",
|
||||
DB = "db"
|
||||
}
|
||||
|
||||
export const initLog = () => {
|
||||
log.transports.file.level = "debug";
|
||||
log.transports.console.level = "debug";
|
||||
};
|
||||
|
||||
// 自定义日志输出函数,记录到指定业务模块
|
||||
export const logInfo = (module: LogModule, message: string) => {
|
||||
log.info(`[${module}] ${message}`);
|
||||
};
|
||||
|
||||
export const logError = (module: LogModule, message: string) => {
|
||||
log.error(`[${module}] ${message}`);
|
||||
};
|
||||
|
||||
export const logDebug = (module: LogModule, message: string) => {
|
||||
log.debug(`[${module}] ${message}`);
|
||||
};
|
||||
|
||||
export const logWarn = (module: LogModule, message: string) => {
|
||||
log.warn(`[${module}] ${message}`);
|
||||
};
|
12
package.json
12
package.json
@ -50,7 +50,7 @@
|
||||
"autoprefixer": "^10.4.15",
|
||||
"canvas-confetti": "^1.9.0",
|
||||
"cssnano": "^6.0.1",
|
||||
"electron": "^26.6.10",
|
||||
"electron": "22.3.27",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-builder-squirrel-windows": "^24.13.3",
|
||||
"element-plus": "^2.4.2",
|
||||
@ -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,6 +82,10 @@
|
||||
"intro.js": "^8.0.0-beta.1",
|
||||
"isbinaryfile": "4.0.10",
|
||||
"js-base64": "^3.7.7",
|
||||
"lodash": "^4.17.21",
|
||||
"pinia": "^3.0.1",
|
||||
"smol-toml": "^1.3.1",
|
||||
"snowflakify": "^1.0.5",
|
||||
"tar": "^6.2.0",
|
||||
"unused-filename": "^4.0.1",
|
||||
"uuid": "^10.0.0"
|
||||
|
@ -2,15 +2,17 @@
|
||||
import { computed, defineComponent, onMounted, ref } from "vue";
|
||||
import router from "@/router";
|
||||
import { RouteRecordRaw } from "vue-router";
|
||||
import pkg from "../../../package.json";
|
||||
import Intro from "@/intro";
|
||||
import "intro.js/introjs.css";
|
||||
import confetti from "canvas-confetti/src/confetti.js";
|
||||
import { useFrpcDesktopStore } from "@/store/frpcDesktop";
|
||||
import pkg from "../../../package.json";
|
||||
|
||||
defineComponent({
|
||||
name: "AppMain"
|
||||
});
|
||||
|
||||
const frpcDesktopStore = useFrpcDesktopStore();
|
||||
const routes = ref<Array<RouteRecordRaw>>([]);
|
||||
const guideSteps = ref({
|
||||
Home: {
|
||||
@ -93,11 +95,14 @@ onMounted(() => {
|
||||
<template>
|
||||
<div class="left-menu-container drop-shadow-xl">
|
||||
<div class="logo-container">
|
||||
<img
|
||||
src="/logo/only/128x128.png"
|
||||
class="logo animate__animated animate__bounceInLeft"
|
||||
alt="Logo"
|
||||
/>
|
||||
<!-- <img-->
|
||||
<!-- src="/logo/only/128x128.png"-->
|
||||
<!-- class="logo animate__animated animate__bounceInLeft"-->
|
||||
<!-- alt="Logo"-->
|
||||
<!-- />-->
|
||||
<!-- <el-badge :value="'v1.1.2'" class="logo" type="primary" :offset="[-10, 42]">-->
|
||||
<img src="/logo/only/128x128.png" class="logo" alt="Logo" />
|
||||
<!-- </el-badge>-->
|
||||
</div>
|
||||
<ul class="menu-container">
|
||||
<!-- enter-active-class="animate__animated animate__bounceIn"-->
|
||||
@ -119,18 +124,24 @@ onMounted(() => {
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu-footer mb-2">
|
||||
<!-- <div-->
|
||||
<!-- class="menu animate__animated"-->
|
||||
<!-- @click="handleOpenGitHubReleases"-->
|
||||
<!-- :data-step="guideSteps.Version?.step"-->
|
||||
<!-- :data-intro="guideSteps.Version?.intro"-->
|
||||
<!-- data-position="top"-->
|
||||
<!-- >-->
|
||||
<!-- <IconifyIconOffline-->
|
||||
<!-- class="animate__animated"-->
|
||||
<!-- icon="attach-money-rounded"-->
|
||||
<!-- ></IconifyIconOffline>-->
|
||||
<!-- </div>-->
|
||||
<!-- <el-tag-->
|
||||
<!-- :type="frpcProcessStore.running ? 'primary' : 'warning'"-->
|
||||
<!-- effect="light"-->
|
||||
<!-- size="small"-->
|
||||
<!-- >{{ frpcProcessStore.running ? "运行中" : "已断开" }}-->
|
||||
<!-- </el-tag>-->
|
||||
<!-- <div-->
|
||||
<!-- class="menu animate__animated"-->
|
||||
<!-- @click="handleOpenGitHubReleases"-->
|
||||
<!-- :data-step="guideSteps.Version?.step"-->
|
||||
<!-- :data-intro="guideSteps.Version?.intro"-->
|
||||
<!-- data-position="top"-->
|
||||
<!-- >-->
|
||||
<!-- <IconifyIconOffline-->
|
||||
<!-- class="animate__animated"-->
|
||||
<!-- icon="attach-money-rounded"-->
|
||||
<!-- ></IconifyIconOffline>-->
|
||||
<!-- </div>-->
|
||||
<div
|
||||
class="version animate__animated"
|
||||
@click="handleOpenGitHubReleases"
|
||||
|
31
src/main.ts
31
src/main.ts
@ -1,22 +1,33 @@
|
||||
import {createApp} from "vue";
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import "./styles/index.scss";
|
||||
import 'animate.css';
|
||||
import "animate.css";
|
||||
import ElementPlus from "element-plus";
|
||||
import {
|
||||
IconifyIconOffline,
|
||||
IconifyIconOnline,
|
||||
IconifyIconOffline,
|
||||
IconifyIconOnline
|
||||
} from "./components/IconifyIcon";
|
||||
import { createPinia } from "pinia";
|
||||
import { useFrpcDesktopStore } from "@/store/frpcDesktop";
|
||||
|
||||
const pinia = createPinia();
|
||||
|
||||
const app = createApp(App);
|
||||
app.component("IconifyIconOffline", IconifyIconOffline);
|
||||
app.component("IconifyIconOnline", IconifyIconOnline);
|
||||
|
||||
app
|
||||
.use(router)
|
||||
.use(ElementPlus)
|
||||
.use(pinia)
|
||||
.mount("#app")
|
||||
.$nextTick(() => {
|
||||
postMessage({ payload: "removeLoading" }, "*");
|
||||
const frpcDesktopStore = useFrpcDesktopStore();
|
||||
frpcDesktopStore.onListenerFrpcProcessRunning();
|
||||
frpcDesktopStore.onListenerDownloadedVersion();
|
||||
frpcDesktopStore.refreshDownloadedVersion();
|
||||
})
|
||||
.then(r => {});
|
||||
|
||||
app.use(router)
|
||||
.use(ElementPlus)
|
||||
.mount("#app")
|
||||
.$nextTick(() => {
|
||||
postMessage({payload: "removeLoading"}, "*");
|
||||
});
|
||||
|
36
src/store/frpcDesktop.ts
Normal file
36
src/store/frpcDesktop.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { on, onListener, send } from "@/utils/ipcUtils";
|
||||
import { ipcRouters, listeners } from "../../electron/core/IpcRouter";
|
||||
|
||||
export const useFrpcDesktopStore = defineStore("frpcDesktop", {
|
||||
state: () => ({ isRunning: false, versions: [] }),
|
||||
getters: {
|
||||
running: state => state.isRunning,
|
||||
downloadedVersions: state => state.versions
|
||||
},
|
||||
actions: {
|
||||
onListenerFrpcProcessRunning() {
|
||||
onListener(listeners.watchFrpcProcess, data => {
|
||||
console.log("watchFrpcProcess", data);
|
||||
this.isRunning = data;
|
||||
});
|
||||
|
||||
on(ipcRouters.LAUNCH.getStatus, data => {
|
||||
console.log("getStatus", data);
|
||||
this.isRunning = data;
|
||||
});
|
||||
},
|
||||
|
||||
onListenerDownloadedVersion() {
|
||||
on(ipcRouters.VERSION.getDownloadedVersions, data => {
|
||||
this.versions = data;
|
||||
});
|
||||
},
|
||||
refreshRunning() {
|
||||
send(ipcRouters.LAUNCH.getStatus);
|
||||
},
|
||||
refreshDownloadedVersion() {
|
||||
send(ipcRouters.VERSION.getDownloadedVersions);
|
||||
}
|
||||
}
|
||||
});
|
@ -6,12 +6,13 @@ $danger-color: #F56C6C;
|
||||
background: $main-bg;
|
||||
width: calc(100% - 60px);
|
||||
height: 100vh;
|
||||
padding: 20px;
|
||||
padding: 15px;
|
||||
|
||||
|
||||
.main {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
//overflow: hidden;
|
||||
}
|
||||
|
||||
.app-container-breadcrumb {
|
||||
@ -26,7 +27,7 @@ $danger-color: #F56C6C;
|
||||
.breadcrumb-left {
|
||||
color: $primary-color;
|
||||
font-size: 36px;
|
||||
height: 30px;
|
||||
height: 32px;
|
||||
|
||||
svg {
|
||||
vertical-align: top;
|
||||
|
@ -1,19 +0,0 @@
|
||||
export function clone<T>(value: T): T {
|
||||
/** 空 */
|
||||
if (!value) return value;
|
||||
/** 数组 */
|
||||
if (Array.isArray(value))
|
||||
return value.map(item => clone(item)) as unknown as T;
|
||||
/** 日期 */
|
||||
if (value instanceof Date) return new Date(value) as unknown as T;
|
||||
/** 普通对象 */
|
||||
if (typeof value === "object") {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([k, v]: [string, any]) => {
|
||||
return [k, clone(v)];
|
||||
})
|
||||
) as unknown as T;
|
||||
}
|
||||
/** 基本类型 */
|
||||
return value;
|
||||
}
|
72
src/utils/ipcUtils.ts
Normal file
72
src/utils/ipcUtils.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { ipcRenderer } from "electron";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
export const send = (router: IpcRouter, params?: any) => {
|
||||
ipcRenderer.send(router.path, params);
|
||||
};
|
||||
|
||||
|
||||
// export const invoke = (router: IpcRouter, params?: any) => {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// ipcRenderer
|
||||
// .invoke(router.path, params)
|
||||
// .then((args: ApiResponse<any>) => {
|
||||
// const { success, data, message } = args;
|
||||
// if (success) {
|
||||
// resolve(data);
|
||||
// } else {
|
||||
// // reject(new Error(message));
|
||||
// }
|
||||
// })
|
||||
// .catch(err => reject(err));
|
||||
// });
|
||||
// };
|
||||
|
||||
export const on = (
|
||||
router: IpcRouter,
|
||||
listerHandler: (data: any) => void,
|
||||
errHandler?: (bizCode: string, message: string) => void
|
||||
) => {
|
||||
ipcRenderer.on(`${router.path}:hook`, (event, args: ApiResponse<any>) => {
|
||||
const { bizCode, data, message } = args;
|
||||
if (bizCode === "A1000") {
|
||||
listerHandler(data);
|
||||
} else {
|
||||
if (errHandler) {
|
||||
errHandler(bizCode, message);
|
||||
} else {
|
||||
// ElMessageBox.alert(message,"出错了");
|
||||
ElMessage({
|
||||
message: message,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
// reject(new Error(message));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const onListener = (
|
||||
listener: Listener,
|
||||
listerHandler: (data: any) => void
|
||||
) => {
|
||||
// return new Promise((resolve, reject) => {
|
||||
ipcRenderer.on(`${listener.channel}`, (event, args: ApiResponse<any>) => {
|
||||
const { bizCode, data, message } = args;
|
||||
if (bizCode === "A1000") {
|
||||
listerHandler(data);
|
||||
}
|
||||
});
|
||||
// });
|
||||
};
|
||||
|
||||
export const removeRouterListeners = (router: IpcRouter) => {
|
||||
ipcRenderer.removeAllListeners(`${router.path}:hook`);
|
||||
};
|
||||
|
||||
export const removeRouterListeners2 = (listen: Listener) => {
|
||||
ipcRenderer.removeAllListeners(`${listen.channel}`);
|
||||
}
|
||||
// export const removeAllListeners = (listen: Listener) => {
|
||||
// ipcRenderer.removeAllListeners(`${listen.channel}:hook`);
|
||||
// };
|
@ -1,11 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import {computed, defineComponent, onMounted, onUnmounted, ref} from "vue";
|
||||
import {ipcRenderer} from "electron";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import { computed, defineComponent, onMounted, onUnmounted, ref } from "vue";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { Icon } from "@iconify/vue";
|
||||
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
|
||||
import pkg from '../../../package.json';
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
|
||||
import pkg from "../../../package.json";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { send } from "@/utils/ipcUtils";
|
||||
import { ipcRouters } from "../../../electron/core/IpcRouter";
|
||||
|
||||
/**
|
||||
* 最后一个版本号
|
||||
@ -17,143 +18,161 @@ const isLastVersion = computed(() => {
|
||||
return true;
|
||||
}
|
||||
// tagName相对固定
|
||||
const tagName = latestVersionInfo.value['tag_name']
|
||||
console.log(tagName, latestVersionInfo.value, 'tagName')
|
||||
const tagName = latestVersionInfo.value["tag_name"];
|
||||
console.log(tagName, latestVersionInfo.value, "tagName");
|
||||
if (!tagName) {
|
||||
return true;
|
||||
}
|
||||
// 最后版本号
|
||||
const lastVersion = tagName.replace('v', '').toString();
|
||||
const lastVersion = tagName.replace("v", "").toString();
|
||||
const currVersion = pkg.version;
|
||||
console.log(lastVersion, currVersion, currVersion >= lastVersion, "isLast")
|
||||
console.log(lastVersion, currVersion, currVersion >= lastVersion, "isLast");
|
||||
// console.log()
|
||||
return currVersion >= lastVersion;
|
||||
// return false;
|
||||
})
|
||||
});
|
||||
/**
|
||||
* 打开github issues
|
||||
*/
|
||||
const handleOpenGitHubIssues = () => {
|
||||
ipcRenderer.send("common.openUrl", "https://github.com/luckjiawei/frpc-desktop/issues")
|
||||
}
|
||||
send(ipcRouters.SYSTEM.openUrl, {
|
||||
url: "https://github.com/luckjiawei/frpc-desktop/issues"
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开github主页
|
||||
*/
|
||||
const handleOpenGitHub = () => {
|
||||
ipcRenderer.send("common.openUrl", "https://github.com/luckjiawei/frpc-desktop")
|
||||
}
|
||||
send(ipcRouters.SYSTEM.openUrl, {
|
||||
url: "https://github.com/luckjiawei/frpc-desktop"
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开捐赠界面
|
||||
*/
|
||||
const handleOpenDonate = () => {
|
||||
ipcRenderer.send("common.openUrl", "https://jwinks.com/donate")
|
||||
}
|
||||
|
||||
send(ipcRouters.SYSTEM.openUrl, {
|
||||
url: "https://jwinks.com/donate"
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开文档
|
||||
*/
|
||||
const handleOpenDoc = () => {
|
||||
ipcRenderer.send("common.openUrl", "https://jwinks.com/p/frp")
|
||||
}
|
||||
|
||||
send(ipcRouters.SYSTEM.openUrl, {
|
||||
url: "https://jwinks.com/p/frp"
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取最后一个版本
|
||||
*/
|
||||
const handleGetLastVersion = () => {
|
||||
|
||||
ipcRenderer.send("github.getFrpcDesktopLastVersions")
|
||||
}
|
||||
ipcRenderer.send("github.getFrpcDesktopLastVersions");
|
||||
};
|
||||
|
||||
const handleOpenNewVersion = () => {
|
||||
ipcRenderer.send("common.openUrl", latestVersionInfo.value['html_url'])
|
||||
}
|
||||
ipcRenderer.send("common.openUrl", latestVersionInfo.value["html_url"]);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
ipcRenderer.on("github.getFrpcDesktopLastVersionsHook", (event, args) => {
|
||||
latestVersionInfo.value = args;
|
||||
console.log(latestVersionInfo.value, '1')
|
||||
console.log(latestVersionInfo.value, "1");
|
||||
if (!isLastVersion.value) {
|
||||
let content = latestVersionInfo.value.body
|
||||
content = content.replaceAll('\n', '<br/>')
|
||||
let content = latestVersionInfo.value.body;
|
||||
content = content.replaceAll("\n", "<br/>");
|
||||
ElMessageBox.alert(content, `🎉 发现新版本 ${args.name}`, {
|
||||
showCancelButton: true,
|
||||
cancelButtonText: "关闭",
|
||||
dangerouslyUseHTMLString: true,
|
||||
confirmButtonText: "去下载"
|
||||
}).then(() => {
|
||||
handleOpenNewVersion()
|
||||
})
|
||||
handleOpenNewVersion();
|
||||
});
|
||||
} else {
|
||||
ElMessage({
|
||||
message: "当前已是最新版本",
|
||||
type: "success"
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
handleGetLastVersion();
|
||||
})
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
ipcRenderer.removeAllListeners("github.getFrpcDesktopLastVersionsHook");
|
||||
})
|
||||
});
|
||||
|
||||
defineComponent({
|
||||
name: "About"
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<breadcrumb/>
|
||||
<breadcrumb />
|
||||
<div class="app-container-breadcrumb">
|
||||
<div
|
||||
class="w-full h-full bg-white p-4 rounded drop-shadow-lg overflow-y-auto flex justify-center items-center flex-col"
|
||||
class="w-full h-full bg-white p-4 rounded drop-shadow-lg overflow-y-auto flex justify-center items-center flex-col"
|
||||
>
|
||||
<img src="/logo/pack/1024x1024.png"
|
||||
class="w-[95px] h-[95px] mt-[-50px] animate__animated animate__flip" alt="Logo"/>
|
||||
<img
|
||||
src="/logo/pack/1024x1024.png"
|
||||
class="w-[95px] h-[95px] mt-[-50px] animate__animated animate__flip"
|
||||
alt="Logo"
|
||||
/>
|
||||
<div class="mt-[8px] text-2xl">Frpc Desktop</div>
|
||||
<div class="mt-[8px] text-neutral-400 flex items-center">
|
||||
<el-link
|
||||
:class="!isLastVersion? 'line-through': ''"
|
||||
class="ml-2 font-bold">v{{ pkg.version }}
|
||||
:class="!isLastVersion ? 'line-through' : ''"
|
||||
class="ml-2 font-bold"
|
||||
>v{{ pkg.version }}
|
||||
</el-link>
|
||||
<el-link v-if="!isLastVersion && latestVersionInfo"
|
||||
@click="handleOpenNewVersion"
|
||||
class="ml-2 text-[#67C23A] font-bold"
|
||||
type="success">v{{ latestVersionInfo.name }}
|
||||
<el-link
|
||||
v-if="!isLastVersion && latestVersionInfo"
|
||||
@click="handleOpenNewVersion"
|
||||
class="ml-2 text-[#67C23A] font-bold"
|
||||
type="success"
|
||||
>v{{ latestVersionInfo.name }}
|
||||
</el-link>
|
||||
<IconifyIconOffline class="ml-1.5 cursor-pointer check-update" icon="refresh-rounded"
|
||||
@click="handleGetLastVersion"/>
|
||||
<IconifyIconOffline
|
||||
class="ml-1.5 cursor-pointer check-update"
|
||||
icon="refresh-rounded"
|
||||
@click="handleGetLastVersion"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-[8px] text-sm text-center">
|
||||
<p>
|
||||
🎉 {{ pkg.description }}
|
||||
</p>
|
||||
<p>
|
||||
开机自启 / 可视化配置 / 免费开源
|
||||
</p>
|
||||
<p>🎉 {{ pkg.description }}</p>
|
||||
<p>开机自启 / 可视化配置 / 免费开源</p>
|
||||
</div>
|
||||
<div class="mt-[12px]">
|
||||
<el-button plain type="success" @click="handleOpenDoc">
|
||||
<IconifyIconOffline class="cursor-pointer mr-2" icon="description"/>
|
||||
<IconifyIconOffline
|
||||
class="cursor-pointer mr-2"
|
||||
icon="description"
|
||||
/>
|
||||
使用教程
|
||||
</el-button>
|
||||
<el-button plain type="success" @click="handleOpenDonate">
|
||||
<IconifyIconOffline class="cursor-pointer mr-2" icon="volunteer-activism-sharp"/>
|
||||
<IconifyIconOffline
|
||||
class="cursor-pointer mr-2"
|
||||
icon="volunteer-activism-sharp"
|
||||
/>
|
||||
捐赠名单
|
||||
</el-button>
|
||||
<el-button plain type="primary" @click="handleOpenGitHub">
|
||||
<Icon class="cursor-pointer mr-2" icon="logos:github-icon"/>
|
||||
<Icon class="cursor-pointer mr-2" icon="logos:github-icon" />
|
||||
仓库地址
|
||||
</el-button>
|
||||
<el-button type="danger" plain @click="handleOpenGitHubIssues">
|
||||
<IconifyIconOffline class="cursor-pointer mr-2" icon="question-mark"/>
|
||||
<IconifyIconOffline
|
||||
class="cursor-pointer mr-2"
|
||||
icon="question-mark"
|
||||
/>
|
||||
反馈问题
|
||||
</el-button>
|
||||
</div>
|
||||
@ -164,6 +183,6 @@ defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.check-update:hover {
|
||||
color: #5F3BB0;
|
||||
color: #5f3bb0;
|
||||
}
|
||||
</style>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,19 @@
|
||||
<script lang="ts" setup>
|
||||
import { defineComponent, onMounted, onUnmounted, ref } from "vue";
|
||||
import { ipcRenderer } from "electron";
|
||||
import moment from "moment";
|
||||
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { useDebounceFn } from "@vueuse/core";
|
||||
import IconifyIconOffline from "@/components/IconifyIcon/src/iconifyIconOffline";
|
||||
import { on, removeRouterListeners, send } from "@/utils/ipcUtils";
|
||||
import { ipcRouters } from "../../../electron/core/IpcRouter";
|
||||
import { useFrpcDesktopStore } from "@/store/frpcDesktop";
|
||||
|
||||
defineComponent({
|
||||
name: "Download"
|
||||
});
|
||||
|
||||
const versions = ref<Array<FrpVersion>>([]);
|
||||
const versions = ref<Array<FrpcVersion>>([]);
|
||||
const loading = ref(1);
|
||||
const downloadPercentage = ref(0);
|
||||
const downloading = ref<Map<number, number>>(new Map<number, number>());
|
||||
@ -22,32 +24,31 @@ const mirrors = ref<Array<GitHubMirror>>([
|
||||
name: "github"
|
||||
}
|
||||
]);
|
||||
const frpcDesktopStore = useFrpcDesktopStore();
|
||||
|
||||
/**
|
||||
* 获取版本
|
||||
*/
|
||||
const handleLoadVersions = () => {
|
||||
ipcRenderer.send("github.getFrpVersions", currMirror.value);
|
||||
const handleLoadAllVersions = () => {
|
||||
send(ipcRouters.VERSION.getVersions);
|
||||
};
|
||||
|
||||
/**
|
||||
* 下载
|
||||
* @param version
|
||||
*/
|
||||
const handleDownload = useDebounceFn((version: FrpVersion) => {
|
||||
// console.log(version, currMirror.value);
|
||||
ipcRenderer.send("github.download", {
|
||||
versionId: version.id,
|
||||
mirror: currMirror.value
|
||||
const handleDownload = useDebounceFn((version: FrpcVersion) => {
|
||||
send(ipcRouters.VERSION.downloadVersion, {
|
||||
githubReleaseId: version.githubReleaseId
|
||||
});
|
||||
downloading.value.set(version.id, 0);
|
||||
downloading.value.set(version.githubReleaseId, 0);
|
||||
}, 300);
|
||||
|
||||
/**
|
||||
* 删除下载
|
||||
* @param version
|
||||
*/
|
||||
const handleDeleteVersion = useDebounceFn((version: FrpVersion) => {
|
||||
const handleDeleteVersion = useDebounceFn((version: FrpcVersion) => {
|
||||
ElMessageBox.alert(
|
||||
`确认要删除 <span class="text-primary font-bold">${version.name} </span> 吗?`,
|
||||
"提示",
|
||||
@ -58,95 +59,103 @@ const handleDeleteVersion = useDebounceFn((version: FrpVersion) => {
|
||||
confirmButtonText: "删除"
|
||||
}
|
||||
).then(() => {
|
||||
ipcRenderer.send("github.deleteVersion", {
|
||||
id: version.id,
|
||||
absPath: version.absPath
|
||||
send(ipcRouters.VERSION.deleteDownloadedVersion, {
|
||||
githubReleaseId: version.githubReleaseId
|
||||
});
|
||||
});
|
||||
}, 300);
|
||||
|
||||
const handleInitDownloadHook = () => {
|
||||
ipcRenderer.on("Download.frpVersionHook", (event, args) => {
|
||||
loading.value--;
|
||||
versions.value = args.map(m => {
|
||||
m.published_at = moment(m.published_at).format("YYYY-MM-DD");
|
||||
return m as FrpVersion;
|
||||
}) as Array<FrpVersion>;
|
||||
console.log(versions, "versions");
|
||||
});
|
||||
// 进度监听
|
||||
ipcRenderer.on("Download.frpVersionDownloadOnProgress", (event, args) => {
|
||||
const { id, progress } = args;
|
||||
downloading.value.set(
|
||||
id,
|
||||
Number(Number(progress.percent * 100).toFixed(2))
|
||||
);
|
||||
});
|
||||
ipcRenderer.on("Download.frpVersionDownloadOnCompleted", (event, args) => {
|
||||
downloading.value.delete(args);
|
||||
const version: FrpVersion | undefined = versions.value.find(
|
||||
f => f.id === args
|
||||
);
|
||||
if (version) {
|
||||
version.download_completed = true;
|
||||
}
|
||||
});
|
||||
ipcRenderer.on("Download.deleteVersion.hook", (event, args) => {
|
||||
const { err, data } = args;
|
||||
if (!err) {
|
||||
loading.value++;
|
||||
ElMessage({
|
||||
type: "success",
|
||||
message: "删除成功"
|
||||
});
|
||||
handleLoadVersions();
|
||||
}
|
||||
});
|
||||
ipcRenderer.on("Download.importFrpFile.hook", (event, args) => {
|
||||
const { success, data } = args;
|
||||
console.log(args);
|
||||
|
||||
// if (err) {
|
||||
loading.value++;
|
||||
ElMessage({
|
||||
type: success ? "success" : "error",
|
||||
message: data
|
||||
});
|
||||
handleLoadVersions();
|
||||
// }
|
||||
});
|
||||
};
|
||||
|
||||
const handleMirrorChange = () => {
|
||||
handleLoadVersions();
|
||||
handleLoadAllVersions();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
handleLoadVersions();
|
||||
handleInitDownloadHook();
|
||||
// ipcRenderer.invoke("process").then((r: any) => {
|
||||
// console.log(r, "rrr");
|
||||
// });
|
||||
handleLoadAllVersions();
|
||||
|
||||
on(ipcRouters.VERSION.getVersions, data => {
|
||||
versions.value = data.map(m => {
|
||||
m.githubCreatedAt = moment(m.githubCreatedAt).format("YYYY-MM-DD");
|
||||
return m as FrpcVersion;
|
||||
}) as Array<FrpcVersion>;
|
||||
loading.value--;
|
||||
});
|
||||
|
||||
on(ipcRouters.VERSION.downloadVersion, data => {
|
||||
const { githubReleaseId, completed, percent } = data;
|
||||
if (completed) {
|
||||
downloading.value.delete(githubReleaseId);
|
||||
const version: FrpcVersion | undefined = versions.value.find(
|
||||
f => f.githubReleaseId === githubReleaseId
|
||||
);
|
||||
if (version) {
|
||||
version.downloaded = true;
|
||||
}
|
||||
} else {
|
||||
downloading.value.set(
|
||||
githubReleaseId,
|
||||
Number(Number(percent * 100).toFixed(2))
|
||||
);
|
||||
}
|
||||
frpcDesktopStore.refreshDownloadedVersion();
|
||||
});
|
||||
|
||||
on(ipcRouters.VERSION.deleteDownloadedVersion, () => {
|
||||
loading.value++;
|
||||
ElMessage({
|
||||
type: "success",
|
||||
message: "删除成功"
|
||||
});
|
||||
handleLoadAllVersions();
|
||||
frpcDesktopStore.refreshDownloadedVersion();
|
||||
});
|
||||
|
||||
on(
|
||||
ipcRouters.VERSION.importLocalFrpcVersion,
|
||||
data => {
|
||||
const { canceled } = data;
|
||||
if (!canceled) {
|
||||
loading.value++;
|
||||
ElMessage({
|
||||
type: "success",
|
||||
message: "导入成功"
|
||||
});
|
||||
handleLoadAllVersions();
|
||||
frpcDesktopStore.refreshDownloadedVersion();
|
||||
}
|
||||
},
|
||||
(bizCode: string, message: string) => {
|
||||
if (bizCode === "B1002") {
|
||||
// 导入失败,版本已存在
|
||||
ElMessageBox.alert(`${message}`, `导入失败`);
|
||||
}
|
||||
if (bizCode === "B1003") {
|
||||
// 所选 frp 架构与操作系统不符
|
||||
ElMessageBox.alert(`${message}`, `导入失败`);
|
||||
}
|
||||
if (bizCode === "B1004") {
|
||||
// 无法识别文件
|
||||
ElMessageBox.alert(`${message}`, `导入失败`);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const handleImportFrp = () => {
|
||||
ipcRenderer.send("download.importFrpFile");
|
||||
send(ipcRouters.VERSION.importLocalFrpcVersion);
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
ipcRenderer.removeAllListeners("Download.frpVersionDownloadOnProgress");
|
||||
ipcRenderer.removeAllListeners("Download.frpVersionDownloadOnCompleted");
|
||||
ipcRenderer.removeAllListeners("Download.frpVersionHook");
|
||||
ipcRenderer.removeAllListeners("Download.deleteVersion.hook");
|
||||
ipcRenderer.removeAllListeners("Download.importFrpFile.hook");
|
||||
removeRouterListeners(ipcRouters.VERSION.deleteDownloadedVersion);
|
||||
removeRouterListeners(ipcRouters.VERSION.downloadVersion);
|
||||
removeRouterListeners(ipcRouters.VERSION.getVersions);
|
||||
removeRouterListeners(ipcRouters.VERSION.importLocalFrpcVersion);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="main">
|
||||
<!-- <breadcrumb> -->
|
||||
<breadcrumb>
|
||||
<div class="flex">
|
||||
<div class="h-full flex items-center justify-center mr-4">
|
||||
<div class="h-full flex items-center justify-center mr-3">
|
||||
<span class="text-sm font-bold">下载源: </span>
|
||||
<el-select
|
||||
class="w-40"
|
||||
@ -161,11 +170,10 @@ onUnmounted(() => {
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<el-button class="mr-2" type="primary" @click="handleImportFrp">
|
||||
<el-button type="primary" @click="handleImportFrp">
|
||||
<IconifyIconOffline icon="unarchive" />
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- <div-->
|
||||
<!-- class="cursor-pointer h-[36px] w-[36px] bg-[#5f3bb0] rounded text-white flex justify-center items-center"-->
|
||||
<!-- @click="handleOpenInsert"-->
|
||||
@ -181,7 +189,7 @@ onUnmounted(() => {
|
||||
<div class="app-container-breadcrumb pr-2" v-loading="loading > 0">
|
||||
<div class="w-full">
|
||||
<template v-if="versions && versions.length > 0">
|
||||
<el-row :gutter="20">
|
||||
<el-row :gutter="15">
|
||||
<!-- <el-col :span="24">-->
|
||||
<!-- <div class="h2 flex justify-between !mb-[10px]">-->
|
||||
<!-- <div>镜像源</div>-->
|
||||
@ -199,16 +207,16 @@ onUnmounted(() => {
|
||||
<!-- </el-col>-->
|
||||
<el-col
|
||||
v-for="version in versions"
|
||||
:key="version.id"
|
||||
:key="version.githubAssetId"
|
||||
:lg="6"
|
||||
:md="8"
|
||||
:sm="12"
|
||||
:xl="6"
|
||||
:xs="12"
|
||||
class="mb-[20px]"
|
||||
class="mb-[15px]"
|
||||
>
|
||||
<div
|
||||
class="w-full download-card bg-white rounded p-4 drop-shadow flex justify-between items-center"
|
||||
class="w-full download-card bg-white rounded p-4 drop-shadow flex justify-between items-center animate__animated"
|
||||
>
|
||||
<div class="left">
|
||||
<div class="mb-2 flex items-center justify-center">
|
||||
@ -223,19 +231,19 @@ onUnmounted(() => {
|
||||
<span class="text-primary font-bold"
|
||||
>{{
|
||||
// moment(version.published_at).format("YYYY-MM-DD HH:mm:ss")
|
||||
version.download_count
|
||||
version.versionDownloadCount
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-[12px]">
|
||||
发布时间:<span class="text-primary font-bold">{{
|
||||
// moment(version.published_at).format("YYYY-MM-DD HH:mm:ss")
|
||||
version.published_at
|
||||
version.githubCreatedAt
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div v-if="version.download_completed">
|
||||
<div v-if="version.downloaded">
|
||||
<!-- <span class="text-[12px] text-primary font-bold mr-2"-->
|
||||
<!-- >已下载</span-->
|
||||
<!-- >-->
|
||||
@ -259,14 +267,15 @@ onUnmounted(() => {
|
||||
删 除
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- <el-button type="text"></el-button>-->
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div class="w-32" v-if="downloading.has(version.id)">
|
||||
<div
|
||||
class="w-32"
|
||||
v-if="downloading.has(version.githubReleaseId)"
|
||||
>
|
||||
<el-progress
|
||||
:percentage="downloading.get(version.id)"
|
||||
:percentage="downloading.get(version.githubReleaseId)"
|
||||
:text-inside="false"
|
||||
/>
|
||||
</div>
|
||||
@ -300,4 +309,18 @@ onUnmounted(() => {
|
||||
.download-card {
|
||||
border-left: 5px solid #5a3daa;
|
||||
}
|
||||
|
||||
//@keyframes download-card-animation {
|
||||
// //0% {
|
||||
// // border-left-width: 5px;
|
||||
// //}
|
||||
// 100% {
|
||||
// border-left-width: 10px;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//.download-card:hover {
|
||||
// animation: download-card-animation 0.5s;
|
||||
//}
|
||||
</style>
|
||||
|
@ -1,27 +1,33 @@
|
||||
<script lang="ts" setup>
|
||||
import { defineComponent, onMounted, onUnmounted, ref } from "vue";
|
||||
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { useDebounceFn } from "@vueuse/core";
|
||||
import { on, removeRouterListeners, send } from "@/utils/ipcUtils";
|
||||
import { ipcRouters } from "../../../electron/core/IpcRouter";
|
||||
import { useFrpcDesktopStore } from "@/store/frpcDesktop";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import router from "@/router";
|
||||
import { useDebounceFn, useIntervalFn } from "@vueuse/core";
|
||||
|
||||
defineComponent({
|
||||
name: "Home"
|
||||
});
|
||||
|
||||
const running = ref(false);
|
||||
const frpcDesktopStore = useFrpcDesktopStore();
|
||||
|
||||
// const running = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
const handleStartFrpc = () => {
|
||||
ipcRenderer.send("frpc.start");
|
||||
send(ipcRouters.LAUNCH.launch);
|
||||
};
|
||||
|
||||
const handleStopFrpc = () => {
|
||||
ipcRenderer.send("frpc.stop");
|
||||
send(ipcRouters.LAUNCH.terminate);
|
||||
};
|
||||
|
||||
const handleButtonClick = useDebounceFn(() => {
|
||||
if (running.value) {
|
||||
loading.value = true;
|
||||
if (frpcDesktopStore.running) {
|
||||
handleStopFrpc();
|
||||
} else {
|
||||
handleStartFrpc();
|
||||
@ -29,30 +35,54 @@ const handleButtonClick = useDebounceFn(() => {
|
||||
}, 300);
|
||||
|
||||
onMounted(() => {
|
||||
useIntervalFn(() => {
|
||||
ipcRenderer.invoke("frpc.running").then(data => {
|
||||
running.value = data;
|
||||
console.log("进程状态", data);
|
||||
});
|
||||
}, 500);
|
||||
|
||||
ipcRenderer.on("Home.frpc.start.error.hook", (event, args) => {
|
||||
if (args) {
|
||||
ElMessageBox.alert(args, "提示", {
|
||||
showCancelButton: true,
|
||||
cancelButtonText: "取消",
|
||||
confirmButtonText: "去设置"
|
||||
}).then(() => {
|
||||
router.replace({
|
||||
name: "Config"
|
||||
on(
|
||||
ipcRouters.LAUNCH.launch,
|
||||
() => {
|
||||
// send(ipcRouters.LAUNCH.getStatus);
|
||||
frpcDesktopStore.refreshRunning();
|
||||
loading.value = false;
|
||||
},
|
||||
(bizCode: string, message: string) => {
|
||||
if (bizCode === "B1001") {
|
||||
ElMessageBox.alert("请先前往设置页面,修改配置后再启动", "提示", {
|
||||
// showCancelButton: true,
|
||||
// cancelButtonText: "取消",
|
||||
confirmButtonText: "去设置"
|
||||
}).then(() => {
|
||||
router.replace({
|
||||
name: "Config"
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
);
|
||||
|
||||
on(ipcRouters.LAUNCH.terminate, () => {
|
||||
// send(ipcRouters.LAUNCH.getStatus);
|
||||
frpcDesktopStore.refreshRunning();
|
||||
loading.value = false;
|
||||
});
|
||||
// ipcRenderer.on("Home.frpc.start.error.hook", (event, args) => {
|
||||
// if (args) {
|
||||
// ElMessageBox.alert(args, "提示", {
|
||||
// showCancelButton: true,
|
||||
// cancelButtonText: "取消",
|
||||
// confirmButtonText: "去设置"
|
||||
// }).then(() => {
|
||||
// router.replace({
|
||||
// name: "Config"
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
ipcRenderer.removeAllListeners("Home.frpc.start.error.hook");
|
||||
// ipcRenderer.removeAllListeners("Home.frpc.start.error.hook");
|
||||
// removeRouterListeners2(listeners.watchFrpcProcess);
|
||||
removeRouterListeners(ipcRouters.LAUNCH.launch);
|
||||
removeRouterListeners(ipcRouters.LAUNCH.terminate);
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -69,19 +99,19 @@ onUnmounted(() => {
|
||||
>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-show="running"
|
||||
v-show="frpcDesktopStore.running"
|
||||
class="z-0 rounded-full opacity-20 left-circle bg-[#5A3DAA] w-full h-full animation-rotate-1"
|
||||
/>
|
||||
</transition>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-show="running"
|
||||
v-show="frpcDesktopStore.running"
|
||||
class="z-0 rounded-full opacity-20 right-circle top-[10px] bg-[#5A3DAA] w-full h-full animation-rotate-2"
|
||||
/>
|
||||
</transition>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-show="running"
|
||||
v-show="frpcDesktopStore.running"
|
||||
class="z-0 rounded-full opacity-20 top-circle bg-[#5A3DAA] w-full h-full animation-rotate-3"
|
||||
/>
|
||||
</transition>
|
||||
@ -96,7 +126,7 @@ onUnmounted(() => {
|
||||
<transition name="fade">
|
||||
<div class="font-bold text-2xl text-center">
|
||||
<IconifyIconOffline
|
||||
v-if="running"
|
||||
v-if="frpcDesktopStore.running"
|
||||
class="text-[#7EC050] inline-block relative top-1"
|
||||
icon="check-circle-rounded"
|
||||
/>
|
||||
@ -105,7 +135,7 @@ onUnmounted(() => {
|
||||
class="text-[#E47470] inline-block relative top-1"
|
||||
icon="error"
|
||||
/>
|
||||
Frpc {{ running ? "已启动" : "已断开" }}
|
||||
Frpc {{ frpcDesktopStore.running ? "已启动" : "已断开" }}
|
||||
</div>
|
||||
</transition>
|
||||
<!-- <el-button-->
|
||||
@ -117,18 +147,25 @@ onUnmounted(() => {
|
||||
<!-- </el-button>-->
|
||||
<div class="w-full justify-center text-center">
|
||||
<el-link
|
||||
v-if="running"
|
||||
v-if="frpcDesktopStore.running"
|
||||
type="primary"
|
||||
@click="$router.replace({ name: 'Logger' })"
|
||||
>查看日志</el-link
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="w-full h-8 bg-[#563EA4] rounded flex justify-center items-center text-white font-bold cursor-pointer"
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleButtonClick"
|
||||
>
|
||||
{{ running ? "断 开" : "启 动" }}
|
||||
</div>
|
||||
size="large"
|
||||
:disabled="loading"
|
||||
>{{ frpcDesktopStore.running ? "断 开" : "启 动" }}
|
||||
</el-button>
|
||||
<!-- <div-->
|
||||
<!-- class="w-full h-8 bg-[#563EA4] rounded flex justify-center items-center text-white font-bold cursor-pointer"-->
|
||||
|
||||
<!-- >-->
|
||||
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,10 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, defineComponent, onMounted, onUnmounted, ref } from "vue";
|
||||
import { defineComponent, onMounted, onUnmounted, ref } from "vue";
|
||||
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
|
||||
import { ipcRenderer } from "electron";
|
||||
import IconifyIconOffline from "@/components/IconifyIcon/src/iconifyIconOffline";
|
||||
import { useDebounce, useDebounceFn } from "@vueuse/core";
|
||||
import { useDebounceFn } from "@vueuse/core";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { ipcRouters, listeners } from "../../../electron/core/IpcRouter";
|
||||
import {
|
||||
on,
|
||||
onListener,
|
||||
removeRouterListeners,
|
||||
removeRouterListeners2,
|
||||
send
|
||||
} from "@/utils/ipcUtils";
|
||||
|
||||
defineComponent({
|
||||
name: "Logger"
|
||||
@ -12,7 +19,7 @@ defineComponent({
|
||||
|
||||
const loggerContent = ref('<div class="text-white">暂无日志</div>');
|
||||
|
||||
const handleLog2Html = (logContent: string) => {
|
||||
const formatLogContent = (logContent: string) => {
|
||||
const logs = logContent
|
||||
.split("\n")
|
||||
.filter(f => f)
|
||||
@ -31,21 +38,17 @@ const handleLog2Html = (logContent: string) => {
|
||||
});
|
||||
return logs.reverse().join("");
|
||||
};
|
||||
|
||||
const refreshStatus = ref(false);
|
||||
|
||||
const logLoading = ref(true);
|
||||
// const isWatch = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
console.log('logger mounted')
|
||||
ipcRenderer.send("logger.getLog");
|
||||
ipcRenderer.on("Logger.getLog.hook", (event, args) => {
|
||||
// console.log("日志", args, args.indexOf("\n"));
|
||||
// const logs = args.split("\n");
|
||||
// console.log(logs, "2");
|
||||
if (args) {
|
||||
loggerContent.value = handleLog2Html(args);
|
||||
send(ipcRouters.LOG.getFrpLogContent);
|
||||
on(ipcRouters.LOG.getFrpLogContent, data => {
|
||||
if (data) {
|
||||
loggerContent.value = formatLogContent(data as string);
|
||||
}
|
||||
|
||||
logLoading.value = false;
|
||||
if (refreshStatus.value) {
|
||||
// 刷新逻辑
|
||||
@ -53,29 +56,23 @@ onMounted(() => {
|
||||
type: "success",
|
||||
message: "刷新成功"
|
||||
});
|
||||
} else {
|
||||
ipcRenderer.send("logger.update");
|
||||
refreshStatus.value = false;
|
||||
}
|
||||
});
|
||||
ipcRenderer.on("Logger.update.hook", (event, args) => {
|
||||
console.log("logger update hook", 1);
|
||||
if (args) {
|
||||
loggerContent.value = handleLog2Html(args);
|
||||
}
|
||||
on(ipcRouters.LOG.openFrpcLogFile, () => {
|
||||
ElMessage({
|
||||
type: "success",
|
||||
message: "打开日志成功"
|
||||
});
|
||||
});
|
||||
|
||||
ipcRenderer.on("Logger.openLog.hook", (event, args) => {
|
||||
if (args) {
|
||||
ElMessage({
|
||||
type: "success",
|
||||
message: "打开日志成功"
|
||||
});
|
||||
}
|
||||
onListener(listeners.watchFrpcLog, data => {
|
||||
console.log("onListener Data", data);
|
||||
send(ipcRouters.LOG.getFrpLogContent);
|
||||
});
|
||||
});
|
||||
|
||||
const openLocalLog = useDebounceFn(() => {
|
||||
ipcRenderer.send("logger.openLog");
|
||||
send(ipcRouters.LOG.openFrpcLogFile);
|
||||
}, 1000);
|
||||
|
||||
const refreshLog = useDebounceFn(() => {
|
||||
@ -86,13 +83,12 @@ const refreshLog = useDebounceFn(() => {
|
||||
// });
|
||||
refreshStatus.value = true;
|
||||
logLoading.value = true;
|
||||
ipcRenderer.send("logger.getLog");
|
||||
send(ipcRouters.LOG.getFrpLogContent);
|
||||
}, 300);
|
||||
|
||||
onUnmounted(() => {
|
||||
console.log('logger unmounted')
|
||||
ipcRenderer.removeAllListeners("Logger.getLog.hook");
|
||||
ipcRenderer.removeAllListeners("Logger.openLog.hook");
|
||||
removeRouterListeners(ipcRouters.LOG.getFrpLogContent);
|
||||
removeRouterListeners2(listeners.watchFrpcLog);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
|
@ -9,12 +9,13 @@ import {
|
||||
} from "vue";
|
||||
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
|
||||
import { ElMessage, FormInstance, FormRules } from "element-plus";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { clone } from "@/utils/clone";
|
||||
import { formatDate, useClipboard, useDebounceFn } from "@vueuse/core";
|
||||
import { useClipboard, useDebounceFn } from "@vueuse/core";
|
||||
import IconifyIconOffline from "@/components/IconifyIcon/src/iconifyIconOffline";
|
||||
import commonIps from "./commonIp.json";
|
||||
import router from "@/router";
|
||||
import path from "path";
|
||||
import { on, removeRouterListeners, send } from "@/utils/ipcUtils";
|
||||
import { ipcRouters } from "../../../electron/core/IpcRouter";
|
||||
import _ from "lodash";
|
||||
|
||||
defineComponent({
|
||||
name: "Proxy"
|
||||
@ -23,7 +24,7 @@ defineComponent({
|
||||
/**
|
||||
* 代理列表
|
||||
*/
|
||||
const proxys = ref<Array<Proxy>>([]);
|
||||
const proxys = ref<Array<FrpcProxy>>([]);
|
||||
/**
|
||||
* loading
|
||||
*/
|
||||
@ -44,20 +45,21 @@ const edit = ref({
|
||||
visible: false
|
||||
});
|
||||
|
||||
const defaultForm = ref<Proxy>({
|
||||
const defaultForm: FrpcProxy = {
|
||||
_id: "",
|
||||
hostHeaderRewrite: "",
|
||||
locations: [],
|
||||
name: "",
|
||||
type: "http",
|
||||
localIp: "",
|
||||
localIP: "",
|
||||
localPort: "8080",
|
||||
remotePort: "8080",
|
||||
customDomains: [""],
|
||||
stcpModel: "visitors",
|
||||
visitorsModel: "visitors",
|
||||
serverName: "",
|
||||
secretKey: "",
|
||||
bindAddr: "",
|
||||
bindPort: null,
|
||||
status: true,
|
||||
subdomain: "",
|
||||
basicAuth: false,
|
||||
httpUser: "",
|
||||
@ -67,27 +69,29 @@ const defaultForm = ref<Proxy>({
|
||||
https2http: false,
|
||||
https2httpCaFile: "",
|
||||
https2httpKeyFile: "",
|
||||
keepTunnelOpen: false
|
||||
});
|
||||
keepTunnelOpen: false,
|
||||
status: 1
|
||||
};
|
||||
|
||||
/**
|
||||
* 表单内容
|
||||
*/
|
||||
const editForm = ref<Proxy>(defaultForm.value);
|
||||
const editForm = ref<FrpcProxy>(_.cloneDeep(defaultForm));
|
||||
|
||||
/**
|
||||
* 代理类型
|
||||
*/
|
||||
const proxyTypes = ref(["http", "https", "tcp", "udp", "stcp", "xtcp", "sudp"]);
|
||||
const currSelectLocalFileType = ref();
|
||||
|
||||
const stcpModels = ref([
|
||||
const visitorsModels = ref([
|
||||
{
|
||||
label: "访问者",
|
||||
value: "visitors"
|
||||
},
|
||||
{
|
||||
label: "被访问者",
|
||||
value: "visited"
|
||||
value: "visitorsProvider"
|
||||
}
|
||||
]);
|
||||
|
||||
@ -104,7 +108,7 @@ const editFormRules = reactive<FormRules>({
|
||||
// }
|
||||
],
|
||||
type: [{ required: true, message: "请选择类型", trigger: "blur" }],
|
||||
localIp: [
|
||||
localIP: [
|
||||
{ required: true, message: "请输入内网地址", trigger: "blur" },
|
||||
{
|
||||
pattern: /^[\w-]+(\.[\w-]+)+$/,
|
||||
@ -128,7 +132,9 @@ const editFormRules = reactive<FormRules>({
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
stcpModel: [{ required: true, message: "请选择stcp模式", trigger: "blur" }],
|
||||
visitorsModel: [
|
||||
{ required: true, message: "请选择stcp模式", trigger: "blur" }
|
||||
],
|
||||
secretKey: [
|
||||
{ required: true, message: "请输入stcp共享密钥", trigger: "blur" }
|
||||
],
|
||||
@ -151,6 +157,7 @@ const editFormRules = reactive<FormRules>({
|
||||
httpPassword: [{ required: true, message: "请输入认证密码", trigger: "blur" }]
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* 表单dom
|
||||
*/
|
||||
@ -184,12 +191,12 @@ const isXtcp = computed(() => {
|
||||
return editForm.value.type === "xtcp";
|
||||
});
|
||||
|
||||
const isStcpVisited = computed(() => {
|
||||
const isStcpvisitorsProvider = computed(() => {
|
||||
return (
|
||||
(editForm.value.type === "stcp" ||
|
||||
editForm.value.type === "sudp" ||
|
||||
editForm.value.type === "xtcp") &&
|
||||
editForm.value.stcpModel === "visited"
|
||||
editForm.value.visitorsModel === "visitorsProvider"
|
||||
);
|
||||
});
|
||||
|
||||
@ -198,7 +205,7 @@ const isStcpVisitors = computed(() => {
|
||||
(editForm.value.type === "stcp" ||
|
||||
editForm.value.type === "sudp" ||
|
||||
editForm.value.type === "xtcp") &&
|
||||
editForm.value.stcpModel === "visitors"
|
||||
editForm.value.visitorsModel === "visitors"
|
||||
);
|
||||
});
|
||||
|
||||
@ -247,7 +254,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);
|
||||
@ -276,11 +283,12 @@ const handleSubmit = async () => {
|
||||
return;
|
||||
}
|
||||
loading.value.form = 1;
|
||||
const data = clone(editForm.value);
|
||||
const data = _.cloneDeep(editForm.value);
|
||||
console.log("submit", data);
|
||||
if (data._id) {
|
||||
ipcRenderer.send("proxy.updateProxy", data);
|
||||
send(ipcRouters.PROXY.modifyProxy, data);
|
||||
} else {
|
||||
ipcRenderer.send("proxy.insertProxy", data);
|
||||
send(ipcRouters.PROXY.createProxy, data);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -305,97 +313,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 = _.cloneDeep(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,32 +339,33 @@ const handleOpenInsert = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const handleOpenUpdate = (proxy: Proxy) => {
|
||||
editForm.value = clone(proxy);
|
||||
if (!editForm.value.fallbackTimeoutMs) {
|
||||
editForm.value.fallbackTimeoutMs = defaultForm.value.fallbackTimeoutMs;
|
||||
}
|
||||
const handleOpenUpdate = (proxy: FrpcProxy) => {
|
||||
editForm.value = _.cloneDeep(proxy);
|
||||
// if (!editForm.value.fallbackTimeoutMs) {
|
||||
// editForm.value.fallbackTimeoutMs = defaultForm.fallbackTimeoutMs;
|
||||
// }
|
||||
edit.value = {
|
||||
title: "修改代理",
|
||||
visible: true
|
||||
};
|
||||
};
|
||||
|
||||
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) => {
|
||||
editForm.value.localPort = port?.toString();
|
||||
editForm.value.localIP = "127.0.0.1";
|
||||
handleCloseLocalPortDialog();
|
||||
});
|
||||
|
||||
@ -441,36 +378,36 @@ 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])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (proxy.type === "stcp" && proxy.stcpModel === "visited") {
|
||||
if (proxy.type === "stcp" && proxy.visitorsModel === "visitorsProvider") {
|
||||
return false;
|
||||
}
|
||||
if (proxy.type === "xtcp" && proxy.stcpModel === "visited") {
|
||||
if (proxy.type === "xtcp" && proxy.visitorsModel === "visitorsProvider") {
|
||||
return false;
|
||||
}
|
||||
if (proxy.type === "sudp" && proxy.stcpModel === "visited") {
|
||||
if (proxy.type === "sudp" && proxy.visitorsModel === "visitorsProvider") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleCopyAccessAddress = (proxy: Proxy) => {
|
||||
const handleCopyAccessAddress = (proxy: FrpcProxy) => {
|
||||
if (
|
||||
(proxy.type === "http" || proxy.type === "https") &&
|
||||
(proxy.customDomains.length < 1 || !proxy.customDomains[0])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (proxy.type === "stcp" && proxy.stcpModel === "visited") {
|
||||
if (proxy.type === "stcp" && proxy.visitorsModel === "visitorsProvider") {
|
||||
return;
|
||||
}
|
||||
if (proxy.type === "xtcp" && proxy.stcpModel === "visited") {
|
||||
if (proxy.type === "xtcp" && proxy.visitorsModel === "visitorsProvider") {
|
||||
return;
|
||||
}
|
||||
let accessAddressStr = "";
|
||||
@ -565,59 +502,139 @@ const handleRandomProxyName = () => {
|
||||
`df_${editForm.value.type}_${result}`.toLocaleLowerCase();
|
||||
};
|
||||
|
||||
import path from "path";
|
||||
function normalizePath(filePath: string) {
|
||||
const normalizePath = (filePath: string) => {
|
||||
return path.normalize(filePath).replace(/\\/g, "/");
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectFile = (type: number, ext: string[]) => {
|
||||
ipcRenderer.invoke("file.selectFile", ext).then(r => {
|
||||
switch (type) {
|
||||
case 1:
|
||||
editForm.value.https2httpCaFile = normalizePath(r[0]);
|
||||
break;
|
||||
case 2:
|
||||
editForm.value.https2httpKeyFile = normalizePath(r[0]);
|
||||
break;
|
||||
}
|
||||
console.log(r);
|
||||
currSelectLocalFileType.value = type;
|
||||
send(ipcRouters.SYSTEM.selectLocalFile, {
|
||||
name: "",
|
||||
extensions: ext
|
||||
});
|
||||
// ipcRenderer.invoke("file.selectFile", ext).then(r => {
|
||||
// switch (type) {
|
||||
// case 1:
|
||||
// editForm.value.https2httpCaFile = normalizePath(r[0]);
|
||||
// break;
|
||||
// case 2:
|
||||
// editForm.value.https2httpKeyFile = normalizePath(r[0]);
|
||||
// break;
|
||||
// }
|
||||
// console.log(r);
|
||||
// });
|
||||
};
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
on(ipcRouters.SYSTEM.selectLocalFile, data => {
|
||||
console.log("data", data);
|
||||
if (!data.canceled) {
|
||||
switch (currSelectLocalFileType.value) {
|
||||
case 1:
|
||||
editForm.value.https2httpCaFile = data.path as string;
|
||||
break;
|
||||
case 2:
|
||||
editForm.value.https2httpKeyFile = data.path as string;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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 => {
|
||||
insertOrUpdateHook("新增成功");
|
||||
});
|
||||
|
||||
on(ipcRouters.PROXY.modifyProxy, 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;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
});
|
||||
|
||||
const handleProxyTypeChange = e => {
|
||||
if (e === "http" || e === "https" || e === "tcp" || e === "udp") {
|
||||
editForm.value.visitorsModel = "";
|
||||
} else {
|
||||
if (editForm.value.visitorsModel === "") {
|
||||
editForm.value.visitorsModel = "visitorsProvider";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
removeRouterListeners(ipcRouters.SYSTEM.selectLocalFile);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<!-- <coming-soon />-->
|
||||
<div class="main">
|
||||
<breadcrumb>
|
||||
<el-button class="mr-2" type="primary" @click="handleOpenInsert">
|
||||
<el-button type="primary" @click="handleOpenInsert">
|
||||
<IconifyIconOffline icon="add" />
|
||||
</el-button>
|
||||
</breadcrumb>
|
||||
<div class="app-container-breadcrumb pr-2" v-loading="loading.list > 0">
|
||||
<div class="app-container-breadcrumb" v-loading="loading.list > 0">
|
||||
<template v-if="proxys && proxys.length > 0">
|
||||
<el-row :gutter="20">
|
||||
<el-row :gutter="15">
|
||||
<el-col
|
||||
v-for="proxy in proxys"
|
||||
:key="proxy._id"
|
||||
@ -626,13 +643,13 @@ onUnmounted(() => {
|
||||
:sm="12"
|
||||
:xl="6"
|
||||
:xs="12"
|
||||
class="mb-[20px]"
|
||||
class="mb-[15px]"
|
||||
>
|
||||
<div class="bg-white w-full rounded drop-shadow-xl p-4">
|
||||
<div class="w-full flex justify-between">
|
||||
<div class="flex">
|
||||
<div
|
||||
class="w-12 h-12 rounded mr-4 flex justify-center items-center font-bold"
|
||||
class="w-12 h-12 rounded mr-3 flex justify-center items-center font-bold"
|
||||
:class="proxy.type"
|
||||
>
|
||||
<span class="text-white text-sm">{{ proxy.type }}</span>
|
||||
@ -642,7 +659,7 @@ onUnmounted(() => {
|
||||
<span>{{ proxy.name }}</span>
|
||||
</div>
|
||||
<el-tag
|
||||
v-if="!proxy.status"
|
||||
v-if="proxy.status === 0"
|
||||
class="mr-2"
|
||||
type="danger"
|
||||
size="small"
|
||||
@ -653,7 +670,7 @@ onUnmounted(() => {
|
||||
(proxy.type === 'stcp' ||
|
||||
proxy.type === 'xtcp' ||
|
||||
proxy.type === 'sudp') &&
|
||||
proxy.stcpModel === 'visitors'
|
||||
proxy.visitorsModel === 'visitors'
|
||||
"
|
||||
size="small"
|
||||
>
|
||||
@ -665,7 +682,7 @@ onUnmounted(() => {
|
||||
(proxy.type === 'stcp' ||
|
||||
proxy.type === 'xtcp' ||
|
||||
proxy.type === 'sudp') &&
|
||||
proxy.stcpModel === 'visited'
|
||||
proxy.visitorsModel === 'visitorsProvider'
|
||||
"
|
||||
>被访问者
|
||||
</el-tag>
|
||||
@ -738,11 +755,11 @@ onUnmounted(() => {
|
||||
(proxy.type !== 'stcp' &&
|
||||
proxy.type !== 'xtcp' &&
|
||||
proxy.type !== 'sudp') ||
|
||||
proxy.stcpModel !== 'visitors'
|
||||
proxy.visitorsModel !== 'visitors'
|
||||
"
|
||||
>
|
||||
<p class="text-[#ADADAD] font-bold">内网地址</p>
|
||||
<p>{{ proxy.localIp }}</p>
|
||||
<p>{{ proxy.localIP }}</p>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-center" v-if="proxy.type === 'tcp'">
|
||||
@ -755,7 +772,7 @@ onUnmounted(() => {
|
||||
(proxy.type !== 'stcp' &&
|
||||
proxy.type !== 'xtcp' &&
|
||||
proxy.type !== 'sudp') ||
|
||||
proxy.stcpModel !== 'visitors'
|
||||
proxy.visitorsModel !== 'visitors'
|
||||
"
|
||||
>
|
||||
<p class="text-[#ADADAD] font-bold">内网端口</p>
|
||||
@ -768,7 +785,7 @@ onUnmounted(() => {
|
||||
(proxy.type === 'stcp' ||
|
||||
proxy.type === 'xtcp' ||
|
||||
proxy.type === 'sudp') &&
|
||||
proxy.stcpModel === 'visitors'
|
||||
proxy.visitorsModel === 'visitors'
|
||||
"
|
||||
>
|
||||
<p class="text-[#ADADAD] font-bold">访问者名称</p>
|
||||
@ -781,7 +798,7 @@ onUnmounted(() => {
|
||||
(proxy.type === 'stcp' ||
|
||||
proxy.type === 'xtcp' ||
|
||||
proxy.type === 'sudp') &&
|
||||
proxy.stcpModel === 'visitors'
|
||||
proxy.visitorsModel === 'visitors'
|
||||
"
|
||||
>
|
||||
<p class="text-[#ADADAD] font-bold">绑定地址</p>
|
||||
@ -794,7 +811,7 @@ onUnmounted(() => {
|
||||
(proxy.type === 'stcp' ||
|
||||
proxy.type === 'xtcp' ||
|
||||
proxy.type === 'sudp') &&
|
||||
proxy.stcpModel === 'visitors'
|
||||
proxy.visitorsModel === 'visitors'
|
||||
"
|
||||
>
|
||||
<p class="text-[#ADADAD] font-bold">绑定端口</p>
|
||||
@ -819,7 +836,7 @@ onUnmounted(() => {
|
||||
v-model="edit.visible"
|
||||
direction="rtl"
|
||||
size="60%"
|
||||
@close="editForm = defaultForm"
|
||||
@close="editForm = _.cloneDeep(defaultForm)"
|
||||
>
|
||||
<!-- <el-dialog-->
|
||||
<!-- v-model="edit.visible"-->
|
||||
@ -838,7 +855,10 @@ onUnmounted(() => {
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="代理类型:" prop="type">
|
||||
<el-radio-group v-model="editForm.type">
|
||||
<el-radio-group
|
||||
v-model="editForm.type"
|
||||
@change="handleProxyTypeChange"
|
||||
>
|
||||
<el-radio-button
|
||||
v-for="p in proxyTypes"
|
||||
:key="p"
|
||||
@ -850,10 +870,13 @@ onUnmounted(() => {
|
||||
</el-col>
|
||||
<template v-if="isStcp || isSudp || isXtcp">
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="`${editForm.type}模式:`" prop="stcpModel">
|
||||
<el-radio-group v-model="editForm.stcpModel">
|
||||
<el-form-item
|
||||
:label="`${editForm.type}模式:`"
|
||||
prop="visitorsModel"
|
||||
>
|
||||
<el-radio-group v-model="editForm.visitorsModel">
|
||||
<el-radio
|
||||
v-for="p in stcpModels"
|
||||
v-for="p in visitorsModels"
|
||||
:key="p.value"
|
||||
:label="p.label"
|
||||
:value="p.value"
|
||||
@ -918,17 +941,19 @@ onUnmounted(() => {
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<template v-if="!(isStcp || isXtcp || isSudp) || isStcpVisited">
|
||||
<template
|
||||
v-if="!(isStcp || isXtcp || isSudp) || isStcpvisitorsProvider"
|
||||
>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="内网地址:" prop="localIp">
|
||||
<el-form-item label="内网地址:" prop="localIP">
|
||||
<el-autocomplete
|
||||
v-model="editForm.localIp"
|
||||
v-model="editForm.localIP"
|
||||
:fetch-suggestions="handleIpFetchSuggestions"
|
||||
clearable
|
||||
placeholder="127.0.0.1"
|
||||
/>
|
||||
<!-- <el-input-->
|
||||
<!-- v-model="editForm.localIp"-->
|
||||
<!-- v-model="editForm.localIP"-->
|
||||
<!-- placeholder="127.0.0.1"-->
|
||||
<!-- clearable-->
|
||||
<!-- />-->
|
||||
|
@ -22,7 +22,7 @@
|
||||
]
|
||||
},
|
||||
"types": [
|
||||
// "node",
|
||||
// "node",
|
||||
"vite/client",
|
||||
"element-plus/global"
|
||||
],
|
||||
|
46
types/core.d.ts
vendored
Normal file
46
types/core.d.ts
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
interface ApiResponse<T> {
|
||||
bizCode: string;
|
||||
data: T;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface ControllerParam {
|
||||
// win: BrowserWindow;
|
||||
channel: string;
|
||||
event: Electron.IpcMainEvent;
|
||||
args: any;
|
||||
}
|
||||
|
||||
interface ListenerParam {
|
||||
// win: BrowserWindow;
|
||||
channel: string;
|
||||
args: any[];
|
||||
}
|
||||
|
||||
type IpcRouter = {
|
||||
path: string;
|
||||
controller: string;
|
||||
}
|
||||
|
||||
type Listener = {
|
||||
channel: string;
|
||||
listenerMethod: any;
|
||||
};
|
||||
|
||||
enum IpcRouterKeys {
|
||||
SERVER = "SERVER",
|
||||
LOG = "LOG",
|
||||
VERSION = "VERSION",
|
||||
LAUNCH = "LAUNCH",
|
||||
PROXY = "PROXY",
|
||||
SYSTEM = "SYSTEM",
|
||||
}
|
||||
|
||||
type IpcRouters = Record<
|
||||
IpcRouterKeys,
|
||||
{
|
||||
[method: string]: IpcRouter;
|
||||
}
|
||||
>;
|
||||
|
||||
type Listeners = Record<string, Listener>;
|
81
types/frp.d.ts
vendored
Normal file
81
types/frp.d.ts
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
type LogConfig = {
|
||||
to: string;
|
||||
level: string;
|
||||
maxDays: number;
|
||||
disablePrintColor: boolean;
|
||||
};
|
||||
|
||||
type AuthConfig = {
|
||||
method: string;
|
||||
token: string;
|
||||
};
|
||||
|
||||
type WebServerConfig = {
|
||||
addr: string;
|
||||
port: number;
|
||||
user: string;
|
||||
password: string;
|
||||
pprofEnable: boolean;
|
||||
};
|
||||
|
||||
type TransportTlsConfig = {
|
||||
enable: boolean;
|
||||
certFile: string;
|
||||
keyFile: string;
|
||||
trustedCaFile: string;
|
||||
serverName: string;
|
||||
disableCustomTLSFirstByte: boolean;
|
||||
};
|
||||
|
||||
type TransportConfig = {
|
||||
dialServerTimeout: number;
|
||||
dialServerKeepalive: number;
|
||||
poolCount: number;
|
||||
tcpMux: boolean;
|
||||
tcpMuxKeepaliveInterval: number;
|
||||
protocol: string;
|
||||
connectServerLocalIP: string;
|
||||
proxyURL: string;
|
||||
tls: TransportTlsConfig;
|
||||
heartbeatInterval: number;
|
||||
heartbeatTimeout: number;
|
||||
};
|
||||
|
||||
interface FrpcCommonConfig {
|
||||
user: string;
|
||||
serverAddr: string;
|
||||
serverPort: number;
|
||||
loginFailExit: boolean;
|
||||
log: LogConfig;
|
||||
auth: AuthConfig;
|
||||
webServer: WebServerConfig;
|
||||
transport: TransportConfig;
|
||||
udpPacketSize: number;
|
||||
metadatas: Record<string, any>;
|
||||
}
|
||||
|
||||
interface FrpcProxyConfig {
|
||||
name: string;
|
||||
type: string;
|
||||
localIP: string;
|
||||
localPort: any;
|
||||
remotePort: any;
|
||||
customDomains: string[];
|
||||
locations: string[];
|
||||
hostHeaderRewrite: string;
|
||||
visitorsModel: string;
|
||||
serverName: string;
|
||||
secretKey: string;
|
||||
bindAddr: string;
|
||||
bindPort: number;
|
||||
subdomain: string;
|
||||
basicAuth: boolean;
|
||||
httpUser: string;
|
||||
httpPassword: string;
|
||||
fallbackTo: string;
|
||||
fallbackTimeoutMs: number;
|
||||
https2http: boolean;
|
||||
https2httpCaFile: string;
|
||||
https2httpKeyFile: string;
|
||||
keepTunnelOpen: boolean;
|
||||
}
|
39
types/frpc-desktop.d.ts
vendored
Normal file
39
types/frpc-desktop.d.ts
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
type FrpcDesktopProxy = FrpcProxyConfig & {};
|
||||
|
||||
interface BaseEntity {
|
||||
_id: string;
|
||||
}
|
||||
|
||||
interface FrpcSystemConfiguration {
|
||||
launchAtStartup: boolean;
|
||||
silentStartup: boolean;
|
||||
autoConnectOnStartup: boolean;
|
||||
}
|
||||
|
||||
type FrpcDesktopServer = BaseEntity &
|
||||
FrpcCommonConfig & {
|
||||
frpcVersion: number;
|
||||
// system: any;
|
||||
};
|
||||
|
||||
type FrpcVersion = BaseEntity & {
|
||||
githubReleaseId: number;
|
||||
githubAssetId: number;
|
||||
githubCreatedAt: string;
|
||||
name: string;
|
||||
assetName: string;
|
||||
versionDownloadCount: number;
|
||||
assetDownloadCount: number;
|
||||
browserDownloadUrl: string;
|
||||
downloaded: boolean;
|
||||
localPath: string;
|
||||
size: string;
|
||||
};
|
||||
|
||||
type OpenSourceFrpcDesktopServer = FrpcDesktopServer & {
|
||||
system: FrpcSystemConfiguration;
|
||||
};
|
||||
|
||||
type FrpcProxy = BaseEntity & FrpcProxyConfig & {
|
||||
status: number; // 0: disable 1: enable
|
||||
};
|
18
types/github.d.ts
vendored
Normal file
18
types/github.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
interface GithubAsset {
|
||||
id: number;
|
||||
name: string;
|
||||
content_type: string;
|
||||
download_count: number;
|
||||
size: number;
|
||||
browser_download_url: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
interface GithubRelease {
|
||||
id: number;
|
||||
name: string;
|
||||
created_at: string;
|
||||
published_at: string;
|
||||
assets: GithubAsset[]
|
||||
}
|
2
types/global.d.ts
vendored
2
types/global.d.ts
vendored
@ -21,7 +21,7 @@ declare global {
|
||||
localPort: any;
|
||||
remotePort: string;
|
||||
customDomains: string[];
|
||||
stcpModel: string;
|
||||
visitorsModel: string;
|
||||
serverName: string;
|
||||
secretKey: string;
|
||||
bindAddr: string;
|
||||
|
@ -23,6 +23,13 @@ export default defineConfig(({ command }) => {
|
||||
const sourcemap = isServe || !!process.env.VSCODE_DEBUG;
|
||||
|
||||
return {
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: "modern-compiler"
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
electron([
|
||||
|
Loading…
Reference in New Issue
Block a user