fix: use fixed HTTPS endpoints for native services
This commit is contained in:
@@ -142,9 +142,11 @@ function serviceEnv(service: ServiceName, env: Record<string, string | undefined
|
||||
}
|
||||
function serviceEndpoints(service: ServiceName, env: Record<string, string | undefined>) {
|
||||
const port = service === "api" ? requiredEnv(env, "CASERUN_API_PORT") : requiredEnv(env, "CASERUN_WEB_PORT");
|
||||
const host = requiredEnv(env, "CASERUN_PUBLIC_HOST");
|
||||
return { host, port: Number(port), url: `http://${host}:${Number(port)}${service === "api" ? "/health" : "/caserun"}` };
|
||||
const publicBaseUrl = fixedHttpsOrigin(requiredEnv(env, "CASERUN_PUBLIC_BASE_URL"), "CASERUN_PUBLIC_BASE_URL");
|
||||
validPort(port);
|
||||
return { url: service === "api" ? publicBaseUrl : `${publicBaseUrl}/caserun` };
|
||||
}
|
||||
function validPort(value: string) { const port = Number(value); if (!Number.isInteger(port) || port < 1 || port > 65535) throw codedError("invalid_service_port", `invalid CaseRun service port: ${value}`); return port; }
|
||||
function validService(value: string): ServiceName { if (value === "api" || value === "web") return value; throw codedError("invalid_service", "CaseRun service must be api or web"); }
|
||||
async function readState(file: string) { return JSON.parse(await readFile(file, "utf8").catch(() => "null")); }
|
||||
function alive(pid: number) { try { process.kill(pid, 0); return true; } catch { return false; } }
|
||||
@@ -154,4 +156,13 @@ async function waitForExit(pid: number, timeoutMs: number) {
|
||||
return !alive(pid);
|
||||
}
|
||||
function requiredEnv(env: Record<string, string | undefined>, key: string) { const value = String(env[key] ?? "").trim(); if (!value) throw codedError("native_exposure_config_required", `${key} is required from YAML-first native CaseRun config`); return value; }
|
||||
function fixedHttpsOrigin(value: string, key: string) {
|
||||
try {
|
||||
const parsed = new URL(value);
|
||||
if (parsed.protocol === "https:" && parsed.pathname === "/" && !parsed.search && !parsed.hash && !parsed.port) return parsed.origin;
|
||||
} catch {
|
||||
// Report one stable configuration error below.
|
||||
}
|
||||
throw codedError("native_exposure_config_invalid", `${key} must be a pathless HTTPS origin`);
|
||||
}
|
||||
function codedError(code: string, message: string) { return Object.assign(new Error(message), { code }); }
|
||||
|
||||
@@ -33,7 +33,7 @@ function help() {
|
||||
],
|
||||
transportContract: "--over-api only changes transport; --overapi is unsupported",
|
||||
localConfig: ["WORKBENCH_MODE", "WORKBENCH_NATIVE_STATE_FILE"],
|
||||
nativeServiceConfig: ["WORKBENCH_API_BIND_HOST", "WORKBENCH_API_PROBE_HOST", "WORKBENCH_API_PUBLIC_HOST", "WORKBENCH_API_PORT", "WORKBENCH_API_IDLE_TIMEOUT_SECONDS", "WORKBENCH_WORKER_BIND_HOST", "WORKBENCH_WORKER_PROBE_HOST", "WORKBENCH_WORKER_HEALTH_PORT", "WORKBENCH_WEB_BIND_HOST", "WORKBENCH_WEB_PROBE_HOST", "WORKBENCH_WEB_PUBLIC_HOST", "WORKBENCH_WEB_PORT", "WORKBENCH_NATIVE_API_URL", "WORKBENCH_WEB_RUNTIME_CONFIG"],
|
||||
nativeServiceConfig: ["WORKBENCH_PUBLIC_BASE_URL", "WORKBENCH_API_BIND_HOST", "WORKBENCH_API_PROBE_HOST", "WORKBENCH_API_PORT", "WORKBENCH_API_IDLE_TIMEOUT_SECONDS", "WORKBENCH_WORKER_BIND_HOST", "WORKBENCH_WORKER_PROBE_HOST", "WORKBENCH_WORKER_HEALTH_PORT", "WORKBENCH_WEB_BIND_HOST", "WORKBENCH_WEB_PROBE_HOST", "WORKBENCH_WEB_PORT", "WORKBENCH_NATIVE_API_URL", "WORKBENCH_WEB_RUNTIME_CONFIG"],
|
||||
apiConfig: ["WORKBENCH_API_URL", "HWLAB_API_KEY"]
|
||||
};
|
||||
}
|
||||
|
||||
@@ -87,18 +87,27 @@ function serviceEndpoints(service: ServiceName, env: Record<string, string | und
|
||||
const prefix = service === "api" ? "WORKBENCH_API" : "WORKBENCH_WEB";
|
||||
const bindHost = requiredEnv(env, `${prefix}_BIND_HOST`);
|
||||
const probeHost = requiredEnv(env, `${prefix}_PROBE_HOST`);
|
||||
const publicHost = requiredEnv(env, `${prefix}_PUBLIC_HOST`);
|
||||
const publicBaseUrl = fixedHttpsOrigin(requiredEnv(env, "WORKBENCH_PUBLIC_BASE_URL"), "WORKBENCH_PUBLIC_BASE_URL");
|
||||
const port = requiredEnv(env, `${prefix}_PORT`);
|
||||
const pathname = service === "api" ? "/health/ready" : "/workbench";
|
||||
return {
|
||||
bind: { host: bindHost, port: validPort(port) },
|
||||
probe: endpoint(probeHost, port, pathname),
|
||||
public: endpoint(publicHost, port, service === "api" ? "" : pathname)
|
||||
public: { url: service === "api" ? publicBaseUrl : `${publicBaseUrl}${pathname}` }
|
||||
};
|
||||
}
|
||||
function endpoint(host: string, port: string, pathname: string) { const numericPort = validPort(port); return { host, port: numericPort, url: `http://${host}:${numericPort}${pathname}` }; }
|
||||
function validPort(value: string) { const port = Number(value); if (!Number.isInteger(port) || port < 1 || port > 65535) throw codedError("invalid_service_port", `invalid Workbench service port: ${value}`); return port; }
|
||||
function requiredEnv(env: Record<string, string | undefined>, key: string) { const value = String(env[key] ?? "").trim(); if (!value) throw codedError("native_exposure_config_required", `${key} is required from the owning YAML native development exposure`); return value; }
|
||||
function fixedHttpsOrigin(value: string, key: string) {
|
||||
try {
|
||||
const parsed = new URL(value);
|
||||
if (parsed.protocol === "https:" && parsed.pathname === "/" && !parsed.search && !parsed.hash && !parsed.port) return parsed.origin;
|
||||
} catch {
|
||||
// Report one stable configuration error below.
|
||||
}
|
||||
throw codedError("native_exposure_config_invalid", `${key} must be a pathless HTTPS origin`);
|
||||
}
|
||||
async function probeHealth(url: string) {
|
||||
try { const response = await fetch(url, { signal: AbortSignal.timeout(1000) }); return { ok: response.ok, status: response.status, url }; }
|
||||
catch (error) { return { ok: false, status: null, url, error: error instanceof Error ? error.message : String(error) }; }
|
||||
|
||||
Reference in New Issue
Block a user