Spaces:
Running
Running
| import path from 'node:path'; | |
| import {fileURLToPath} from 'node:url'; | |
| import cors from 'cors'; | |
| import express from 'express'; | |
| import { | |
| CLUSTER_MODELS, | |
| DEFAULT_CLUSTER_MODEL_ID, | |
| isValidClusterModelId, | |
| } from '../shared/clusterModels.js'; | |
| import {ApiTaskHandler} from './ApiTaskHandler.js'; | |
| import {validateHostRegistered} from './hostValidation.js'; | |
| import {initSse, writeSse, writeSseComment} from './sse.js'; | |
| import {store} from './store.js'; | |
| import type {RegisterHostBody} from './types.js'; | |
| const PORT = Number(process.env.PORT ?? 8787); | |
| const TASK_TIMEOUT_MS = Number(process.env.TASK_TIMEOUT_MS ?? 120_000); | |
| const SERVE_STATIC = process.env.SERVE_STATIC === '1'; | |
| const staticDir = path.resolve( | |
| path.dirname(fileURLToPath(import.meta.url)), | |
| '../docs', | |
| ); | |
| const app = express(); | |
| app.use((_req, res, next) => { | |
| res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); | |
| res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp'); | |
| next(); | |
| }); | |
| app.use(cors()); | |
| app.use(express.json({limit: '32mb'})); | |
| app.get('/health', (_req, res) => { | |
| res.json({status: 'ok', api_version: 2}); | |
| }); | |
| app.get('/v1/hosts', (_req, res) => { | |
| res.json({hosts: store.listHosts()}); | |
| }); | |
| app.get('/v1/models', (_req, res) => { | |
| res.json({ | |
| models: CLUSTER_MODELS.map((model) => ({ | |
| id: model.id, | |
| label: model.label, | |
| description: model.description, | |
| implemented: model.implemented, | |
| })), | |
| }); | |
| }); | |
| app.post('/api/hosts/register', (req, res) => { | |
| const body = req.body as RegisterHostBody; | |
| if (!body?.id?.trim()) { | |
| res.status(400).json({error: 'id is required'}); | |
| return; | |
| } | |
| const modelId = body.model?.trim() || DEFAULT_CLUSTER_MODEL_ID; | |
| if (!isValidClusterModelId(modelId)) { | |
| res.status(400).json({ | |
| error: `Unknown model '${modelId}'. Supported: ${CLUSTER_MODELS.map((m) => m.id).join(', ')}`, | |
| }); | |
| return; | |
| } | |
| const host = store.registerHost(body.id.trim(), modelId); | |
| res.json({ | |
| id: host.id, | |
| model: host.model, | |
| online: store.isHostOnline(host.id), | |
| }); | |
| }); | |
| app.get('/api/hosts/stream', (req, res) => { | |
| const hostId = String(req.query.host_id ?? '').trim(); | |
| if (!hostId) { | |
| res.status(400).json({error: 'host_id query parameter is required'}); | |
| return; | |
| } | |
| const hostError = validateHostRegistered(store, hostId); | |
| if (hostError) { | |
| res.status(hostError.status).json({error: hostError.message}); | |
| return; | |
| } | |
| initSse(res); | |
| const heartbeat = setInterval(() => { | |
| store.heartbeat(hostId); | |
| writeSseComment(res, 'ping'); | |
| }, 15_000); | |
| store.attachHostStream(hostId, { | |
| write: (event, data) => writeSse(res, event, data), | |
| close: () => { | |
| clearInterval(heartbeat); | |
| if (!res.writableEnded) { | |
| res.end(); | |
| } | |
| }, | |
| }); | |
| writeSse(res, 'ready', {host_id: hostId}); | |
| req.on('close', () => { | |
| clearInterval(heartbeat); | |
| store.detachHostStream(hostId); | |
| }); | |
| }); | |
| new ApiTaskHandler(store, TASK_TIMEOUT_MS).mount(app); | |
| if (SERVE_STATIC) { | |
| app.use(express.static(staticDir)); | |
| app.get('/monitor.html', (_req, res) => { | |
| res.sendFile(path.join(staticDir, 'monitor.html')); | |
| }); | |
| app.get('/', (_req, res) => { | |
| res.sendFile(path.join(staticDir, 'index.html')); | |
| }); | |
| } | |
| app.listen(PORT, '0.0.0.0', () => { | |
| console.log(`Detection broker listening on http://0.0.0.0:${PORT}`); | |
| if (SERVE_STATIC) { | |
| console.log(` Static UI: / and /monitor.html`); | |
| } | |
| console.log(` POST /v1/detect`); | |
| console.log(` POST /v1/describe`); | |
| console.log(` GET /v1/hosts`); | |
| console.log(` GET /v1/models`); | |
| console.log(` GET /api/hosts/stream?host_id=...`); | |
| }); | |