diff --git a/README.md b/README.md
index f559c77..d0c1ffa 100644
--- a/README.md
+++ b/README.md
@@ -16,15 +16,30 @@
Frpc-Desktop
- 🎉 一个Frp Client 跨平台桌面端
+ 🎉 一个 Frp 跨平台桌面客户端 支持多个frp版本
+## TODO
+- [x] 开机自启动
+- [ ] 支持配置的导出导入
+- [ ] 适配多用户 user & meta_token
+- [ ] 优化配置
+- [ ] 便携版
+
## 里程碑
+- 2024-07-17: 发布v1.0.3版本 修复已知bug 增加开机自启 增加删除frp版本
+- 2024-01-29: 发布v1.0.2版本 增加Linux客户端和代理模式
- 2023-12-01: 发布v1.0.1版本
- 2023-11-28: 发布v1.0版本
+## 社区
+微信扫描加入开源项目交流群 广告勿进!!!
+
+
+
+
## 演示

@@ -39,6 +54,8 @@
[MIT](LICENSE)
+## Stargazers over time
+[](https://starchart.cc/luckjiawei/frpc-desktop)
[forks-shield]: https://img.shields.io/github/forks/luckjiawei/frpc-desktop.svg?style=for-the-badge
[forks-url]: https://github.com/luckjiawei/frpc-desktop/network/members
diff --git a/electron/api/config.ts b/electron/api/config.ts
index 44cb054..a6ca050 100644
--- a/electron/api/config.ts
+++ b/electron/api/config.ts
@@ -1,42 +1,50 @@
-import { ipcMain } from "electron";
-import { getConfig, saveConfig } from "../storage/config";
-import { listVersion } from "../storage/version";
+import {app, ipcMain} from "electron";
+import {getConfig, saveConfig} from "../storage/config";
+import {listVersion} from "../storage/version";
export const initConfigApi = () => {
- ipcMain.on("config.saveConfig", async (event, args) => {
- saveConfig(args, (err, numberOfUpdated, upsert) => {
- event.reply("Config.saveConfig.hook", {
- err: err,
- numberOfUpdated: numberOfUpdated,
- upsert: upsert
- });
+ ipcMain.on("config.saveConfig", async (event, args) => {
+ saveConfig(args, (err, numberOfUpdated, upsert) => {
+ if (!err) {
+ const start = args.systemSelfStart || false;
+ console.log('开机自启', start)
+ app.setLoginItemSettings({
+ openAtLogin: start, //win
+ openAsHidden: start, //macOs
+ });
+ }
+ event.reply("Config.saveConfig.hook", {
+ err: err,
+ numberOfUpdated: numberOfUpdated,
+ upsert: upsert
+ });
+ });
});
- });
- ipcMain.on("config.getConfig", async (event, args) => {
- getConfig((err, doc) => {
- event.reply("Config.getConfig.hook", {
- err: err,
- data: doc
- });
+ ipcMain.on("config.getConfig", async (event, args) => {
+ getConfig((err, doc) => {
+ event.reply("Config.getConfig.hook", {
+ err: err,
+ data: doc
+ });
+ });
});
- });
- ipcMain.on("config.versions", event => {
- listVersion((err, doc) => {
- event.reply("Config.versions.hook", {
- err: err,
- data: doc
- });
+ ipcMain.on("config.versions", event => {
+ listVersion((err, doc) => {
+ event.reply("Config.versions.hook", {
+ err: err,
+ data: doc
+ });
+ });
});
- });
- ipcMain.on("config.hasConfig", event => {
- getConfig((err, doc) => {
- event.reply("Config.getConfig.hook", {
- err: err,
- data: doc
- });
+ ipcMain.on("config.hasConfig", event => {
+ getConfig((err, doc) => {
+ event.reply("Config.getConfig.hook", {
+ err: err,
+ data: doc
+ });
+ });
});
- });
};
diff --git a/electron/api/frpc.ts b/electron/api/frpc.ts
index a3fe7cb..bdbf95d 100644
--- a/electron/api/frpc.ts
+++ b/electron/api/frpc.ts
@@ -1,6 +1,6 @@
import {app, ipcMain, Notification} from "electron";
import {Config, getConfig} from "../storage/config";
-import {listProxy} from "../storage/proxy";
+import {listProxy, Proxy} from "../storage/proxy";
import {getVersionById} from "../storage/version";
import treeKill from "tree-kill";
@@ -25,43 +25,42 @@ const getFrpcVersionWorkerPath = (
) => {
getVersionById(versionId, (err2, version) => {
if (!err2) {
- callback(version["frpcVersionPath"]);
+ if (version) {
+ callback(version["frpcVersionPath"]);
+ }
}
});
};
/**
- * 生成配置文件
+ * 生成toml配置文件
+ * @param config
+ * @param proxys
*/
-export const generateConfig = (
- config: Config,
- callback: (configPath: string) => void
-) => {
- listProxy((err3, proxys) => {
- if (!err3) {
- const proxyToml = proxys.map(m => {
- let toml = `
+const genTomlConfig = (config: Config, proxys: Proxy[]) => {
+ const proxyToml = proxys.map(m => {
+ let toml = `
[[proxies]]
name = "${m.name}"
type = "${m.type}"
localIP = "${m.localIp}"
localPort = ${m.localPort}
`;
- switch (m.type) {
- case "tcp":
- toml += `remotePort = ${m.remotePort}`;
- break;
- case "http":
- case "https":
- toml += `customDomains=[${m.customDomains.map(m => `"${m}"`)}]`;
- break;
- default:
- break;
- }
+ switch (m.type) {
+ case "tcp":
+ toml += `remotePort = ${m.remotePort}`;
+ break;
+ case "http":
+ case "https":
+ toml += `customDomains=[${m.customDomains.map(m => `"${m}"`)}]`;
+ break;
+ default:
+ break;
+ }
- return toml;
- });
- let toml = `
+ return toml;
+ });
+ const toml = `
serverAddr = "${config.serverAddr}"
serverPort = ${config.serverPort}
auth.method = "${config.authMethod}"
@@ -82,17 +81,92 @@ ${config.proxyConfigEnable ? `
transport.proxyURL = "${config.proxyConfigProxyUrl}"
` : ""}
-
${proxyToml.join("")}
`;
+ return toml;
+}
- // const configPath = path.join("frp.toml");
- const filename = "frp.toml";
+
+/**
+ * 生成ini配置
+ * @param config
+ * @param proxys
+ */
+const genIniConfig = (config: Config, proxys: Proxy[]) => {
+ const proxyIni = proxys.map(m => {
+ let ini = `
+[${m.name}]
+type = "${m.type}"
+local_ip = "${m.localIp}"
+local_port = ${m.localPort}
+`;
+ switch (m.type) {
+ case "tcp":
+ ini += `remote_port = ${m.remotePort}`;
+ break;
+ case "http":
+ case "https":
+ ini += `custom_domains=[${m.customDomains.map(m => `"${m}"`)}]`;
+ break;
+ default:
+ break;
+ }
+
+ return ini;
+ });
+ const ini = `
+[common]
+server_addr = ${config.serverAddr}
+server_port = ${config.serverPort}
+authentication_method = "${config.authMethod}"
+auth_token = "${config.authToken}"
+log_file = "frpc.log"
+log_level = ${config.logLevel}
+log_max_days = ${config.logMaxDays}
+admin_addr = 127.0.0.1
+admin_port = 57400
+tls_enable = ${config.tlsConfigEnable}
+${config.tlsConfigEnable ? `
+tls_cert_file = ${config.tlsConfigCertFile}
+tls_key_file = ${config.tlsConfigKeyFile}
+tls_trusted_ca_file = ${config.tlsConfigTrustedCaFile}
+tls_server_name = ${config.tlsConfigServerName}
+` : ""}
+${config.proxyConfigEnable ? `
+http_proxy = "${config.proxyConfigProxyUrl}"
+` : ""}
+
+${proxyIni.join("")}
+ `
+ return ini;
+}
+
+/**
+ * 生成配置文件
+ */
+export const generateConfig = (
+ config: Config,
+ callback: (configPath: string) => void
+) => {
+ listProxy((err3, proxys) => {
+ if (!err3) {
+ console.log(config, 'config')
+ const {currentVersion} = config;
+ let filename = null;
+ let configContent = "";
+ if (currentVersion < 124395282) {
+ // 版本小于v0.52.0
+ filename = "frp.ini";
+ configContent = genIniConfig(config, proxys)
+ } else {
+ filename = "frp.toml";
+ configContent = genTomlConfig(config, proxys)
+ }
const configPath = path.join(app.getPath("userData"), filename)
console.debug("生成配置成功", configPath)
fs.writeFile(
configPath, // 配置文件目录
- toml, // 配置文件内容
+ configContent, // 配置文件内容
{flag: "w"},
err => {
if (!err) {
@@ -111,6 +185,7 @@ ${proxyToml.join("")}
* @param configPath
*/
const startFrpcProcess = (commandPath: string, configPath: string) => {
+ console.log(commandPath, 'commandP')
const command = `${commandPath} -c ${configPath}`;
console.info("启动", command)
frpcProcess = spawn(command, {
@@ -124,7 +199,8 @@ const startFrpcProcess = (commandPath: string, configPath: string) => {
});
frpcProcess.stdout.on("error", data => {
console.log("启动错误", data)
- stopFrpcProcess()
+ stopFrpcProcess(() => {
+ })
});
frpcStatusListener = setInterval(() => {
const status = frpcProcessStatus()
@@ -196,6 +272,35 @@ export const frpcProcessStatus = () => {
}
}
+/**
+ * 启动frpc流程
+ * @param config
+ */
+export const startFrpWorkerProcess = (config: Config) => {
+ getFrpcVersionWorkerPath(
+ config.currentVersion,
+ (frpcVersionPath: string) => {
+ console.log(1, '1')
+ if (frpcVersionPath) {
+ generateConfig(config, configPath => {
+ const platform = process.platform;
+ if (platform === 'win32') {
+ startFrpcProcess(
+ path.join(frpcVersionPath, "frpc.exe"),
+ configPath
+ );
+ } else {
+ startFrpcProcess(
+ path.join(frpcVersionPath, "frpc"),
+ configPath
+ );
+ }
+ });
+ }
+ }
+ );
+}
+
export const initFrpcApi = () => {
ipcMain.handle("frpc.running", async (event, args) => {
@@ -206,38 +311,21 @@ export const initFrpcApi = () => {
getConfig((err1, config) => {
if (!err1) {
if (config) {
- getFrpcVersionWorkerPath(
- config.currentVersion,
- (frpcVersionPath: string) => {
- generateConfig(config, configPath => {
- const platform = process.platform;
- if (platform === 'win32') {
- startFrpcProcess(
- path.join(frpcVersionPath, "frpc.exe"),
- configPath
- );
- } else {
- startFrpcProcess(
- path.join(frpcVersionPath, "frpc"),
- configPath
- );
- }
- });
- }
- );
+ startFrpWorkerProcess(config)
} else {
event.reply(
- "Home.frpc.start.error.hook",
- "请先前往设置页面,修改配置后再启动"
+ "Home.frpc.start.error.hook", "请先前往设置页面,修改配置后再启动"
);
}
}
});
+
});
ipcMain.on("frpc.stop", () => {
if (frpcProcess && !frpcProcess.killed) {
- stopFrpcProcess()
+ stopFrpcProcess(() => {
+ })
}
});
};
diff --git a/electron/api/github.ts b/electron/api/github.ts
index 46e2624..46fb6fa 100644
--- a/electron/api/github.ts
+++ b/electron/api/github.ts
@@ -1,5 +1,5 @@
import {app, BrowserWindow, ipcMain, net, shell} from "electron";
-import {insertVersion} from "../storage/version";
+import {deleteVersionById, insertVersion} from "../storage/version";
const fs = require("fs");
const path = require("path");
@@ -86,7 +86,7 @@ export const initGitHubApi = () => {
ipcMain.on("github.getFrpVersions", async event => {
const request = net.request({
method: "get",
- url: "https://api.github.com/repos/fatedier/frp/releases"
+ url: "https://api.github.com/repos/fatedier/frp/releases?page=1&per_page=1000"
});
request.on("response", response => {
let responseData: Buffer = Buffer.alloc(0);
@@ -104,6 +104,7 @@ export const initGitHubApi = () => {
const asset = getAdaptiveAsset(m.id);
if (asset) {
const absPath = `${downloadPath}/${asset.name}`;
+ m.absPath = absPath;
m.download_completed = fs.existsSync(absPath);
}
return m;
@@ -163,6 +164,23 @@ export const initGitHubApi = () => {
});
});
+ /**
+ * 删除下载
+ */
+ ipcMain.on("github.deleteVersion", async (event, args) => {
+ const {absPath, id} = args;
+ console.log('删除下载', args)
+ if (fs.existsSync(absPath)) {
+ deleteVersionById(id, () => {
+ fs.unlinkSync(absPath)
+ })
+ }
+ event.reply("Download.deleteVersion.hook", {
+ err: null,
+ data: "删除成功"
+ });
+ })
+
/**
* 打开GitHub
*/
diff --git a/electron/main/index.ts b/electron/main/index.ts
index 3a59204..0531980 100644
--- a/electron/main/index.ts
+++ b/electron/main/index.ts
@@ -4,9 +4,10 @@ import node_path, {join} from "node:path";
import {initGitHubApi} from "../api/github";
import {initConfigApi} from "../api/config";
import {initProxyApi} from "../api/proxy";
-import {initFrpcApi, stopFrpcProcess} from "../api/frpc";
+import {initFrpcApi, startFrpWorkerProcess, stopFrpcProcess} from "../api/frpc";
import {initLoggerApi} from "../api/logger";
import {initFileApi} from "../api/file";
+import {getConfig} from "../storage/config";
// The built directory structure
//
// ├─┬ dist-electron
@@ -135,6 +136,16 @@ export const createTray = () => {
tray.on('double-click', () => {
win.show();
})
+
+ getConfig((err, config) => {
+ if (!err) {
+ if (config) {
+ if (config.systemStartupConnect) {
+ startFrpWorkerProcess(config)
+ }
+ }
+ }
+ })
}
app.whenReady().then(() => {
diff --git a/electron/storage/version.ts b/electron/storage/version.ts
index ce9a80c..30f07b1 100644
--- a/electron/storage/version.ts
+++ b/electron/storage/version.ts
@@ -1,11 +1,11 @@
import Datastore from "nedb";
import path from "path";
-import { Proxy } from "./proxy";
-import { app } from "electron";
+import {Proxy} from "./proxy";
+import {app} from "electron";
const versionDB = new Datastore({
- autoload: true,
- filename: path.join(app.getPath("userData"), "version.db")
+ autoload: true,
+ filename: path.join(app.getPath("userData"), "version.db")
});
/**
@@ -14,10 +14,10 @@ const versionDB = new Datastore({
* @param cb
*/
export const insertVersion = (
- version: any,
- cb?: (err: Error | null, document: any) => void
+ version: any,
+ cb?: (err: Error | null, document: any) => void
) => {
- versionDB.insert(version, cb);
+ versionDB.insert(version, cb);
};
/**
@@ -25,14 +25,18 @@ export const insertVersion = (
* @param cb
*/
export const listVersion = (
- callback: (err: Error | null, documents: any[]) => void
+ callback: (err: Error | null, documents: any[]) => void
) => {
- versionDB.find({}, callback);
+ versionDB.find({}, callback);
};
export const getVersionById = (
- id: string,
- callback: (err: Error | null, document: any) => void
+ id: string,
+ callback: (err: Error | null, document: any) => void
) => {
- versionDB.findOne({ id: id }, callback);
+ versionDB.findOne({id: id}, callback);
};
+
+export const deleteVersionById = (id: string, callback: (err: Error | null, document: any) => void) => {
+ versionDB.remove({id: id}, callback);
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index fdad2f9..5986327 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "Frpc-Desktop",
- "version": "1.0.2",
+ "version": "1.0.3",
"main": "dist-electron/main/index.js",
"description": "一个frpc桌面客户端",
"author": "刘嘉伟 <8473136@qq.com>",
@@ -54,7 +54,7 @@
"vite-plugin-electron-renderer": "^0.14.5",
"vue": "^3.3.4",
"vue-router": "^4.2.4",
- "vue-tsc": "^1.8.8",
+ "vue-tsc": "^2.0.22",
"vue-types": "^5.1.1"
},
"dependencies": {
diff --git a/src/layout/compoenets/LeftMenu.vue b/src/layout/compoenets/LeftMenu.vue
index 3dd3ecc..1f3ea7d 100644
--- a/src/layout/compoenets/LeftMenu.vue
+++ b/src/layout/compoenets/LeftMenu.vue
@@ -19,7 +19,7 @@ const currentRoute = computed(() => {
* 菜单切换
* @param mi 菜单索引
*/
-const handleMenuChange = (route: RouteRecordRaw) => {
+const handleMenuChange = (route: any) => {
if (currentRoute.value.name === route.name) {
return;
}
diff --git a/src/styles/layout.scss b/src/styles/layout.scss
index 8970772..6504d8b 100644
--- a/src/styles/layout.scss
+++ b/src/styles/layout.scss
@@ -1,5 +1,6 @@
$main-bg: #F3F3F3;
$primary-color: #5F3BB0;
+$danger-color: #F56C6C;
.main-container {
background: $main-bg;
@@ -84,3 +85,7 @@ $primary-color: #5F3BB0;
.primary-text {
color: $primary-color;
}
+
+.danger-text {
+ color: $danger-color !important;
+}
\ No newline at end of file
diff --git a/src/views/config/index.vue b/src/views/config/index.vue
index c4806a7..e0ca4ab 100644
--- a/src/views/config/index.vue
+++ b/src/views/config/index.vue
@@ -229,23 +229,6 @@ onUnmounted(() => {
-
-
-
-
- Frps服务端地址
支持 域名、IP
-
-
-
-
-
-
- 服务器地址:
-
{
-
-
-
-
- 对应参数:auth.method
-
-
-
-
-
-
- 验证方式:
-
-
-
+
-
+
{
/>
-
-
-
-
-
-
- 对应参数:user
-
-
-
-
-
-
- 用户:
-
-
-
-
-
-
-
-
-
-
- 对应参数:meta_token
-
-
-
-
-
-
- 用户令牌:
-
-
-
-
TSL Config
diff --git a/src/views/download/index.vue b/src/views/download/index.vue
index d0a072d..f8630e0 100644
--- a/src/views/download/index.vue
+++ b/src/views/download/index.vue
@@ -4,6 +4,7 @@ import {ipcRenderer} from "electron";
import moment from "moment";
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
import {Icon} from "@iconify/vue";
+import {ElMessage} from "element-plus";
defineComponent({
name: "Download"
@@ -18,6 +19,7 @@ type Version = {
name: string;
published_at: string;
download_completed: boolean;
+ absPath: string;
assets: Asset[]
};
@@ -42,6 +44,17 @@ const handleDownload = (version: Version) => {
downloading.value.set(version.id, 0);
};
+/**
+ * 删除下载
+ * @param version
+ */
+const handleDeleteVersion = (version: Version) => {
+ ipcRenderer.send("github.deleteVersion", {
+ id: version.id,
+ absPath: version.absPath
+ });
+}
+
const handleInitDownloadHook = () => {
ipcRenderer.on("Download.frpVersionHook", (event, args) => {
loading.value--;
@@ -49,6 +62,7 @@ const handleInitDownloadHook = () => {
m.published_at = moment(m.published_at).format("YYYY-MM-DD HH:mm:ss")
return m as Version;
}) as Array;
+ console.log(versions, 'versions')
});
// 进度监听
ipcRenderer.on("Download.frpVersionDownloadOnProgress", (event, args) => {
@@ -67,6 +81,18 @@ const handleInitDownloadHook = () => {
version.download_completed = true;
}
});
+ ipcRenderer.on("Download.deleteVersion.hook", (event, args) => {
+ const {err, data} = args
+ if (!err) {
+ loading.value++;
+ ElMessage({
+ type: "success",
+ message: "删除成功"
+ });
+ handleLoadVersions();
+ }
+
+ })
};
onMounted(() => {
@@ -81,6 +107,7 @@ onUnmounted(() => {
ipcRenderer.removeAllListeners("Download.frpVersionDownloadOnProgress");
ipcRenderer.removeAllListeners("Download.frpVersionDownloadOnCompleted");
ipcRenderer.removeAllListeners("Download.frpVersionHook");
+ ipcRenderer.removeAllListeners("Download.deleteVersion.hook");
});
@@ -96,7 +123,7 @@ onUnmounted(() => {
{{ version.name }}
-
+
发布时间:{{
@@ -106,11 +133,25 @@ onUnmounted(() => {
-
已下载
+
+ 已下载
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+