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 @@ ## 社区 微信扫描加入开源项目交流群 广告勿进!!! - Logo + Logo ## 演示 -![connect server](https://github.com/luckjiawei/frpc-desktop/blob/main/demo/conn.png?raw=true) +![connect server](https://github.com/luckjiawei/frpc-desktop/blob/main/screenshots/conn.png?raw=true) -![proxys manager](https://github.com/luckjiawei/frpc-desktop/blob/main/demo/proxys.png?raw=true) +![proxys manager](https://github.com/luckjiawei/frpc-desktop/blob/main/screenshots/proxys.png?raw=true) -![frp download](https://github.com/luckjiawei/frpc-desktop/blob/main/demo/versions.png?raw=true) +![frp download](https://github.com/luckjiawei/frpc-desktop/blob/main/screenshots/versions.png?raw=true) -![log](https://github.com/luckjiawei/frpc-desktop/blob/main/demo/log.png?raw=true) +![log](https://github.com/luckjiawei/frpc-desktop/blob/main/screenshots/log.png?raw=true) ## 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" > +
日志配置
@@ -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 @@