From 6af8043f2a59598fc1a45ddb3c38c67e042339e5 Mon Sep 17 00:00:00 2001 From: decolua Date: Fri, 20 Mar 2026 00:26:01 +0700 Subject: [PATCH] Fix Tunnel --- package.json | 2 +- src/lib/tunnel/cloudflared.js | 23 +++++++++++++++++++---- src/lib/tunnel/tunnelManager.js | 15 +++++++++++---- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index b470bb83..2e3238d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "9router-app", - "version": "0.3.58", + "version": "0.3.60", "description": "9Router web dashboard", "private": true, "scripts": { diff --git a/src/lib/tunnel/cloudflared.js b/src/lib/tunnel/cloudflared.js index a6fd445f..61ae3e51 100644 --- a/src/lib/tunnel/cloudflared.js +++ b/src/lib/tunnel/cloudflared.js @@ -217,6 +217,23 @@ export async function spawnQuickTunnel(localPort, onUrlUpdate) { return new Promise((resolve, reject) => { let resolved = false; + + function getQuickTunnelUrlFromLog(message) { + // cloudflared logs may contain "api.trycloudflare.com" as well, + // but that is NOT the quick-tunnel endpoint we need. + const regex = /https:\/\/([a-z0-9-]+)\.trycloudflare\.com/gi; + const candidates = []; + + for (const match of message.matchAll(regex)) { + const host = match[1]; + if (host === "api") continue; + candidates.push(`https://${host}.trycloudflare.com`); + } + + if (!candidates.length) return null; + return candidates[candidates.length - 1]; + } + const timeout = setTimeout(() => { if (resolved) return; resolved = true; @@ -226,10 +243,8 @@ export async function spawnQuickTunnel(localPort, onUrlUpdate) { const handleLog = (data) => { const msg = data.toString(); - // Parse trycloudflare.com URL from cloudflared output - const match = msg.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/); - if (match && !resolved) { - const tunnelUrl = match[0]; + const tunnelUrl = getQuickTunnelUrlFromLog(msg); + if (tunnelUrl && !resolved) { resolved = true; clearTimeout(timeout); cleanup(); diff --git a/src/lib/tunnel/tunnelManager.js b/src/lib/tunnel/tunnelManager.js index 1f346e8e..0f9c25b6 100644 --- a/src/lib/tunnel/tunnelManager.js +++ b/src/lib/tunnel/tunnelManager.js @@ -11,6 +11,7 @@ const RECONNECT_DELAYS_MS = [5000, 10000, 20000, 30000, 60000]; const MAX_RECONNECT_ATTEMPTS = RECONNECT_DELAYS_MS.length; let isReconnecting = false; +let exitHandlerRegistered = false; function generateShortId() { let result = ""; @@ -68,11 +69,16 @@ export async function enableTunnel(localPort = 20128) { saveState({ shortId, machineId, tunnelUrl }); await updateSettings({ tunnelEnabled: true, tunnelUrl }); - setUnexpectedExitHandler(() => { - if (!isReconnecting) scheduleReconnect(0); - }); + // Set exit handler only once (not on every reconnect) + if (!exitHandlerRegistered) { + setUnexpectedExitHandler(() => { + if (!isReconnecting) scheduleReconnect(0); + }); + exitHandlerRegistered = true; + } - return { success: true, tunnelUrl, shortId }; + const publicUrl = `https://r${shortId}.9router.com`; + return { success: true, tunnelUrl, shortId, publicUrl }; } async function scheduleReconnect(attempt) { @@ -104,6 +110,7 @@ async function scheduleReconnect(attempt) { export async function disableTunnel() { killCloudflared(); + exitHandlerRegistered = false; // Reset handler flag when tunnel disabled const state = loadState(); if (state) {