feat: setup project
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { createServer } from 'node:http';
|
||||
import path from 'node:path';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import sirv from 'sirv';
|
||||
import { PROTOCOL_VERSION, type ServerMessage } from '@idle/shared';
|
||||
import { WsGateway } from './net/ws';
|
||||
import { stubAvatarsAt } from './sim/stub';
|
||||
|
||||
const PORT = Number(process.env.PORT ?? 3000);
|
||||
const TICK_MS = Number(process.env.TICK_MS ?? 1000);
|
||||
const startedAt = Date.now();
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const currentTick = (): number => Math.floor((Date.now() - startedAt) / TICK_MS);
|
||||
|
||||
const snapshot = (): ServerMessage => ({
|
||||
t: 'snapshot',
|
||||
tick: currentTick(),
|
||||
avatars: stubAvatarsAt(Date.now()),
|
||||
});
|
||||
|
||||
const gateway = new WsGateway({ getSnapshot: snapshot, tickMs: TICK_MS });
|
||||
|
||||
// Собранные клиенты раздаём сами — OBS обращается к одному порту.
|
||||
// В dev клиенты живут на своих vite-портах (5173/5174).
|
||||
const staticMounts = [
|
||||
{ prefix: '/overlay/', dir: path.resolve(__dirname, '../../overlay/dist') },
|
||||
{ prefix: '/web/', dir: path.resolve(__dirname, '../../web/dist') },
|
||||
].map((m) => ({ ...m, middleware: existsSync(m.dir) ? sirv(m.dir) : null }));
|
||||
|
||||
function json(res: ServerResponse, status: number, body: unknown): void {
|
||||
res.writeHead(status, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify(body));
|
||||
}
|
||||
|
||||
function handleHttp(req: IncomingMessage, res: ServerResponse): void {
|
||||
const url = new URL(req.url ?? '/', 'http://localhost');
|
||||
const p = url.pathname;
|
||||
|
||||
if (p === '/healthz') {
|
||||
json(res, 200, {
|
||||
ok: true,
|
||||
protocol: PROTOCOL_VERSION,
|
||||
tickMs: TICK_MS,
|
||||
uptimeSec: Math.round((Date.now() - startedAt) / 1000),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (const m of staticMounts) {
|
||||
if (!m.middleware) continue;
|
||||
const base = m.prefix.slice(0, -1);
|
||||
if (p === base) {
|
||||
res.writeHead(301, { location: `${base}/` });
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
if (p.startsWith(m.prefix)) {
|
||||
req.url = p.slice(base.length) + url.search;
|
||||
m.middleware(req, res, () => json(res, 404, { error: 'not found' }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
json(res, 404, {
|
||||
error: 'not found',
|
||||
hint: 'dev: overlay http://localhost:5173, web http://localhost:5174; для /overlay/ и /web/ сначала pnpm build',
|
||||
});
|
||||
}
|
||||
|
||||
const server = createServer(handleHttp);
|
||||
|
||||
server.on('upgrade', (req, socket, head) => {
|
||||
const { pathname } = new URL(req.url ?? '/', 'http://localhost');
|
||||
if (pathname === '/ws') {
|
||||
gateway.handleUpgrade(req, socket, head);
|
||||
} else {
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
setInterval(() => gateway.broadcast(snapshot()), TICK_MS);
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`[idle-xboct] http://localhost:${PORT} — протокол v${PROTOCOL_VERSION}, тик ${TICK_MS} мс`);
|
||||
console.log(`[idle-xboct] overlay: http://localhost:5173 (dev) | web: http://localhost:5174 (dev)`);
|
||||
const built = staticMounts.filter((m) => m.middleware).map((m) => m.prefix);
|
||||
if (built.length > 0) {
|
||||
console.log(`[idle-xboct] раздаю сборки: ${built.join(', ')}`);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user