From af273b17d367a94a7132e95126f63cc9cfca366b Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Thu, 25 Sep 2025 15:52:31 +1000 Subject: [PATCH] trying ngrok instead --- ui/desktop/package-lock.json | 416 +++++++++++++++++++------------- ui/desktop/package.json | 3 +- ui/desktop/src/main.ts | 9 +- ui/desktop/src/preload.ts | 4 +- ui/desktop/src/utils/tunnel.ts | 124 ++++------ ui/desktop/vite.main.config.mts | 2 +- 6 files changed, 300 insertions(+), 258 deletions(-) diff --git a/ui/desktop/package-lock.json b/ui/desktop/package-lock.json index 9efcf5039a..8060cb6f11 100644 --- a/ui/desktop/package-lock.json +++ b/ui/desktop/package-lock.json @@ -12,6 +12,7 @@ "@ai-sdk/openai": "^2.0.14", "@ai-sdk/ui-utils": "^1.2.11", "@mcp-ui/client": "^5.9.0", + "@ngrok/ngrok": "^1.5.2", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-dialog": "^1.1.15", @@ -39,7 +40,6 @@ "electron-window-state": "^5.0.3", "express": "^5.1.0", "gsap": "^3.13.0", - "localtunnel": "^2.0.2", "lodash": "^4.17.21", "lucide-react": "^0.539.0", "qrcode": "^1.5.4", @@ -87,6 +87,7 @@ "@types/electron-squirrel-startup": "^1.0.2", "@types/express": "^5.0.3", "@types/lodash": "^4.17.20", + "@types/qrcode": "^1.5.5", "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", "@types/react-syntax-highlighter": "^15.5.13", @@ -2689,6 +2690,235 @@ "@tybys/wasm-util": "^0.10.0" } }, + "node_modules/@ngrok/ngrok": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok/-/ngrok-1.5.2.tgz", + "integrity": "sha512-gN7KKdLTKer+wBSk9s9eDx53MUFdcnXNHsXxiC5sJLLD5HY9JRMSn6UzcCqnk7IgeIgCgw5h1k6YDqhjx6lmtg==", + "license": "(MIT OR Apache-2.0)", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@ngrok/ngrok-android-arm64": "1.5.2", + "@ngrok/ngrok-darwin-arm64": "1.5.2", + "@ngrok/ngrok-darwin-universal": "1.5.2", + "@ngrok/ngrok-darwin-x64": "1.5.2", + "@ngrok/ngrok-freebsd-x64": "1.5.2", + "@ngrok/ngrok-linux-arm-gnueabihf": "1.5.2", + "@ngrok/ngrok-linux-arm64-gnu": "1.5.2", + "@ngrok/ngrok-linux-arm64-musl": "1.5.2", + "@ngrok/ngrok-linux-x64-gnu": "1.5.2", + "@ngrok/ngrok-linux-x64-musl": "1.5.2", + "@ngrok/ngrok-win32-arm64-msvc": "1.5.2", + "@ngrok/ngrok-win32-ia32-msvc": "1.5.2", + "@ngrok/ngrok-win32-x64-msvc": "1.5.2" + } + }, + "node_modules/@ngrok/ngrok-android-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-android-arm64/-/ngrok-android-arm64-1.5.2.tgz", + "integrity": "sha512-v81VbxxAgg2W7jbjhEcn8K9R2aUf0h1AuTx+8tDlw3L4H1YEmbmllIpBAGgMjHRBxLZKOo5GBi0k7oS+VRM5TA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-arm64/-/ngrok-darwin-arm64-1.5.2.tgz", + "integrity": "sha512-8CVzS9AveYpNhWbydm7cJ6XqmVg29/VRKF15l4kJ2djlNoJxuGSibgM9A627dWRdnJyj5uhmU3VzsgeU8t+/3g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-universal": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-universal/-/ngrok-darwin-universal-1.5.2.tgz", + "integrity": "sha512-mEMH1OxN6RxnqRSWb4xY9RqbtdlCpv+WlRKxq4lVy8JVsxEyFNnzVQ0jn+iuiy981jCXjokctzJeGMvECuSQBQ==", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-x64/-/ngrok-darwin-x64-1.5.2.tgz", + "integrity": "sha512-rGdcADw4NtMSU7SHUTly7uvMVYX6eMeMCppKyL5g3CSlEQntKf3AWs/89ah2TBWJA2WVl0UgGLkXp4xs1tg9eQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-freebsd-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-freebsd-x64/-/ngrok-freebsd-x64-1.5.2.tgz", + "integrity": "sha512-WgY54qUekaUGa5+lFvzYUMjlzf22IEXuZHhxnzJM2/gMqa7gjU8N5W4U8XNDjVW/oz6DekrzIjuoAEPO+2icDg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm-gnueabihf": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm-gnueabihf/-/ngrok-linux-arm-gnueabihf-1.5.2.tgz", + "integrity": "sha512-azMxr/TGEeFU4JAUbSu5MO2aZEvdq+TzcxiLw6d+yhdEtNAjDW9TOyCczTrIZPOG5fP8G3lcCd8TP7mVIWdOnw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm64-gnu": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-gnu/-/ngrok-linux-arm64-gnu-1.5.2.tgz", + "integrity": "sha512-79eFCxio4rM0ICRBXx/CVvbXDeWk1Jxr7szkezEYWtHaL+gXivrtS1QjtMnJpGY1GJlLTQL+49w2lGydqPOJQA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm64-musl": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-musl/-/ngrok-linux-arm64-musl-1.5.2.tgz", + "integrity": "sha512-ou9Z7iPQJIQ0RX5bdBhb3y7GwYRt+X0G9tenyRzKLXXvs0XfUUcg/23aBP61hmdRvBq7xpliV1PnvEVBgUIYMg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-x64-gnu": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-gnu/-/ngrok-linux-x64-gnu-1.5.2.tgz", + "integrity": "sha512-VI1mmtl3Ie5uXTVAR9thPiMNMsCWeqkjBUbHAyk2vZ2OXR4Vs2DGjOPXK+wTl/hjF29FXoxunjhMy6caF9ht0Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-x64-musl": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-musl/-/ngrok-linux-x64-musl-1.5.2.tgz", + "integrity": "sha512-F4j9EyC/0R3IgYSd+OER4bC8bxuBubvj33e24GvQnRF/IQaKhpybkvQbz54fnvsL7y0j2BB42NAIm2CFtk7tCw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-arm64-msvc": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-arm64-msvc/-/ngrok-win32-arm64-msvc-1.5.2.tgz", + "integrity": "sha512-0OMXNjWElM1MQX7lMBnpRtafS9+3ybauqGD4m2dZcIm6hFvexsJFwNgx0mCa5aKxe2mQ4zNarEUd+SqG2Aa4/g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-ia32-msvc": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-ia32-msvc/-/ngrok-win32-ia32-msvc-1.5.2.tgz", + "integrity": "sha512-hdvhnr7Br4XhUblpW67v5XP6FyoQwJ2xSbwas4KW4hZ3F4cw0m6sqXpssRfmqg3/5HJony1H5B2jLi0x4J7uOw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-x64-msvc": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-x64-msvc/-/ngrok-win32-x64-msvc-1.5.2.tgz", + "integrity": "sha512-aHuMiRti9Taow9DlYLGVmu9CXtXD/v4CBQWpZlmt7VGuK1KsTWWLaGIBFVp6UXnyW87b0A+KC69Kn/Xjylw+sg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -5928,6 +6158,16 @@ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", "license": "MIT" }, + "node_modules/@types/qrcode": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz", + "integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -6962,15 +7202,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -9539,6 +9770,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -10336,26 +10568,6 @@ "node": ">= 12" } }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -13004,143 +13216,6 @@ "node": ">=0.10.0" } }, - "node_modules/localtunnel": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/localtunnel/-/localtunnel-2.0.2.tgz", - "integrity": "sha512-n418Cn5ynvJd7m/N1d9WVJISLJF/ellZnfsLnx8WBWGzxv/ntNcFkJ1o6se5quUhCplfLGBNL5tYHiq5WF3Nug==", - "license": "MIT", - "dependencies": { - "axios": "0.21.4", - "debug": "4.3.2", - "openurl": "1.1.1", - "yargs": "17.1.1" - }, - "bin": { - "lt": "bin/lt.js" - }, - "engines": { - "node": ">=8.3.0" - } - }, - "node_modules/localtunnel/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/localtunnel/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/localtunnel/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/localtunnel/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/localtunnel/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT" - }, - "node_modules/localtunnel/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/localtunnel/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/localtunnel/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/localtunnel/node_modules/yargs": { - "version": "17.1.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.1.1.tgz", - "integrity": "sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==", - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/localtunnel/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -15117,12 +15192,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/openurl": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/openurl/-/openurl-1.1.1.tgz", - "integrity": "sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA==", - "license": "MIT" - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -19785,6 +19854,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" diff --git a/ui/desktop/package.json b/ui/desktop/package.json index 980629801e..74aa368ffc 100644 --- a/ui/desktop/package.json +++ b/ui/desktop/package.json @@ -42,6 +42,7 @@ "@ai-sdk/openai": "^2.0.14", "@ai-sdk/ui-utils": "^1.2.11", "@mcp-ui/client": "^5.9.0", + "@ngrok/ngrok": "^1.5.2", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-dialog": "^1.1.15", @@ -69,7 +70,6 @@ "electron-window-state": "^5.0.3", "express": "^5.1.0", "gsap": "^3.13.0", - "localtunnel": "^2.0.2", "lodash": "^4.17.21", "lucide-react": "^0.539.0", "qrcode": "^1.5.4", @@ -117,6 +117,7 @@ "@types/electron-squirrel-startup": "^1.0.2", "@types/express": "^5.0.3", "@types/lodash": "^4.17.20", + "@types/qrcode": "^1.5.5", "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", "@types/react-syntax-highlighter": "^15.5.13", diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 32a4468df7..27c546b523 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -2192,9 +2192,10 @@ async function appMain() { }); // Tunnel IPC handlers - ipcMain.handle('tunnel-start', async (_event, port: number, secret?: string) => { + ipcMain.handle('tunnel-start', async (_event, port: number) => { try { - const tunnelInfo = await startTunnel(port, secret); + // Use the same secret that goosed was launched with + const tunnelInfo = await startTunnel(port, SERVER_SECRET); log.info('Tunnel started:', tunnelInfo.url); // Open QR code image @@ -2209,9 +2210,9 @@ async function appMain() { } }); - ipcMain.handle('tunnel-stop', () => { + ipcMain.handle('tunnel-stop', async () => { try { - stopTunnel(); + await stopTunnel(); log.info('Tunnel stopped'); return true; } catch (error) { diff --git a/ui/desktop/src/preload.ts b/ui/desktop/src/preload.ts index 6e0a0731c4..4be8823d75 100644 --- a/ui/desktop/src/preload.ts +++ b/ui/desktop/src/preload.ts @@ -115,7 +115,7 @@ type ElectronAPI = { recordRecipeHash: (recipeConfig: Recipe) => Promise; openDirectoryInExplorer: (directoryPath: string) => Promise; // Tunnel functions - tunnelStart: (port: number, secret?: string) => Promise; + tunnelStart: (port: number) => Promise; tunnelStop: () => Promise; tunnelStatus: () => Promise; tunnelShowQr: () => Promise; @@ -243,7 +243,7 @@ const electronAPI: ElectronAPI = { ipcRenderer.invoke('record-recipe-hash', recipeConfig), openDirectoryInExplorer: (directoryPath: string) => ipcRenderer.invoke('open-directory-in-explorer', directoryPath), - tunnelStart: (port: number, secret?: string) => ipcRenderer.invoke('tunnel-start', port, secret), + tunnelStart: (port: number) => ipcRenderer.invoke('tunnel-start', port), tunnelStop: () => ipcRenderer.invoke('tunnel-stop'), tunnelStatus: () => ipcRenderer.invoke('tunnel-status'), tunnelShowQr: () => ipcRenderer.invoke('tunnel-show-qr'), diff --git a/ui/desktop/src/utils/tunnel.ts b/ui/desktop/src/utils/tunnel.ts index 0ed262fa19..a20e1014e4 100644 --- a/ui/desktop/src/utils/tunnel.ts +++ b/ui/desktop/src/utils/tunnel.ts @@ -1,4 +1,4 @@ -import { spawn, ChildProcess } from 'child_process'; +import * as ngrok from '@ngrok/ngrok'; import * as QRCode from 'qrcode'; import * as crypto from 'crypto'; import { Buffer } from 'node:buffer'; @@ -24,7 +24,7 @@ export interface TunnelConnectionInfo { } export class TunnelManager { - private tunnelProcess: ChildProcess | null = null; + private ngrokListener: any | null = null; private config: TunnelConfig | null = null; private isStarting = false; private tunnelUrl: string | null = null; @@ -32,10 +32,10 @@ export class TunnelManager { constructor() {} /** - * Start a localtunnel for the given port with a secret + * Start an ngrok tunnel for the given port with a secret */ - async start(port: number, secret?: string): Promise { - if (this.tunnelProcess) { + async start(port: number, secret: string): Promise { + if (this.ngrokListener) { throw new Error('Tunnel is already running'); } @@ -46,17 +46,17 @@ export class TunnelManager { this.isStarting = true; try { - // Generate secret if not provided - const tunnelSecret = secret || crypto.randomBytes(24).toString('base64'); + // Use the provided secret (from goosed) + const tunnelSecret = secret; this.config = { port, secret: tunnelSecret, }; - // Start localtunnel - log.info(`Starting localtunnel on port ${port}`); - this.tunnelUrl = await this.startLocaltunnel(port); + // Start ngrok tunnel + log.info(`Starting ngrok tunnel on port ${port}`); + this.tunnelUrl = await this.startNgrokTunnel(port); if (!this.tunnelUrl) { throw new Error('Failed to get tunnel URL'); @@ -106,68 +106,38 @@ export class TunnelManager { } /** - * Start localtunnel process and capture the URL + * Start ngrok tunnel and return the URL */ - private startLocaltunnel(port: number): Promise { - return new Promise((resolve, reject) => { - const lt = spawn('npx', ['localtunnel', '--port', port.toString()], { - shell: true, - stdio: ['ignore', 'pipe', 'pipe'], + private async startNgrokTunnel(port: number): Promise { + try { + // Get ngrok auth token from environment + const authToken = process.env.NGROK_AUTHTOKEN; + if (authToken) { + await ngrok.authtoken(authToken); + } + + // Connect to ngrok with the specified port + this.ngrokListener = await ngrok.forward({ + addr: port, + authtoken: authToken, }); - this.tunnelProcess = lt; + // Get the URL from the listener + const url = this.ngrokListener.url(); - let output = ''; - let errorOutput = ''; - let urlFound = false; - const timeout = setTimeout(() => { - if (!urlFound) { - this.stop(); - reject(new Error('Timeout waiting for tunnel URL')); - } - }, 30000); // 30 second timeout + if (!url) { + throw new Error('Failed to get ngrok URL'); + } - lt.stdout?.on('data', (data) => { - const text = data.toString(); - output += text; - log.info('localtunnel stdout:', text); + this.tunnelUrl = url; + log.info(`Ngrok tunnel established: ${url}`); - // Look for the URL in the output - const urlMatch = text.match(/your url is: (https:\/\/.*\.loca\.lt)/); - if (urlMatch && !urlFound) { - urlFound = true; - clearTimeout(timeout); - this.tunnelUrl = urlMatch[1]; - resolve(urlMatch[1]); - } - }); - - lt.stderr?.on('data', (data) => { - errorOutput += data.toString(); - log.error('localtunnel stderr:', data.toString()); - }); - - lt.on('error', (error) => { - clearTimeout(timeout); - log.error('Failed to start localtunnel:', error); - this.tunnelProcess = null; - reject(error); - }); - - lt.on('close', (code) => { - clearTimeout(timeout); - log.info(`localtunnel process exited with code ${code}`); - this.tunnelProcess = null; - - if (!urlFound) { - reject( - new Error( - `Localtunnel exited without providing URL. Output: ${output}, Error: ${errorOutput}` - ) - ); - } - }); - }); + return url; + } catch (error) { + log.error('Failed to start ngrok tunnel:', error); + this.ngrokListener = null; + throw new Error(`Failed to start ngrok tunnel: ${error}`); + } } /** @@ -221,15 +191,15 @@ export class TunnelManager { /** * Stop the tunnel */ - stop(): void { - if (this.tunnelProcess) { - log.info('Stopping localtunnel'); + async stop(): Promise { + if (this.ngrokListener) { + log.info('Stopping ngrok tunnel'); try { - this.tunnelProcess.kill(); + await this.ngrokListener.close(); } catch (error) { - log.error('Error stopping tunnel:', error); + log.error('Error stopping ngrok tunnel:', error); } - this.tunnelProcess = null; + this.ngrokListener = null; } this.config = null; @@ -240,7 +210,7 @@ export class TunnelManager { * Get current tunnel status */ isRunning(): boolean { - return this.tunnelProcess !== null; + return this.ngrokListener !== null; } /** @@ -288,7 +258,7 @@ export function getTunnelManager(): TunnelManager { /** * Start a tunnel for the given port */ -export async function startTunnel(port: number, secret?: string): Promise { +export async function startTunnel(port: number, secret: string): Promise { const manager = getTunnelManager(); return manager.start(port, secret); } @@ -296,9 +266,9 @@ export async function startTunnel(port: number, secret?: string): Promise { const manager = getTunnelManager(); - manager.stop(); + await manager.stop(); } /** @@ -322,6 +292,6 @@ export function getTunnelConfig(): TunnelConfig | null { */ export async function cleanupTunnel(): Promise { const manager = getTunnelManager(); - manager.stop(); + await manager.stop(); await manager.cleanupQRCodes(); } diff --git a/ui/desktop/vite.main.config.mts b/ui/desktop/vite.main.config.mts index c163aea0e2..85753f8644 100644 --- a/ui/desktop/vite.main.config.mts +++ b/ui/desktop/vite.main.config.mts @@ -4,7 +4,7 @@ import { defineConfig } from 'vite'; export default defineConfig({ build: { rollupOptions: { - external: ['qrcode', 'localtunnel'], + external: ['qrcode', '@ngrok/ngrok'], }, }, });