86 lines
2.2 KiB
JavaScript
86 lines
2.2 KiB
JavaScript
|
|
import express from "express";
|
||
|
|
import http from "http";
|
||
|
|
import { WebSocketServer } from "ws";
|
||
|
|
import cors from "cors";
|
||
|
|
import pkg from "pg";
|
||
|
|
|
||
|
|
const { Pool } = pkg;
|
||
|
|
|
||
|
|
const app = express();
|
||
|
|
const PORT = 3000;
|
||
|
|
|
||
|
|
// Setup Postgres connection pool
|
||
|
|
const pool = new Pool({
|
||
|
|
connectionString: process.env.DATABASE_URL || "neverleavetheconnectionstringhereforproduction",
|
||
|
|
});
|
||
|
|
|
||
|
|
app.use(cors());
|
||
|
|
|
||
|
|
// HTTP GET /games/:id → return game info from DB
|
||
|
|
app.get("/games/:id", async (req, res) => {
|
||
|
|
const gameId = req.params.id;
|
||
|
|
try {
|
||
|
|
const result = await pool.query("SELECT info FROM items WHERE id = $1", [gameId]);
|
||
|
|
if (result.rows.length === 0) {
|
||
|
|
return res.redirect(302, "https://engine.battlesnake.com");
|
||
|
|
// return res.status(404).json({ error: "Game not found" });
|
||
|
|
|
||
|
|
}
|
||
|
|
res.json(result.rows[0].info); // send back the JSONB field
|
||
|
|
} catch (err) {
|
||
|
|
console.error(err);
|
||
|
|
res.status(500).json({ error: "Database error" });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Create HTTP server and attach WebSocket server
|
||
|
|
const server = http.createServer(app);
|
||
|
|
const wss = new WebSocketServer({ server });
|
||
|
|
|
||
|
|
// Helper to match the path pattern
|
||
|
|
function matchGameEventsPath(url) {
|
||
|
|
if (!url) return null;
|
||
|
|
const match = url.match(/^\/games\/([^\/]+)\/events$/);
|
||
|
|
return match ? match[1] : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
wss.on("connection", async (ws, req) => {
|
||
|
|
const gameId = matchGameEventsPath(req.url);
|
||
|
|
if (!gameId) {
|
||
|
|
ws.close();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`WS client connected for game: ${gameId}`);
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Grab frames array from DB
|
||
|
|
const result = await pool.query("SELECT frames FROM items WHERE id = $1", [gameId]);
|
||
|
|
if (result.rows.length === 0) {
|
||
|
|
ws.send(JSON.stringify({ error: "Game not found" }));
|
||
|
|
ws.close();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const frames = result.rows[0].frames || [];
|
||
|
|
|
||
|
|
// Stream frames one by one with delay
|
||
|
|
frames.forEach((frame, i) => {
|
||
|
|
setTimeout(() => {
|
||
|
|
if (ws.readyState === ws.OPEN) {
|
||
|
|
ws.send(JSON.stringify(frame));
|
||
|
|
}
|
||
|
|
}, i * 5); // staggered delay
|
||
|
|
});
|
||
|
|
|
||
|
|
} catch (err) {
|
||
|
|
console.error(err);
|
||
|
|
ws.send(JSON.stringify({ error: "Database error" }));
|
||
|
|
ws.close();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
server.listen(PORT, "0.0.0.0", () => {
|
||
|
|
console.log(`Server running at http://localhost:${PORT} (HTTP + WebSocket)`);
|
||
|
|
});
|