From 70ce32fbe08b35e259e45a2c6ac9283214bd27b6 Mon Sep 17 00:00:00 2001 From: Sami Date: Mon, 30 Mar 2026 03:21:13 -0700 Subject: [PATCH] fix: add Pylon relay fallback to /probe/:id endpoint The lightweight probe endpoint used by the dashboard for live status checks had no Pylon integration. When DNS2 (Singapore) tried to probe home network services directly, all probes timed out with 502. Now falls back to the configured Pylon relay before the domain fallback. Co-Authored-By: Claude Opus 4.6 --- dashcaddy-api/src/app.js | 61 +++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/dashcaddy-api/src/app.js b/dashcaddy-api/src/app.js index 97b760b..ada8844 100644 --- a/dashcaddy-api/src/app.js +++ b/dashcaddy-api/src/app.js @@ -487,25 +487,48 @@ async function createApp() { statusCode = await makeRequest('GET'); } } catch { - const fallbackUrl = `https://${config.buildDomain(id)}`; - const fp = new URL(fallbackUrl); - statusCode = await new Promise((resolve, reject) => { - const fReq = https.request({ - hostname: fp.hostname, - port: 443, - path: '/', - method: 'GET', - timeout: 5000, - agent: httpsAgent, - headers: { 'User-Agent': APP.USER_AGENTS.PROBE } - }, (fRes) => { - fRes.resume(); - resolve(fRes.statusCode); - }); - fReq.on('error', reject); - fReq.on('timeout', () => { fReq.destroy(); reject(new Error('Timeout')); }); - fReq.end(); - }); + // Direct probe failed — try Pylon relay if configured + const pylonConfig = config.siteConfig?.pylon; + if (pylonConfig?.url) { + try { + const pylonUrl = `${pylonConfig.url}/probe?url=${encodeURIComponent(url)}`; + const headers = { 'User-Agent': APP.USER_AGENTS.PROBE }; + if (pylonConfig.key) headers['x-pylon-key'] = pylonConfig.key; + const controller = new AbortController(); + const pylonTimeout = setTimeout(() => controller.abort(), 8000); + const pylonRes = await fetchT(pylonUrl, { method: 'GET', signal: controller.signal, headers }); + clearTimeout(pylonTimeout); + if (pylonRes.ok) { + const data = await pylonRes.json(); + statusCode = data.statusCode || 502; + } + } catch { + // Pylon also failed — fall through to domain fallback + } + } + + // Domain-based fallback (last resort) + if (!statusCode) { + const fallbackUrl = `https://${config.buildDomain(id)}`; + const fp = new URL(fallbackUrl); + statusCode = await new Promise((resolve, reject) => { + const fReq = https.request({ + hostname: fp.hostname, + port: 443, + path: '/', + method: 'GET', + timeout: 5000, + agent: httpsAgent, + headers: { 'User-Agent': APP.USER_AGENTS.PROBE } + }, (fRes) => { + fRes.resume(); + resolve(fRes.statusCode); + }); + fReq.on('error', reject); + fReq.on('timeout', () => { fReq.destroy(); reject(new Error('Timeout')); }); + fReq.end(); + }).catch(() => 502); + } } res.status(statusCode).send();