diff --git a/README.md b/README.md
index 72a6a4d..8e5646f 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@
Frpc-Desktop
- 🎉 一个 Frp 跨平台桌面客户端 支持多个frp版本
+ 🎉 Frp 跨平台桌面客户端 支持所有frp版本
@@ -24,12 +24,20 @@
## TODO
- [x] 开机自启动
- [x] 适配多用户 user & meta_token
-- [ ] 支持配置的导出导入
-- [ ] 优化配置
-- [ ] 便携版
+- [x] 便携版
- [ ] 在线更新
+- [ ] 支持stcp代理类型
+- [ ] 支持快速分享frps
+- [ ] 支持配的导出导入
+- [ ] 优化配置
+
+## 常见问题
+
+### Mac提示已损坏
+执行命令:`sudo xattr -cr Frpc-Desktop.app`
## 里程碑
+- 2024-08-09: 发布v1.0.6版本
- 2024-08-06: 发布v1.0.5版本
- 2024-08-06: 发布v1.0.4版本 适配支持多用户插件
- 2024-07-17: 发布v1.0.3版本 修复已知bug 增加开机自启 增加删除frp版本
@@ -40,18 +48,18 @@
## 社区
微信扫描加入开源项目交流群 广告勿进!!!
-
+
## 演示
-
+
-
+
-
+
-
+
## License
diff --git a/electron-builder.json5 b/electron-builder.json5
index 35078a3..a7bf322 100644
--- a/electron-builder.json5
+++ b/electron-builder.json5
@@ -14,33 +14,131 @@
"dist",
"dist-electron"
],
+ "dmg": {
+ "window": {
+ "width": 540,
+ "height": 380
+ },
+ "contents": [
+ {
+ "x": 410,
+ "y": 230,
+ "type": "link",
+ "path": "/Applications"
+ },
+ {
+ "x": 130,
+ "y": 230,
+ "type": "file"
+ }
+ ]
+ },
"mac": {
"target": [
- "dmg"
+ {
+ target: "dmg",
+ "arch": [
+ "x64",
+ "arm64",
+ "universal"
+ ]
+ },
+ {
+ "target": "zip",
+ "arch": [
+ "x64",
+ "arm64",
+ "universal"
+ ],
+ }
],
- "artifactName": "${productName}-Mac-${arch}-${version}-Installer.${ext}"
+// "artifactName": "${productName}-mac-${arch}-${version}.${ext}",
+ category: "public.app-category.utilities"
},
"win": {
"target": [
{
"target": "nsis",
"arch": [
- "x64"
+ "x64",
+ "ia32"
+ ]
+ },
+ // {
+ // "target": "appx",
+ // "arch": [
+ // "x64",
+ // "ia32"
+ // ]
+ // },
+ {
+ "target": "zip",
+ "arch": [
+ "x64",
+ "ia32"
+ ]
+ },
+ {
+ "target": "portable",
+ "arch": [
+ "x64",
+ "ia32"
]
}
],
- "artifactName": "${productName}-Windows-${arch}-${version}-Setup.${ext}"
+// "artifactName": "${productName}-win-${arch}-${version}.${ext}"
},
"nsis": {
+// "artifactName": "${productName}-win-${arch}-${version}-Setup.${ext}",
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": false
},
+ "portable": {
+// "artifactName": "${productName}-win-${arch}-${version}.${ext}"
+ },
"linux": {
"target": [
- "AppImage"
+ {
+ "target": "AppImage",
+ "arch": [
+ "x64",
+ "arm64",
+ // "armv7l"
+ ]
+ },
+ {
+ "target": "deb",
+ "arch": [
+ "x64",
+ "arm64",
+ // "armv7l"
+ ]
+ },
+ {
+ "target": "rpm",
+ "arch": [
+ "x64"
+ ]
+ },
+ // {
+ // "target": "snap",
+ // "arch": [
+ // "x64"
+ // ]
+ // }
+
],
- "artifactName": "${productName}-Linux-${arch}-${version}.${ext}"
+ "category": "Network",
+// "artifactName": "${productName}-Linux-${arch}-${version}.${ext}"
},
+ "publish": [
+ {
+ "provider": "github",
+ "owner": "luckjiawei",
+ "repo": "frpc-desktop",
+ "releaseType": "draft",
+ },
+ ]
}
diff --git a/electron/api/frpc.ts b/electron/api/frpc.ts
index e620f49..99eae16 100644
--- a/electron/api/frpc.ts
+++ b/electron/api/frpc.ts
@@ -49,6 +49,7 @@ localPort = ${m.localPort}
`;
switch (m.type) {
case "tcp":
+ case "udp":
toml += `remotePort = ${m.remotePort}`;
break;
case "http":
@@ -72,6 +73,12 @@ ${config.authMethod === 'multiuser' ? `
user = "${config.user}"
metadatas.token = "${config.metaToken}"
` : ""}
+${config.transportHeartbeatInterval ? `
+transport.heartbeatInterval = ${config.transportHeartbeatInterval}
+` : ""}
+${config.transportHeartbeatTimeout ? `
+transport.heartbeatTimeout = ${config.transportHeartbeatTimeout}
+` : ""}
log.to = "frpc.log"
@@ -111,6 +118,7 @@ local_port = ${m.localPort}
`;
switch (m.type) {
case "tcp":
+ case "udp":
ini += `remote_port = ${m.remotePort}`;
break;
case "http":
@@ -135,6 +143,14 @@ ${config.authMethod === 'multiuser' ? `
user = ${config.user}
meta_token = ${config.metaToken}
` : ""}
+
+${config.transportHeartbeatInterval ? `
+heartbeat_interval = ${config.transportHeartbeatInterval}
+` : ""}
+${config.transportHeartbeatTimeout ? `
+heartbeat_timeout = ${config.transportHeartbeatTimeout}
+` : ""}
+
log_file = "frpc.log"
log_level = ${config.logLevel}
log_max_days = ${config.logMaxDays}
diff --git a/electron/api/github.ts b/electron/api/github.ts
index 7954d75..3c01417 100644
--- a/electron/api/github.ts
+++ b/electron/api/github.ts
@@ -5,7 +5,6 @@ const fs = require("fs");
const path = require("path");
const zlib = require("zlib");
const {download} = require("electron-dl");
-const unzipper = require('unzipper');
const AdmZip = require('adm-zip');
const log = require('electron-log');
@@ -15,13 +14,13 @@ const versionRelation = {
"win32_ia32": ["window", "386"],
"darwin_arm64": ["darwin", "arm64"],
"darwin_x64": ["darwin", "amd64"],
- // "darwin_arm64": ["window", "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}`
-// currArch = `darwin_x64`
const frpArch = versionRelation[currArch]
const unTarGZ = (tarGzPath: string, targetPath: string) => {
diff --git a/electron/api/update.ts b/electron/api/update.ts
new file mode 100644
index 0000000..9d4554d
--- /dev/null
+++ b/electron/api/update.ts
@@ -0,0 +1,84 @@
+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', () => {
+ console.log('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("手动检查更新一次")
+
+
+}
diff --git a/electron/main/index.ts b/electron/main/index.ts
index 6aa998f..88a9c9f 100644
--- a/electron/main/index.ts
+++ b/electron/main/index.ts
@@ -7,6 +7,7 @@ import {initProxyApi} from "../api/proxy";
import {initFrpcApi, startFrpWorkerProcess, stopFrpcProcess} from "../api/frpc";
import {initLoggerApi} from "../api/logger";
import {initFileApi} from "../api/file";
+import {initUpdaterApi} from "../api/update";
import {getConfig} from "../storage/config";
import log from "electron-log";
// The built directory structure
@@ -117,7 +118,7 @@ async function createWindow() {
}
export const createTray = () => {
- log.info(`当前环境 platform:${process.platform} arch:${process.arch} appData:${app.getPath("userData")}`)
+ log.info(`当前环境 platform:${process.platform} arch:${process.arch} appData:${app.getPath("userData")} version:${app.getVersion()}`)
let menu: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
{
label: '显示主窗口', click: function () {
@@ -162,6 +163,14 @@ export const createTray = () => {
app.whenReady().then(() => {
createWindow().then(r => {
createTray()
+ // 初始化各个API
+ initGitHubApi();
+ initConfigApi();
+ initProxyApi();
+ initFrpcApi();
+ initLoggerApi();
+ initFileApi();
+ // initUpdaterApi(win);
})
});
@@ -212,15 +221,3 @@ ipcMain.handle("open-win", (_, arg) => {
childWindow.loadFile(indexHtml, {hash: arg});
}
});
-
-ipcMain.on('open-url', (event, url) => {
- shell.openExternal(url).then(r => {
- });
-});
-
-initGitHubApi();
-initConfigApi();
-initProxyApi();
-initFrpcApi();
-initLoggerApi();
-initFileApi();
diff --git a/electron/preload/index.ts b/electron/preload/index.ts
index ebf1276..8061fd0 100644
--- a/electron/preload/index.ts
+++ b/electron/preload/index.ts
@@ -44,7 +44,7 @@ function useLoading() {
animation-fill-mode: both;
width: 50px;
height: 50px;
- background: #fff;
+ background: #5F3BB0;
animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
}
.app-loading-wrap {
@@ -56,7 +56,7 @@ function useLoading() {
display: flex;
align-items: center;
justify-content: center;
- background: #282c34;
+ background: #ffffff;
z-index: 9;
}
`
diff --git a/electron/storage/config.ts b/electron/storage/config.ts
index ca2e7bf..5c41b6c 100644
--- a/electron/storage/config.ts
+++ b/electron/storage/config.ts
@@ -28,6 +28,8 @@ export type Config = {
systemStartupConnect: boolean;
user: string;
metaToken: string;
+ transportHeartbeatInterval: number;
+ transportHeartbeatTimeout: number;
};
/**
diff --git a/logo(new).pxd b/logo(new).pxd
deleted file mode 100644
index 125d9db..0000000
Binary files a/logo(new).pxd and /dev/null differ
diff --git a/logo(old).png b/logo(old).png
deleted file mode 100644
index f60b506..0000000
Binary files a/logo(old).png and /dev/null differ
diff --git a/package.json b/package.json
index 0193af5..4cf9376 100644
--- a/package.json
+++ b/package.json
@@ -1,19 +1,21 @@
{
"name": "Frpc-Desktop",
- "version": "1.0.5",
+ "version": "1.0.6",
"main": "dist-electron/main/index.js",
"description": "一个frpc桌面客户端",
- "repository": "https://github.com/luckjiawei/frpc-desktop",
+ "repository": "github:luckjiawei/frpc-desktop",
"author": "刘嘉伟 <8473136@qq.com>",
"license": "MIT",
"private": true,
"keywords": [
- "electron",
- "rollup",
- "vite",
- "vue3",
- "vue"
+ "frp",
+ "frpc",
+ "proxy",
+ "electron-app"
],
+ "engines": {
+ "node": ">= 18"
+ },
"debug": {
"env": {
"VITE_DEV_SERVER_URL": "http://127.0.0.1:3344/"
@@ -22,11 +24,14 @@
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
- "build:electron": "npm run build && electron-builder",
- "build:electron:mac": "npm run build && electron-builder --mac --x64 --arm64",
- "build:electron:win": "npm run build && electron-builder --win --arm64 --x64 --ia32",
- "build:electron:linux": "npm run build && electron-builder --linux --arm64 --x64",
- "build:electron:all": "npm run build:electron:win && npm run build:electron:mac && npm run build:electron:linux",
+ "build:electron": "npm run build && electron-builder --mac --win --linux",
+ "build:electron:mac": "npm run build && electron-builder --mac",
+ "build:electron:win": "npm run build && electron-builder --win",
+ "build:electron:linux": "npm run build && electron-builder --linux",
+ "release": "npm run build && electron-builder --mac --win --linux -p always",
+ "release:mac": "npm run build && electron-builder --mac -p always",
+ "release:win": "npm run build && electron-builder --win -p always",
+ "release:linux": "npm run build && electron-builder --linux -p always",
"preview": "vite preview",
"electron:generate-icons": "electron-icon-builder --input=./public/logo.png --output=build --flatten"
},
@@ -38,16 +43,18 @@
"@vueuse/core": "^9.13.0",
"autoprefixer": "^10.4.15",
"cssnano": "^6.0.1",
- "electron": "^26.0.0",
- "electron-builder": "^24.6.3",
+ "electron": "^26.6.10",
+ "electron-builder": "^24.13.3",
+ "electron-builder-squirrel-windows": "^24.13.3",
"element-plus": "^2.4.2",
+ "eslint": "^7.28.0",
"eslint-plugin-prettier": "^4.2.1",
"moment": "^2.29.4",
"nedb": "^1.8.0",
"node-cmd": "^5.0.0",
+ "postcss": "^8.4.31",
"prettier": "^2.8.8",
"sass": "^1.66.1",
- "sass-loader": "^13.3.2",
"tailwindcss": "^3.3.3",
"tree-kill": "^1.2.2",
"typescript": "^5.1.6",
@@ -64,7 +71,9 @@
"animate.css": "^4.1.1",
"electron-dl": "^3.5.1",
"electron-log": "^5.1.7",
+ "isbinaryfile": "4.0.10",
+ "js-base64": "^3.7.7",
"tar": "^6.2.0",
- "unzipper": "^0.10.14"
+ "unused-filename": "^4.0.1"
}
}
diff --git a/demo/conn.png b/screenshots/conn.png
similarity index 100%
rename from demo/conn.png
rename to screenshots/conn.png
diff --git a/demo/log.png b/screenshots/log.png
similarity index 100%
rename from demo/log.png
rename to screenshots/log.png
diff --git a/demo/proxys.png b/screenshots/proxys.png
similarity index 100%
rename from demo/proxys.png
rename to screenshots/proxys.png
diff --git a/demo/versions.png b/screenshots/versions.png
similarity index 100%
rename from demo/versions.png
rename to screenshots/versions.png
diff --git a/wechat-qr.png b/screenshots/wechat-qr.png
similarity index 100%
rename from wechat-qr.png
rename to screenshots/wechat-qr.png
diff --git a/src/layout/compoenets/LeftMenu.vue b/src/layout/compoenets/LeftMenu.vue
index ddbe491..f09c6d6 100644
--- a/src/layout/compoenets/LeftMenu.vue
+++ b/src/layout/compoenets/LeftMenu.vue
@@ -54,5 +54,9 @@ onMounted(() => {
+
+ v1.0.6
+
+
diff --git a/src/views/config/index.vue b/src/views/config/index.vue
index 5124624..1b2399b 100644
--- a/src/views/config/index.vue
+++ b/src/views/config/index.vue
@@ -6,6 +6,7 @@ import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
import {useDebounceFn} from "@vueuse/core";
import {clone} from "@/utils/clone";
import {Icon} from "@iconify/vue";
+import {Base64} from "js-base64";
defineComponent({
name: "Config"
@@ -30,6 +31,8 @@ type Config = {
systemStartupConnect: boolean;
user: string;
metaToken: string;
+ transportHeartbeatInterval: number;
+ transportHeartbeatTimeout: number;
};
type Version = {
@@ -37,7 +40,18 @@ type Version = {
name: string;
}
-const formData = ref({
+type ShareLinkConfig = {
+ serverAddr: string,
+ serverPort: number,
+ authMethod: string,
+ authToken: string,
+ transportHeartbeatInterval: number,
+ transportHeartbeatTimeout: number,
+ user: string,
+ metaToken: string,
+}
+
+const defaultFormData = ref({
currentVersion: "",
serverAddr: "",
serverPort: 7000,
@@ -55,9 +69,14 @@ const formData = ref({
systemSelfStart: false,
systemStartupConnect: false,
user: "",
- metaToken: ""
+ metaToken: "",
+ transportHeartbeatInterval: 30,
+ transportHeartbeatTimeout: 90,
});
+
+const formData = ref(defaultFormData.value);
+
const loading = ref(1);
const rules = reactive({
@@ -99,12 +118,27 @@ const rules = reactive({
],
systemSelfStart: [
{required: true, message: "请选择是否开机自启", trigger: "change"},
- ]
+ ],
+ transportHeartbeatInterval: [
+ {required: true, message: "心跳间隔时间不能为空", trigger: "change"},
+ ],
+ transportHeartbeatTimeout: [
+ {required: true, message: "心跳超时时间不能为空", trigger: "change"},
+ ],
+
});
const versions = ref>([]);
+const copyServerConfigBase64 = ref();
+const pasteServerConfigBase64 = ref();
const formRef = ref();
+
+const visibles = reactive({
+ copyServerConfig: false,
+ pasteServerConfig: false,
+});
+
const handleSubmit = useDebounceFn(() => {
if (!formRef.value) return;
formRef.value.validate(valid => {
@@ -138,6 +172,13 @@ onMounted(() => {
const {err, data} = args;
if (!err) {
if (data) {
+ console.log('data', data)
+ if (!data.transportHeartbeatInterval) {
+ data.transportHeartbeatInterval = defaultFormData.value.transportHeartbeatInterval
+ }
+ if (!data.transportHeartbeatTimeout) {
+ data.transportHeartbeatTimeout = defaultFormData.value.transportHeartbeatTimeout
+ }
formData.value = data;
}
}
@@ -177,6 +218,78 @@ const handleSelectFile = (type: number, ext: string[]) => {
})
}
+/**
+ * 分享配置
+ */
+const handleCopyServerConfig2Base64 = useDebounceFn(() => {
+ const shareConfig: ShareLinkConfig = {
+ serverAddr: formData.value.serverAddr,
+ serverPort: formData.value.serverPort,
+ authMethod: formData.value.authMethod,
+ authToken: formData.value.authToken,
+ transportHeartbeatInterval: formData.value.transportHeartbeatInterval,
+ transportHeartbeatTimeout: formData.value.transportHeartbeatTimeout,
+ user: formData.value.user,
+ metaToken: formData.value.metaToken,
+ };
+ const base64str = Base64.encode(
+ JSON.stringify(shareConfig)
+ )
+ copyServerConfigBase64.value = base64str;
+ visibles.copyServerConfig = true;
+
+}, 300);
+
+/**
+ * 导入配置
+ */
+const handlePasteServerConfig4Base64 = useDebounceFn(() => {
+ visibles.pasteServerConfig = true;
+}, 300);
+
+const handlePasteServerConfigBase64 = useDebounceFn(() => {
+ const plain = Base64.decode(pasteServerConfigBase64.value)
+ console.log('plain', plain)
+ let serverConfig: ShareLinkConfig = null;
+ try {
+ serverConfig = JSON.parse(plain)
+ } catch {
+ ElMessage({
+ type: "warning",
+ message: "链接不正确 请输入正确的链接"
+ });
+ return;
+ }
+
+ if (!serverConfig && !serverConfig.serverAddr) {
+ ElMessage({
+ type: "warning",
+ message: "链接不正确 请输入正确的链接"
+ });
+ return;
+ }
+ if (!serverConfig && !serverConfig.serverPort) {
+ ElMessage({
+ type: "warning",
+ message: "链接不正确 请输入正确的链接"
+ });
+ return;
+ }
+ formData.value.serverAddr = serverConfig.serverAddr
+ formData.value.serverPort = serverConfig.serverPort
+ formData.value.authMethod = serverConfig.authMethod
+ formData.value.authToken = serverConfig.authToken
+ formData.value.transportHeartbeatInterval = serverConfig.transportHeartbeatInterval
+ formData.value.transportHeartbeatTimeout = serverConfig.transportHeartbeatTimeout
+ formData.value.user = serverConfig.user
+ formData.value.metaToken = serverConfig.metaToken
+
+ handleSubmit();
+ visibles.pasteServerConfig = false;
+
+}, 300)
+
+
onUnmounted(() => {
ipcRenderer.removeAllListeners("Config.getConfig.hook");
ipcRenderer.removeAllListeners("Config.saveConfig.hook");
@@ -236,7 +349,16 @@ onUnmounted(() => {
- 服务器配置
+
+
@@ -300,6 +422,7 @@ onUnmounted(() => {
@change="handleAuthMethodChange"
clearable
>
+
@@ -365,7 +488,7 @@ onUnmounted(() => {
trigger="hover"
>
- 对应参数:meta_token
+ 对应参数:metadatas.token
@@ -381,6 +504,70 @@ onUnmounted(() => {
/>
+
+
+
+
+
+
+ 多长向服务端发发送一次心跳包 单位: 秒
+ 对应参数:transport.heartbeatInterval
+
+
+
+
+
+
+ 心跳间隔:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 心跳超时时间 单位: 秒
+ 对应参数:transport.heartbeatTimeout
+
+
+
+
+
+
+ 心跳超时:
+
+
+
+
+
+
+
+
+
+
+
+
TSL Config
@@ -539,6 +726,7 @@ onUnmounted(() => {
+
日志配置
@@ -621,6 +809,25 @@ onUnmounted(() => {
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -641,4 +848,5 @@ onUnmounted(() => {
.button-input {
width: calc(100% - 68px);
}
+
diff --git a/src/views/proxy/index.vue b/src/views/proxy/index.vue
index 6b6abe2..35d24cf 100644
--- a/src/views/proxy/index.vue
+++ b/src/views/proxy/index.vue
@@ -1,10 +1,10 @@