Create-CC-Shop/server.js
2026-01-27 14:06:29 +01:00

275 lines
7.1 KiB
JavaScript

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: <string>,
// ts: <number>,
// items: [ { name, count }, ... ],
// clientId: <ws client id> // 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/shop/items/buy", (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`);
});