Support remote workspace service links

This commit is contained in:
Mohamed Boudra
2026-04-11 10:15:10 +07:00
parent 2bbdc7ed86
commit 3b8cfcbec6
15 changed files with 235 additions and 29 deletions
@@ -0,0 +1,64 @@
import { describe, expect, it } from "vitest";
import type { WorkspaceScriptPayload } from "@server/shared/messages";
import type { ActiveConnection } from "@/runtime/host-runtime";
import { resolveWorkspaceScriptLink } from "./workspace-script-links";
const runningService: WorkspaceScriptPayload = {
scriptName: "web",
type: "service",
hostname: "feature.web.localhost",
port: 3000,
proxyUrl: "http://feature.web.localhost:6767",
lifecycle: "running",
health: "healthy",
exitCode: null,
};
function resolveLink(activeConnection: ActiveConnection | null) {
return resolveWorkspaceScriptLink({
script: runningService,
activeConnection,
});
}
describe("resolveWorkspaceScriptLink", () => {
it("uses the local proxy URL for loopback TCP connections", () => {
expect(
resolveLink({ type: "directTcp", endpoint: "localhost:6767", display: "localhost:6767" }),
).toEqual({
openUrl: "http://feature.web.localhost:6767",
labelUrl: "http://feature.web.localhost:6767",
});
});
it("uses the local proxy URL for socket and pipe connections", () => {
expect(
resolveLink({ type: "directSocket", endpoint: "/tmp/paseo.sock", display: "socket" }),
).toEqual({
openUrl: "http://feature.web.localhost:6767",
labelUrl: "http://feature.web.localhost:6767",
});
});
it("degrades to daemon-host plus service port for direct network connections", () => {
expect(
resolveLink({
type: "directTcp",
endpoint: "mac-mini.tail123.ts.net:6767",
display: "mac-mini.tail123.ts.net:6767",
}),
).toEqual({
openUrl: "http://mac-mini.tail123.ts.net:3000",
labelUrl: "http://mac-mini.tail123.ts.net:3000",
});
});
it("shows the local proxy URL but disables opening over relay", () => {
expect(
resolveLink({ type: "relay", endpoint: "relay.paseo.sh:443", display: "relay" }),
).toEqual({
openUrl: null,
labelUrl: "http://feature.web.localhost:6767",
});
});
});
@@ -0,0 +1,64 @@
import { parseHostPort } from "@server/shared/daemon-endpoints";
import type { WorkspaceScriptPayload } from "@server/shared/messages";
import type { ActiveConnection } from "@/runtime/host-runtime";
export type ResolvedWorkspaceScriptLink = {
openUrl: string | null;
labelUrl: string | null;
};
function isLoopbackHost(host: string): boolean {
const normalizedHost = host.trim().toLowerCase();
return normalizedHost === "localhost" || normalizedHost === "127.0.0.1" || normalizedHost === "::1";
}
function buildDirectServiceUrl(endpoint: string, port: number): string | null {
try {
const { host, isIpv6 } = parseHostPort(endpoint);
const base = isIpv6 ? `[${host}]` : host;
return `http://${base}:${port}`;
} catch {
return null;
}
}
export function resolveWorkspaceScriptLink(input: {
script: WorkspaceScriptPayload;
activeConnection: ActiveConnection | null;
}): ResolvedWorkspaceScriptLink {
const { script, activeConnection } = input;
if (script.type !== "service" || script.lifecycle !== "running") {
return { openUrl: null, labelUrl: null };
}
if (!activeConnection) {
return { openUrl: null, labelUrl: script.proxyUrl };
}
if (activeConnection.type === "relay") {
return { openUrl: null, labelUrl: script.proxyUrl };
}
if (activeConnection.type === "directSocket" || activeConnection.type === "directPipe") {
return { openUrl: script.proxyUrl, labelUrl: script.proxyUrl };
}
try {
const { host } = parseHostPort(activeConnection.endpoint);
if (isLoopbackHost(host)) {
return { openUrl: script.proxyUrl, labelUrl: script.proxyUrl };
}
} catch {
return { openUrl: null, labelUrl: script.proxyUrl };
}
if (script.port === null) {
return { openUrl: null, labelUrl: script.proxyUrl };
}
const directUrl = buildDirectServiceUrl(activeConnection.endpoint, script.port);
return {
openUrl: directUrl,
labelUrl: directUrl ?? script.proxyUrl,
};
}