const express = require("express"); const http = require("http"); const WebSocket = require("ws"); const path = require("path"); const pool = require("./db"); const app = express(); const PORT = process.env.PORT || 3100; app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Serve static files from public directory app.use("/static", express.static(path.join(__dirname, "public"))); // In-memory store for latest item lists. // Structure: // { // sourceIdOrName: { // from: , // ts: , // items: [ { name, count }, ... ], // clientId: // optional internal id // }, // ... // } const latestItems = {}; // Convenience: sanitize/normalize an items array coming from clients function normalizeItems(arr) { if (typeof arr !== "object" || !Array.isArray(arr)) return []; const out = []; for (let i = 0; i < arr.length; i++) { const it = arr[i]; if (!it) continue; const name = String(it.name || it.id || it.item || "unknown"); const count = Number(it.count || it.qty || it.stackSize || 0) || 0; out.push({ name, count }); } return out; } // Create HTTP server & WebSocket server const server = http.createServer(app); const wss = new WebSocket.Server({ server }); require("./users-api")(app, wss, pool); // Helper to send JSON to a ws client function sendJSON(ws, obj) { try { ws.send(JSON.stringify(obj)); } catch (err) { // ignore send errors for now } } wss.on("connection", (ws, req) => { const clientId = Date.now().toString(36) + Math.random().toString(36).slice(2, 8); ws._clientId = clientId; console.log("Client connected:", clientId, "from", req.socket.remoteAddress); // Welcome message sendJSON(ws, { type: "welcome", id: clientId, timestamp: Date.now() }); ws.on("message", (raw) => { const text = raw.toString(); // Try to parse JSON; if parse fails we still broadcast the raw text as a message let parsed = null; try { parsed = JSON.parse(text); } catch (e) { parsed = null; } // If the parsed object contains an items array, store it if ( parsed && typeof parsed === "object" && "items" in parsed && Array.isArray(parsed.items) ) { const from = String( parsed.from || parsed.source || parsed.client || parsed.id || clientId, ); const id = parsed.id || clientId; const ts = Number(parsed.ts || parsed.time || Date.now()) || Date.now(); const items = normalizeItems(parsed.items); latestItems[id] = { from, ts, items, clientId, }; console.log(`Stored items from '${from}' (${items.length} entries)`); } // Broadcast to other clients (keep original behaviour: broadcast as { type: 'message', ... }) var out = {}; if (parsed && parsed.type === "request") { out = { type: parsed.type, items: parsed.items || [], address: parsed.address || "", to: parsed.to || "", }; } else if (parsed) { out = { type: parsed.type || "message", from: parsed && parsed.from ? parsed.from : parsed && parsed.id ? parsed.id : clientId, text: (parsed && parsed.message) || (typeof parsed === "string" ? parsed : text), raw: parsed || text, ts: Date.now(), }; } if (parsed && parsed.type && parsed.type !== "items") { wss.clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { try { client.send(JSON.stringify(out)); } catch (err) { // Ignore send errors for now console.warn( `Failed to send message to client ${client._clientId}: ${err.message}`, ); } } }); } }); ws.on("close", () => { console.log("Client disconnected:", clientId); // delete stored items on disconnect for (const shopId in latestItems) { const shop = latestItems[shopId]; if (shop.clientId === clientId) { delete latestItems[shopId]; } } }); ws.on("error", (err) => { console.log("WebSocket error from", clientId, err && err.message); }); }); // API endpoint that returns the latest items as JSON app.get("/api/items", (req, res) => { res.set("Access-Control-Allow-Origin", "*"); // Return the whole latestItems object with metadata res.json({ ok: true, ts: Date.now(), itemCount: Object.values(latestItems).reduce( (sum, item) => sum + item.items.length, 0, ), data: latestItems, }); }); app.get("/api/item/:id", (req, res) => { const itemName = req.params.id; res.set("Access-Control-Allow-Origin", "*"); for (const shopId in latestItems) { const shop = latestItems[shopId]; const item = shop.items.find((i) => i.name === itemName); if (item) { return res.json({ ok: true, shopId, item, source: shop.from, timestamp: shop.ts, }); } } res.status(404).json({ ok: false, error: "Item not found", }); }); app.post("/api/send", (req, res) => { const { shopId, address, items } = req.body; // Basic validation if ( typeof shopId !== "number" || typeof address !== "string" || !Array.isArray(items) ) { return res.status(400).json({ ok: false, error: "Invalid parameters", }); } for (const item of items) { if (typeof item.id !== "string" || typeof item.quantity !== "number") { return res.status(400).json({ ok: false, error: "Invalid item format", }); } } // Build request payload const request = { type: "request", to: shopId, address, items: items.map((item) => ({ name: item.id, _requestCount: item.quantity, })), }; // Send via WebSocket wss.clients.forEach((client) => { if (client.readyState === 1) { client.send(JSON.stringify(request)); } }); return res.status(200).json({ ok: true, message: "Items sent successfully", data: request, }); }); // Serve the items UI page from public/items.html for the /items route. // The actual HTML/JS for the page lives in public/items.html (moved from server-side rendering). app.get("/", (req, res) => { res.sendFile(path.join(__dirname, "public", "index.html")); }); app.get("/items", (req, res) => { res.sendFile(path.join(__dirname, "public", "items.html")); }); app.get("/msg", (req, res) => { res.sendFile(path.join(__dirname, "public", "msg.html")); }); app.get("/register", (req, res) => { res.sendFile(path.join(__dirname, "public", "register.html")); }); app.get("/ws-test", (req, res) => { res.sendFile(path.join(__dirname, "public", "ws-test.html")); }); app.get("/admin", (req, res) => { res.sendFile(path.join(__dirname, "public", "admin.html")); }); server.listen(PORT, () => { console.log(`🚀 Server listening on http://localhost:${PORT}`); console.log(`🌐 Web UI: http://localhost:${PORT}/`); console.log(`📄 Items page: http://localhost:${PORT}/items`); console.log(`📦 Items API: http://localhost:${PORT}/api/items`); });