feat: implement daemon connectivity updates and relay protocol versioning
This commit is contained in:
@@ -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"'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user