fix: show why a client tunnel failed to start (Plugin-Tunnels#2)

This commit is contained in:
LukeGus committed 2026-10-15 21:42:28 -05:00
1 parent 6084ed4d3c
commit cc5dce18e2
3 files changed
+40 -1

No files matched your search

+1
View File
@@ -32,6 +32,7 @@
- Indonesian uses the same technical terms everywhere - Indonesian uses the same technical terms everywhere
- When the server was down or still starting, the login page said it could not connect to the database and stopped trying. It now says the server is not responding and continues on its own once the server is back - When the server was down or still starting, the login page said it could not connect to the database and stopped trying. It now says the server is not responding and continues on its own once the server is back
- nginx answered 404 instead of 502 while the backend was down - nginx answered 404 instead of 502 while the backend was down
- A client tunnel that could not start always said "Endpoint SSH connection closed" on the desktop app instead of the real reason, like the target refusing the connection
- The Docker server crashed and restarted every few hours in ssh2's native crypto on Node 24 - The Docker server crashed and restarted every few hours in ssh2's native crypto on Node 24
## 26.10.1 ## 26.10.1
+10 -1
View File
@@ -2389,6 +2389,8 @@ async function testC2SRelay(
if (runtime) runtime.authSocket = ws; if (runtime) runtime.authSocket = ws;
return new Promise((resolve) => { return new Promise((resolve) => {
let settled = false; let settled = false;
// The relay sends its reason and then closes; keep the reason.
let relayError = null;
const settle = (result) => { const settle = (result) => {
if (settled) return; if (settled) return;
settled = true; settled = true;
@@ -2425,6 +2427,10 @@ async function testC2SRelay(
settle({ success: true }); settle({ success: true });
} else if (message.type === "error") { } else if (message.type === "error") {
clearTimeout(timer); clearTimeout(timer);
relayError = normalizeC2SErrorMessage(
message.error,
"Tunnel test failed",
);
settle(createC2SFailure(message.error, "Tunnel test failed")); settle(createC2SFailure(message.error, "Tunnel test failed"));
} }
} catch (error) { } catch (error) {
@@ -2438,7 +2444,10 @@ async function testC2SRelay(
}); });
ws.on("close", () => { ws.on("close", () => {
if (runtime && c2sTunnelRuntimes.get(tunnel.name) === runtime) { if (runtime && c2sTunnelRuntimes.get(tunnel.name) === runtime) {
setC2STunnelError(tunnel.name, "Endpoint SSH connection closed"); setC2STunnelError(
tunnel.name,
relayError || "Endpoint SSH connection closed",
);
} }
clearTimeout(timer); clearTimeout(timer);
settle({ success: false, error: "Tunnel test connection closed" }); settle({ success: false, error: "Tunnel test connection closed" });
@@ -145,6 +145,35 @@ describe("client tunnel runtime authentication", () => {
expect(sockets).toHaveLength(1); expect(sockets).toHaveLength(1);
}); });
it("keeps the relay's reason when it closes after an error", async () => {
const { context, runtimes, sockets } = setup();
const runtime = { status: { connected: true } };
runtimes.set("web", runtime);
const test = context.testC2SRelay(
{ name: "web" },
"127.0.0.1",
8443,
null,
runtime,
);
await vi.waitFor(() => expect(sockets).toHaveLength(1));
sockets[0].emit(
"message",
Buffer.from(
JSON.stringify({
type: "error",
error: "Target 127.0.0.1:8443 refused",
}),
),
false,
);
sockets[0].close();
expect(await test).toMatchObject({ success: false });
expect(runtimes.get("web").status.reason).toBe(
"Target 127.0.0.1:8443 refused",
);
});
it("keeps the tunnel connected when one of its connections fails", async () => { it("keeps the tunnel connected when one of its connections fails", async () => {
const { context, runtimes, sockets } = setup(); const { context, runtimes, sockets } = setup();
runtimes.set("web", { status: { connected: true, status: "CONNECTED" } }); runtimes.set("web", { status: { connected: true, status: "CONNECTED" } });