122 lines
3.7 KiB
JavaScript
122 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { createServer, request as httpRequest } from "node:http";
|
|
import path from "node:path";
|
|
import process from "node:process";
|
|
import { fileURLToPath } from "node:url";
|
|
import dotenv from "dotenv";
|
|
import WebSocket, { WebSocketServer } from "ws";
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
dotenv.config({ path: path.join(root, ".env.local") });
|
|
|
|
const host = "127.0.0.1";
|
|
const port = Number(process.env.HICAD_ZOO_WS_DEV_PROXY_PORT || 3010);
|
|
const appPort = Number(process.env.HICAD_APP_DEV_PORT || 3003);
|
|
const apiBaseUrl = String(
|
|
process.env.KITTYCAD_BASE_URL ||
|
|
process.env.ZOO_BASE_URL ||
|
|
"https://api.zoo.dev"
|
|
).replace(/\/+$/, "");
|
|
const apiKey = String(
|
|
process.env.KITTYCAD_API_TOKEN ||
|
|
process.env.KITTYCAD_API_KEY ||
|
|
process.env.ZOO_API_TOKEN ||
|
|
""
|
|
)
|
|
.replace(/\s+/g, "")
|
|
.trim();
|
|
|
|
if (!apiKey) {
|
|
throw new Error("Missing KITTYCAD_API_TOKEN in .env.local");
|
|
}
|
|
|
|
const allowedPaths = new Map([
|
|
["/api/ai-cad/zoo-ws/ml/copilot", "/ws/ml/copilot"],
|
|
["/api/ai-cad/zoo-ws/modeling/commands", "/ws/modeling/commands"],
|
|
]);
|
|
const browserSockets = new WebSocketServer({ noServer: true });
|
|
const server = createServer((request, response) => {
|
|
const upstreamRequest = httpRequest(
|
|
{
|
|
headers: request.headers,
|
|
host,
|
|
method: request.method,
|
|
path: request.url,
|
|
port: appPort,
|
|
},
|
|
(upstreamResponse) => {
|
|
response.writeHead(
|
|
upstreamResponse.statusCode || 502,
|
|
upstreamResponse.headers
|
|
);
|
|
upstreamResponse.pipe(response);
|
|
}
|
|
);
|
|
upstreamRequest.on("error", (error) => {
|
|
if (!response.headersSent) {
|
|
response.writeHead(502, {
|
|
"Content-Type": "text/plain; charset=utf-8",
|
|
});
|
|
}
|
|
response.end(`HiCAD development proxy failed: ${error.message}`);
|
|
});
|
|
request.pipe(upstreamRequest);
|
|
});
|
|
|
|
server.on("upgrade", (request, socket, head) => {
|
|
const incomingUrl = new URL(request.url || "/", `http://${host}:${port}`);
|
|
const upstreamPath = allowedPaths.get(incomingUrl.pathname);
|
|
if (!upstreamPath) {
|
|
socket.write("HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n");
|
|
socket.destroy();
|
|
return;
|
|
}
|
|
|
|
browserSockets.handleUpgrade(request, socket, head, (browserSocket) => {
|
|
const base = new URL(apiBaseUrl);
|
|
base.protocol = base.protocol === "http:" ? "ws:" : "wss:";
|
|
base.pathname = upstreamPath;
|
|
base.search = incomingUrl.search;
|
|
const upstreamSocket = new WebSocket(base, {
|
|
headers: {
|
|
Authorization: `Bearer ${apiKey}`,
|
|
Origin: "https://app.zoo.dev",
|
|
},
|
|
});
|
|
|
|
const closeBoth = (code = 1011, reason = "Zoo connection closed") => {
|
|
if (browserSocket.readyState < WebSocket.CLOSING) {
|
|
browserSocket.close(code, reason);
|
|
}
|
|
if (upstreamSocket.readyState < WebSocket.CLOSING) {
|
|
upstreamSocket.close(code, reason);
|
|
}
|
|
};
|
|
|
|
upstreamSocket.on("open", () => {
|
|
browserSocket.on("message", (data, isBinary) => {
|
|
if (upstreamSocket.readyState === WebSocket.OPEN) {
|
|
upstreamSocket.send(data, { binary: isBinary });
|
|
}
|
|
});
|
|
upstreamSocket.on("message", (data, isBinary) => {
|
|
if (browserSocket.readyState === WebSocket.OPEN) {
|
|
browserSocket.send(data, { binary: isBinary });
|
|
}
|
|
});
|
|
});
|
|
browserSocket.on("close", () => closeBoth(1000, "Browser disconnected"));
|
|
browserSocket.on("error", () => closeBoth());
|
|
upstreamSocket.on("close", () => closeBoth(1000, "Zoo disconnected"));
|
|
upstreamSocket.on("error", (error) => {
|
|
console.error("[HiCAD Zoo WS proxy]", error.message);
|
|
closeBoth();
|
|
});
|
|
});
|
|
});
|
|
|
|
server.listen(port, host, () => {
|
|
console.log(`HiCAD development proxy: http://${host}:${port}`);
|
|
});
|