feat: implement daemon connectivity updates and relay protocol versioning

This commit is contained in:
Mohamed Boudra
2026-02-20 19:27:56 +07:00
parent 6dad3212da
commit 0350f00840
49 changed files with 4146 additions and 1351 deletions
@@ -0,0 +1,47 @@
import { describe, expect, test } from "vitest";
import {
buildRelayWebSocketUrl,
CURRENT_RELAY_PROTOCOL_VERSION,
normalizeRelayProtocolVersion,
} from "./daemon-endpoints.js";
describe("relay websocket URL versioning", () => {
test("defaults relay URLs to v2", () => {
const url = new URL(
buildRelayWebSocketUrl({
endpoint: "relay.paseo.sh:443",
serverId: "srv_test",
role: "client",
clientSessionKey: "clsk_test",
})
);
expect(url.searchParams.get("v")).toBe(CURRENT_RELAY_PROTOCOL_VERSION);
expect(url.searchParams.get("clientId")).toBe("clsk_test");
});
test("allows explicitly requesting v1 relay URLs", () => {
const url = new URL(
buildRelayWebSocketUrl({
endpoint: "relay.paseo.sh:443",
serverId: "srv_test",
role: "server",
version: "1",
})
);
expect(url.searchParams.get("v")).toBe("1");
});
test("normalizes numeric relay versions", () => {
expect(normalizeRelayProtocolVersion(2)).toBe("2");
expect(normalizeRelayProtocolVersion(1)).toBe("1");
});
test("rejects unsupported relay versions", () => {
expect(() => normalizeRelayProtocolVersion("3")).toThrow(
'Relay version must be "1" or "2"'
);
});
});
+47 -4
View File
@@ -5,6 +5,28 @@ export type HostPortParts = {
};
export type RelayRole = "server" | "client";
export type RelayProtocolVersion = "1" | "2";
export const CURRENT_RELAY_PROTOCOL_VERSION: RelayProtocolVersion = "2";
export function normalizeRelayProtocolVersion(
value: unknown,
fallback: RelayProtocolVersion = CURRENT_RELAY_PROTOCOL_VERSION
): RelayProtocolVersion {
if (value == null) {
return fallback;
}
const normalized =
typeof value === "string" ? value.trim() : typeof value === "number" ? String(value) : "";
if (!normalized) {
return fallback;
}
if (normalized === "1" || normalized === "2") {
return normalized;
}
throw new Error('Relay version must be "1" or "2"');
}
function parsePort(portStr: string, context: string): number {
const port = Number(portStr);
@@ -60,11 +82,21 @@ function shouldUseSecureWebSocket(port: number): boolean {
return port === 443;
}
export function buildDaemonWebSocketUrl(endpoint: string): string {
export function buildDaemonWebSocketUrl(
endpoint: string,
params?: { clientSessionKey?: string }
): string {
const { host, port, isIpv6 } = parseHostPort(endpoint);
const protocol = shouldUseSecureWebSocket(port) ? "wss" : "ws";
const hostPart = isIpv6 ? `[${host}]` : host;
return `${protocol}://${hostPart}:${port}/ws`;
const url = new URL(`${protocol}://${hostPart}:${port}/ws`);
if (
typeof params?.clientSessionKey === "string" &&
params.clientSessionKey.trim().length > 0
) {
url.searchParams.set("clientSessionKey", params.clientSessionKey.trim());
}
return url.toString();
}
export function buildRelayWebSocketUrl(params: {
@@ -72,6 +104,8 @@ export function buildRelayWebSocketUrl(params: {
serverId: string;
role: RelayRole;
clientId?: string;
clientSessionKey?: string;
version?: RelayProtocolVersion | 1 | 2;
}): string {
const { host, port, isIpv6 } = parseHostPort(params.endpoint);
const protocol = shouldUseSecureWebSocket(port) ? "wss" : "ws";
@@ -79,8 +113,17 @@ export function buildRelayWebSocketUrl(params: {
const url = new URL(`${protocol}://${hostPart}:${port}/ws`);
url.searchParams.set("serverId", params.serverId);
url.searchParams.set("role", params.role);
if (params.clientId) {
url.searchParams.set("clientId", params.clientId);
url.searchParams.set("v", normalizeRelayProtocolVersion(params.version));
if (
params.clientId &&
params.clientSessionKey &&
params.clientId !== params.clientSessionKey
) {
throw new Error("clientId and clientSessionKey must match when both are provided");
}
const resolvedClientId = params.clientId ?? params.clientSessionKey;
if (resolvedClientId) {
url.searchParams.set("clientId", resolvedClientId);
}
return url.toString();
}