Files
karczma-aplikacja-stoliki/server.js
2026-04-30 20:21:29 +02:00

175 lines
4.8 KiB
JavaScript

const path = require("path");
const express = require("express");
const WebSocket = require("ws");
const PORT = process.env.PORT || 3000;
const UPSTREAM_URL = "wss://api.serwer.magico.pl/gastro_serwer";
const WS_ORIGIN = process.env.WS_ORIGIN || "https://serwer.magico.pl";
const WS_COOKIE = process.env.WS_COOKIE || "";
const WS_ACCESS_TOKEN = process.env.WS_ACCESS_TOKEN || "d55323bbe5fe56e5a7b9ecf657979a6d5ac49d00";
const app = express();
app.use(express.static(path.join(__dirname, "public")));
const browserClients = new Set();
let upstream = null;
let reconnectTimer = null;
let lastBillsSnapshot = null;
function getDefaultInitMessages() {
const list = [];
// Z obserwacji aplikacji źródłowej: najpierw token, potem ustawienie kierunku.
if (WS_ACCESS_TOKEN) {
list.push({ cmd: "token", access_token: WS_ACCESS_TOKEN });
}
list.push({ cmd: "kierunek", kierunek: "all" });
// Dodatkowe żądania pobrania aktualnych rachunków.
list.push(
{ Type: "bills", kierunek: "all", type: "current" },
{ type: "current", kierunek: "all" },
{ action: "current" }
);
return list;
}
function getInitMessages() {
const raw = process.env.INIT_MESSAGES;
if (!raw) return getDefaultInitMessages();
try {
const parsed = JSON.parse(raw);
if (Array.isArray(parsed)) return parsed;
} catch (err) {
console.warn("[CONFIG] INIT_MESSAGES is invalid JSON, using defaults:", err.message);
}
return getDefaultInitMessages();
}
function broadcast(payload) {
const text = JSON.stringify(payload);
for (const client of browserClients) {
if (client.readyState === WebSocket.OPEN) {
client.send(text);
}
}
}
function connectUpstream() {
if (upstream && (upstream.readyState === WebSocket.OPEN || upstream.readyState === WebSocket.CONNECTING)) {
return;
}
console.log("[UPSTREAM] Connecting:", UPSTREAM_URL);
upstream = new WebSocket(UPSTREAM_URL, {
headers: {
Origin: WS_ORIGIN,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
Pragma: "no-cache",
"Cache-Control": "no-cache",
...(WS_COOKIE ? { Cookie: WS_COOKIE } : {})
}
});
upstream.on("open", () => {
console.log("[UPSTREAM] Connected");
broadcast({ event: "status", connected: true, ts: Date.now() });
// W wielu implementacjach połączenie zwraca dane dopiero po żądaniu inicjalnym.
for (const msg of getInitMessages()) {
try {
upstream.send(JSON.stringify(msg));
console.log("[UPSTREAM] Sent init message:", msg);
broadcast({ event: "sent", payload: msg, ts: Date.now() });
} catch (err) {
console.warn("[UPSTREAM] Failed to send init message:", err.message);
}
}
});
upstream.on("message", (data) => {
const raw = data.toString();
let parsed = null;
try {
parsed = JSON.parse(raw);
} catch {
// wiadomość może być nie-JSON
}
if (parsed && parsed.Type === "bills" && Array.isArray(parsed.Bills)) {
lastBillsSnapshot = {
event: "snapshot",
ts: Date.now(),
parsed
};
}
broadcast({
event: "message",
ts: Date.now(),
raw,
parsed
});
});
upstream.on("close", (code, reasonBuffer) => {
const reason = reasonBuffer ? reasonBuffer.toString() : "";
console.log(`[UPSTREAM] Closed (${code}) ${reason}`);
broadcast({ event: "status", connected: false, code, reason, ts: Date.now() });
clearTimeout(reconnectTimer);
reconnectTimer = setTimeout(connectUpstream, 3000);
});
upstream.on("error", (err) => {
console.error("[UPSTREAM] Error:", err.message);
broadcast({ event: "error", message: err.message, ts: Date.now() });
});
}
const server = app.listen(PORT, () => {
console.log(`Viewer running at http://localhost:${PORT}`);
connectUpstream();
});
const wss = new WebSocket.Server({ server, path: "/ws" });
wss.on("connection", (socket) => {
browserClients.add(socket);
socket.send(JSON.stringify({ event: "status", connected: upstream?.readyState === WebSocket.OPEN, ts: Date.now() }));
if (lastBillsSnapshot) {
socket.send(JSON.stringify(lastBillsSnapshot));
}
socket.on("message", (raw) => {
let data;
try {
data = JSON.parse(raw.toString());
} catch {
return;
}
if (data.action === "sendUpstream" && data.payload && upstream?.readyState === WebSocket.OPEN) {
try {
const text = JSON.stringify(data.payload);
upstream.send(text);
console.log("[BROWSER->UPSTREAM]", text);
broadcast({ event: "sent", payload: data.payload, ts: Date.now() });
} catch (err) {
socket.send(JSON.stringify({ event: "error", message: err.message, ts: Date.now() }));
}
}
});
socket.on("close", () => {
browserClients.delete(socket);
});
});