feat: complete TASK-WP0-07 supervisor
This commit is contained in:
@@ -2,6 +2,12 @@ import { resolve } from "node:path";
|
||||
|
||||
import { createApp } from "./app.js";
|
||||
import { readBrowserSupportRelease } from "./browser-support.js";
|
||||
import { attachApiSupervisorControl, initializeApiCredentialClients, receiveApiCredentials } from "./supervisor-channel.js";
|
||||
|
||||
const credentialChannelEnabled = process.argv.includes("--dada-credential-stdin");
|
||||
if (credentialChannelEnabled) {
|
||||
initializeApiCredentialClients(await receiveApiCredentials());
|
||||
}
|
||||
|
||||
const browserSupportRelease = readBrowserSupportRelease(resolve("RELEASE.json"));
|
||||
const app = await createApp(browserSupportRelease ? { browserSupportRelease } : {});
|
||||
@@ -10,3 +16,10 @@ await app.listen({
|
||||
host: "127.0.0.1",
|
||||
port: 43121,
|
||||
});
|
||||
|
||||
const controlPipeIndex = process.argv.indexOf("--dada-control-pipe");
|
||||
if (controlPipeIndex >= 0) {
|
||||
const controlPipe = process.argv[controlPipeIndex + 1];
|
||||
if (!controlPipe) throw new Error("Supervisor control pipe name is required.");
|
||||
attachApiSupervisorControl(controlPipe, () => app.close());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { createConnection } from "node:net";
|
||||
|
||||
const API_CREDENTIALS = ["Dada/P0A/api/resend", "Dada/P0A/api/amap"] as const;
|
||||
|
||||
export async function receiveApiCredentials(input: NodeJS.ReadableStream = process.stdin) {
|
||||
const chunks: Buffer[] = [];
|
||||
for await (const chunk of input) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
||||
const payload = Buffer.concat(chunks);
|
||||
try {
|
||||
const parsed = JSON.parse(payload.toString("utf8")) as Record<string, unknown>;
|
||||
const names = Object.keys(parsed).sort();
|
||||
const expected = [...API_CREDENTIALS].sort();
|
||||
if (names.length !== expected.length || names.some((name, index) => name !== expected[index])) {
|
||||
throw new Error("API credential channel contains an unexpected credential scope.");
|
||||
}
|
||||
if (expected.some((name) => typeof parsed[name] !== "string" || parsed[name] === "")) {
|
||||
throw new Error("API credential channel contains an invalid credential value.");
|
||||
}
|
||||
return parsed as Record<(typeof API_CREDENTIALS)[number], string>;
|
||||
} finally {
|
||||
payload.fill(0);
|
||||
for (const chunk of chunks) chunk.fill(0);
|
||||
chunks.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function initializeApiCredentialClients(credentials: Record<(typeof API_CREDENTIALS)[number], string>) {
|
||||
const configured = API_CREDENTIALS.every((name) => credentials[name].length > 0);
|
||||
for (const name of API_CREDENTIALS) credentials[name] = "";
|
||||
if (!configured) throw new Error("API credential client initialization failed.");
|
||||
}
|
||||
|
||||
export function attachApiSupervisorControl(pipeName: string, shutdown: () => Promise<void>) {
|
||||
const socket = createConnection(`\\\\.\\pipe\\${pipeName}`);
|
||||
let pending = "";
|
||||
socket.setEncoding("utf8");
|
||||
socket.on("connect", () => socket.write("ready\n"));
|
||||
socket.on("data", (chunk) => {
|
||||
pending += chunk;
|
||||
if (!pending.includes("\n")) return;
|
||||
const [command] = pending.split("\n", 1);
|
||||
pending = "";
|
||||
if (command === "shutdown") void shutdown().finally(() => socket.end());
|
||||
});
|
||||
return socket;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { createConnection } from "node:net";
|
||||
|
||||
const WORKER_CREDENTIALS = ["Dada/P0A/worker/ai-gateway"] as const;
|
||||
|
||||
export async function receiveWorkerCredentials(input: NodeJS.ReadableStream = process.stdin) {
|
||||
const chunks: Buffer[] = [];
|
||||
for await (const chunk of input) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
||||
const payload = Buffer.concat(chunks);
|
||||
try {
|
||||
const parsed = JSON.parse(payload.toString("utf8")) as Record<string, unknown>;
|
||||
const names = Object.keys(parsed).sort();
|
||||
const expected = [...WORKER_CREDENTIALS].sort();
|
||||
if (names.length !== expected.length || names.some((name, index) => name !== expected[index])) {
|
||||
throw new Error("Worker credential channel contains an unexpected credential scope.");
|
||||
}
|
||||
if (expected.some((name) => typeof parsed[name] !== "string" || parsed[name] === "")) {
|
||||
throw new Error("Worker credential channel contains an invalid credential value.");
|
||||
}
|
||||
return parsed as Record<(typeof WORKER_CREDENTIALS)[number], string>;
|
||||
} finally {
|
||||
payload.fill(0);
|
||||
for (const chunk of chunks) chunk.fill(0);
|
||||
chunks.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function initializeWorkerCredentialClient(credentials: Record<(typeof WORKER_CREDENTIALS)[number], string>) {
|
||||
const configured = WORKER_CREDENTIALS.every((name) => credentials[name].length > 0);
|
||||
for (const name of WORKER_CREDENTIALS) credentials[name] = "";
|
||||
if (!configured) throw new Error("Worker credential client initialization failed.");
|
||||
}
|
||||
|
||||
export function attachWorkerSupervisorControl(pipeName: string, shutdown: () => Promise<void> | void) {
|
||||
const socket = createConnection(`\\\\.\\pipe\\${pipeName}`);
|
||||
let pending = "";
|
||||
socket.setEncoding("utf8");
|
||||
socket.on("connect", () => socket.write("ready\n"));
|
||||
socket.on("data", (chunk) => {
|
||||
pending += chunk;
|
||||
if (!pending.includes("\n")) return;
|
||||
const [command] = pending.split("\n", 1);
|
||||
pending = "";
|
||||
if (command === "shutdown") void Promise.resolve(shutdown()).finally(() => socket.end());
|
||||
});
|
||||
return socket;
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { parentPort } from "node:worker_threads";
|
||||
|
||||
import { attachWorkerSupervisorControl, initializeWorkerCredentialClient, receiveWorkerCredentials } from "./supervisor-channel.js";
|
||||
|
||||
export function handleWorkerProbe(message: unknown) {
|
||||
return message === "ping" ? "pong" : null;
|
||||
}
|
||||
@@ -11,3 +13,12 @@ if (workerPort) {
|
||||
workerPort.postMessage(handleWorkerProbe(message));
|
||||
});
|
||||
}
|
||||
|
||||
if (!workerPort && process.argv.includes("--dada-credential-stdin")) {
|
||||
initializeWorkerCredentialClient(await receiveWorkerCredentials());
|
||||
const controlPipeIndex = process.argv.indexOf("--dada-control-pipe");
|
||||
const controlPipe = process.argv[controlPipeIndex + 1];
|
||||
if (controlPipeIndex < 0 || !controlPipe) throw new Error("Supervisor control pipe name is required.");
|
||||
const keepAlive = setInterval(() => undefined, 30_000);
|
||||
attachWorkerSupervisorControl(controlPipe, () => clearInterval(keepAlive));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user