mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 15:30:22 +00:00
diffs plugin
This commit is contained in:
120
extensions/diffs/src/url.ts
Normal file
120
extensions/diffs/src/url.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import os from "node:os";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
||||
|
||||
const DEFAULT_GATEWAY_PORT = 18789;
|
||||
|
||||
export function buildViewerUrl(params: {
|
||||
config: OpenClawConfig;
|
||||
viewerPath: string;
|
||||
baseUrl?: string;
|
||||
}): string {
|
||||
const baseUrl = params.baseUrl?.trim() || resolveGatewayBaseUrl(params.config);
|
||||
const normalizedBase = normalizeViewerBaseUrl(baseUrl);
|
||||
const normalizedPath = params.viewerPath.startsWith("/")
|
||||
? params.viewerPath
|
||||
: `/${params.viewerPath}`;
|
||||
return `${normalizedBase}${normalizedPath}`;
|
||||
}
|
||||
|
||||
export function normalizeViewerBaseUrl(raw: string): string {
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(raw);
|
||||
} catch {
|
||||
throw new Error(`Invalid baseUrl: ${raw}`);
|
||||
}
|
||||
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
||||
throw new Error(`baseUrl must use http or https: ${raw}`);
|
||||
}
|
||||
const withoutTrailingSlash = parsed.toString().replace(/\/+$/, "");
|
||||
return withoutTrailingSlash;
|
||||
}
|
||||
|
||||
function resolveGatewayBaseUrl(config: OpenClawConfig): string {
|
||||
const scheme = config.gateway?.tls?.enabled ? "https" : "http";
|
||||
const port =
|
||||
typeof config.gateway?.port === "number" ? config.gateway.port : DEFAULT_GATEWAY_PORT;
|
||||
const bind = config.gateway?.bind ?? "loopback";
|
||||
|
||||
if (bind === "custom" && config.gateway?.customBindHost?.trim()) {
|
||||
return `${scheme}://${config.gateway.customBindHost.trim()}:${port}`;
|
||||
}
|
||||
|
||||
if (bind === "lan") {
|
||||
return `${scheme}://${pickPrimaryLanIPv4() ?? "127.0.0.1"}:${port}`;
|
||||
}
|
||||
|
||||
if (bind === "tailnet") {
|
||||
return `${scheme}://${pickPrimaryTailnetIPv4() ?? "127.0.0.1"}:${port}`;
|
||||
}
|
||||
|
||||
return `${scheme}://127.0.0.1:${port}`;
|
||||
}
|
||||
|
||||
function pickPrimaryLanIPv4(): string | undefined {
|
||||
const nets = os.networkInterfaces();
|
||||
const preferredNames = ["en0", "eth0"];
|
||||
|
||||
for (const name of preferredNames) {
|
||||
const candidate = pickPrivateAddress(nets[name]);
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
for (const entries of Object.values(nets)) {
|
||||
const candidate = pickPrivateAddress(entries);
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function pickPrimaryTailnetIPv4(): string | undefined {
|
||||
const nets = os.networkInterfaces();
|
||||
for (const entries of Object.values(nets)) {
|
||||
const candidate = entries?.find((entry) => isTailnetIPv4(entry.address) && !entry.internal);
|
||||
if (candidate?.address) {
|
||||
return candidate.address;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function pickPrivateAddress(entries: os.NetworkInterfaceInfo[] | undefined): string | undefined {
|
||||
return entries?.find(
|
||||
(entry) => entry.family === "IPv4" && !entry.internal && isPrivateIPv4(entry.address),
|
||||
)?.address;
|
||||
}
|
||||
|
||||
function isPrivateIPv4(address: string): boolean {
|
||||
const octets = parseIpv4(address);
|
||||
if (!octets) {
|
||||
return false;
|
||||
}
|
||||
const [a, b] = octets;
|
||||
return a === 10 || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168);
|
||||
}
|
||||
|
||||
function isTailnetIPv4(address: string): boolean {
|
||||
const octets = parseIpv4(address);
|
||||
if (!octets) {
|
||||
return false;
|
||||
}
|
||||
const [a, b] = octets;
|
||||
return a === 100 && b >= 64 && b <= 127;
|
||||
}
|
||||
|
||||
function parseIpv4(address: string): number[] | null {
|
||||
const parts = address.split(".");
|
||||
if (parts.length !== 4) {
|
||||
return null;
|
||||
}
|
||||
const octets = parts.map((part) => Number.parseInt(part, 10));
|
||||
if (octets.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
||||
return null;
|
||||
}
|
||||
return octets;
|
||||
}
|
||||
Reference in New Issue
Block a user