Files
Tony Simons a5b0895dd8 Fix WS auth + gym release directive TypeScript error (#16)
* Fix WS auth: wire accessGate.allowUpgrade via verifyClient

The allowWs callback was never actually calling accessGate.allowUpgrade
during the WS handshake - the ws library passes (info) not (req), and
verifyClient must be set on the WebSocketServer constructor options.

Fix: pass verifyClient to WebSocketServer constructor and wrap
allowUpgrade to extract info.req.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix TypeScript error and add gym release directive support

Add "release" value to OfficeGymDirective type for symmetry with
OfficeQaDirective ("qa_lab" | "release"). Previously OfficeGymDirective
was only "gym" with no release state, making the "!== 'release'"
check in eventTriggers.ts dead code that TypeScript flagged as an
unintentional comparison.

Changes:
- deskDirectives.ts: add "release" to OfficeGymDirective type
- deskDirectives.ts: add gym release patterns to skill and command
  directive resolvers (e.g. "leave the gym", "done with skills")
- eventTriggers.ts: change !== "release" to === "gym" for clarity
  and consistency with reduceOfficeGymHoldState pattern

This fixes: https://github.com/iamlukethedev/Claw3D/issues/15

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 23:00:58 -05:00

131 lines
3.6 KiB
JavaScript

const http = require("node:http");
const next = require("next");
const { createAccessGate } = require("./access-gate");
const { createGatewayProxy } = require("./gateway-proxy");
const { assertPublicHostAllowed, resolveHosts } = require("./network-policy");
const { loadUpstreamGatewaySettings } = require("./studio-settings");
const resolvePort = () => {
const raw = process.env.PORT?.trim() || "3000";
const port = Number(raw);
if (!Number.isFinite(port) || port <= 0) return 3000;
return port;
};
const resolvePathname = (url) => {
const raw = typeof url === "string" ? url : "";
const idx = raw.indexOf("?");
return (idx === -1 ? raw : raw.slice(0, idx)) || "/";
};
async function main() {
const dev = process.argv.includes("--dev");
const hostnames = Array.from(new Set(resolveHosts(process.env)));
const hostname = hostnames[0] ?? "127.0.0.1";
const port = resolvePort();
for (const host of hostnames) {
assertPublicHostAllowed({
host,
studioAccessToken: process.env.STUDIO_ACCESS_TOKEN,
});
}
const app = next({
dev,
hostname,
port,
...(dev ? { webpack: true } : null),
});
const handle = app.getRequestHandler();
const accessGate = createAccessGate({
token: process.env.STUDIO_ACCESS_TOKEN,
});
const proxy = createGatewayProxy({
loadUpstreamSettings: async () => {
const settings = loadUpstreamGatewaySettings(process.env);
return { url: settings.url, token: settings.token };
},
allowWs: (req) => {
if (resolvePathname(req.url) !== "/api/gateway/ws") return false;
return true;
},
verifyClient: (info) => accessGate.allowUpgrade(info.req),
});
await app.prepare();
const handleUpgrade = app.getUpgradeHandler();
const handleServerUpgrade = (req, socket, head) => {
if (resolvePathname(req.url) === "/api/gateway/ws") {
proxy.handleUpgrade(req, socket, head);
return;
}
handleUpgrade(req, socket, head);
};
const createServer = () =>
http.createServer((req, res) => {
if (accessGate.handleHttp(req, res)) return;
handle(req, res);
});
const servers = hostnames.map(() => createServer());
const attachUpgradeHandlers = (server) => {
server.on("upgrade", handleServerUpgrade);
server.on("newListener", (eventName, listener) => {
if (eventName !== "upgrade") return;
if (listener === handleServerUpgrade) return;
process.nextTick(() => {
server.removeListener("upgrade", listener);
});
});
};
for (const server of servers) {
attachUpgradeHandlers(server);
}
const listenOnHost = (server, host) =>
new Promise((resolve, reject) => {
const onError = (err) => {
server.off("error", onError);
reject(err);
};
server.once("error", onError);
server.listen(port, host, () => {
server.off("error", onError);
resolve();
});
});
const closeServer = (server) =>
new Promise((resolve) => {
if (!server.listening) return resolve();
server.close(() => resolve());
});
try {
await Promise.all(servers.map((server, index) => listenOnHost(server, hostnames[index])));
} catch (err) {
await Promise.all(servers.map((server) => closeServer(server)));
throw err;
}
const hostForBrowser = hostnames.some((value) => value === "127.0.0.1" || value === "::1")
? "localhost"
: hostname === "0.0.0.0" || hostname === "::"
? "localhost"
: hostname;
const browserUrl = `http://${hostForBrowser}:${port}`;
console.info(`Open in browser: ${browserUrl}`);
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});