refactor: node tools and canvas host url
This commit is contained in:
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
||||
import net from "node:net";
|
||||
import os from "node:os";
|
||||
|
||||
import { resolveCanvasHostUrl } from "../canvas-host-url.js";
|
||||
import {
|
||||
getPairedNode,
|
||||
listNodePairing,
|
||||
@@ -188,23 +189,13 @@ export async function startNodeBridgeServer(
|
||||
? opts.serverName.trim()
|
||||
: os.hostname();
|
||||
|
||||
const isLoopbackHost = (host: string) => {
|
||||
const normalized = host.trim().toLowerCase();
|
||||
if (normalized === "localhost") return true;
|
||||
if (normalized === "::1") return true;
|
||||
if (normalized === "0.0.0.0" || normalized === "::") return true;
|
||||
return normalized.startsWith("127.");
|
||||
};
|
||||
|
||||
const buildCanvasHostUrl = (socket: net.Socket) => {
|
||||
const port = opts.canvasHostPort;
|
||||
if (!port) return undefined;
|
||||
const localHost = socket.localAddress?.trim() ?? "";
|
||||
const override = opts.canvasHostHost?.trim() ?? "";
|
||||
const host = !localHost || isLoopbackHost(localHost) ? override : localHost;
|
||||
if (!host) return undefined;
|
||||
const formatted = host.includes(":") ? `[${host}]` : host;
|
||||
return `http://${formatted}:${port}`;
|
||||
return resolveCanvasHostUrl({
|
||||
canvasPort: opts.canvasHostPort,
|
||||
hostOverride: opts.canvasHostHost,
|
||||
localAddress: socket.localAddress,
|
||||
scheme: "http",
|
||||
});
|
||||
};
|
||||
|
||||
type ConnectionState = {
|
||||
|
||||
64
src/infra/canvas-host-url.ts
Normal file
64
src/infra/canvas-host-url.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
type HostSource = string | null | undefined;
|
||||
|
||||
type CanvasHostUrlParams = {
|
||||
canvasPort?: number;
|
||||
hostOverride?: HostSource;
|
||||
requestHost?: HostSource;
|
||||
forwardedProto?: HostSource | HostSource[];
|
||||
localAddress?: HostSource;
|
||||
scheme?: "http" | "https";
|
||||
};
|
||||
|
||||
const isLoopbackHost = (value: string) => {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (!normalized) return false;
|
||||
if (normalized === "localhost") return true;
|
||||
if (normalized === "::1") return true;
|
||||
if (normalized === "0.0.0.0" || normalized === "::") return true;
|
||||
return normalized.startsWith("127.");
|
||||
};
|
||||
|
||||
const normalizeHost = (value: HostSource, rejectLoopback: boolean) => {
|
||||
if (!value) return "";
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return "";
|
||||
if (rejectLoopback && isLoopbackHost(trimmed)) return "";
|
||||
return trimmed;
|
||||
};
|
||||
|
||||
const parseHostHeader = (value: HostSource) => {
|
||||
if (!value) return "";
|
||||
try {
|
||||
return new URL(`http://${String(value).trim()}`).hostname;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const parseForwardedProto = (value: HostSource | HostSource[]) => {
|
||||
if (Array.isArray(value)) return value[0];
|
||||
return value;
|
||||
};
|
||||
|
||||
export function resolveCanvasHostUrl(params: CanvasHostUrlParams) {
|
||||
const port = params.canvasPort;
|
||||
if (!port) return undefined;
|
||||
|
||||
const scheme =
|
||||
params.scheme ??
|
||||
(parseForwardedProto(params.forwardedProto)?.trim() === "https"
|
||||
? "https"
|
||||
: "http");
|
||||
|
||||
const override = normalizeHost(params.hostOverride, true);
|
||||
const requestHost = normalizeHost(parseHostHeader(params.requestHost), !!override);
|
||||
const localAddress = normalizeHost(
|
||||
params.localAddress,
|
||||
Boolean(override || requestHost),
|
||||
);
|
||||
|
||||
const host = override || requestHost || localAddress;
|
||||
if (!host) return undefined;
|
||||
const formatted = host.includes(":") ? `[${host}]` : host;
|
||||
return `${scheme}://${formatted}:${port}`;
|
||||
}
|
||||
@@ -292,3 +292,22 @@ export async function updatePairedNodeMetadata(
|
||||
await persistState(state, baseDir);
|
||||
});
|
||||
}
|
||||
|
||||
export async function renamePairedNode(
|
||||
nodeId: string,
|
||||
displayName: string,
|
||||
baseDir?: string,
|
||||
): Promise<NodePairingPairedNode | null> {
|
||||
return await withLock(async () => {
|
||||
const state = await loadState(baseDir);
|
||||
const normalized = normalizeNodeId(nodeId);
|
||||
const existing = state.pairedByNodeId[normalized];
|
||||
if (!existing) return null;
|
||||
const trimmed = displayName.trim();
|
||||
if (!trimmed) throw new Error("displayName required");
|
||||
const next: NodePairingPairedNode = { ...existing, displayName: trimmed };
|
||||
state.pairedByNodeId[normalized] = next;
|
||||
await persistState(state, baseDir);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user