trying ngrok instead

This commit is contained in:
Michael Neale
2025-09-25 15:52:31 +10:00
parent 01c922f386
commit af273b17d3
6 changed files with 300 additions and 258 deletions
+243 -173
View File
@@ -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"
+2 -1
View File
@@ -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",
+5 -4
View File
@@ -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) {
+2 -2
View File
@@ -115,7 +115,7 @@ type ElectronAPI = {
recordRecipeHash: (recipeConfig: Recipe) => Promise<boolean>;
openDirectoryInExplorer: (directoryPath: string) => Promise<boolean>;
// Tunnel functions
tunnelStart: (port: number, secret?: string) => Promise<any>;
tunnelStart: (port: number) => Promise<any>;
tunnelStop: () => Promise<boolean>;
tunnelStatus: () => Promise<any>;
tunnelShowQr: () => Promise<boolean>;
@@ -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'),
+47 -77
View File
@@ -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<TunnelConnectionInfo> {
if (this.tunnelProcess) {
async start(port: number, secret: string): Promise<TunnelConnectionInfo> {
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<string> {
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<string> {
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<void> {
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<TunnelConnectionInfo> {
export async function startTunnel(port: number, secret: string): Promise<TunnelConnectionInfo> {
const manager = getTunnelManager();
return manager.start(port, secret);
}
@@ -296,9 +266,9 @@ export async function startTunnel(port: number, secret?: string): Promise<Tunnel
/**
* Stop the current tunnel
*/
export function stopTunnel(): void {
export async function stopTunnel(): Promise<void> {
const manager = getTunnelManager();
manager.stop();
await manager.stop();
}
/**
@@ -322,6 +292,6 @@ export function getTunnelConfig(): TunnelConfig | null {
*/
export async function cleanupTunnel(): Promise<void> {
const manager = getTunnelManager();
manager.stop();
await manager.stop();
await manager.cleanupQRCodes();
}
+1 -1
View File
@@ -4,7 +4,7 @@ import { defineConfig } from 'vite';
export default defineConfig({
build: {
rollupOptions: {
external: ['qrcode', 'localtunnel'],
external: ['qrcode', '@ngrok/ngrok'],
},
},
});