UNPKG

@fdm-monster/server

Version:

FDM Monster is a bulk OctoPrint manager to set up, configure and monitor 3D printers. Our aim is to provide extremely optimized websocket performance and reliability.

291 lines (290 loc) 11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const _express = /*#__PURE__*/ _interop_require_default(require("express")); const _multer = /*#__PURE__*/ _interop_require_default(require("multer")); const _fs = /*#__PURE__*/ _interop_require_default(require("fs")); const _path = /*#__PURE__*/ _interop_require_default(require("path")); const _ws = require("ws"); const _http = /*#__PURE__*/ _interop_require_default(require("http")); const _zod = require("zod"); const _nodeconsole = /*#__PURE__*/ _interop_require_wildcard(require("node:console")); const _apimessages = require("./utils/api-messages"); const _wsmessages = require("./utils/ws-messages"); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interop_require_wildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = { __proto__: null }; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for(var key in obj){ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } const port = process.argv[2] ? parseInt(process.argv[2]) : 1234; const uploadsDir = _path.default.join(__dirname, "uploads", `server-${port.toString()}`); const WS_MESSAGE_INTERVAL = 1000; if (!_fs.default.existsSync(uploadsDir)) { _nodeconsole.log(`[PORT ${port}] Creating uploads folder ${uploadsDir}`); _fs.default.mkdirSync(uploadsDir, { recursive: true }); } cleanupUploads(); const upload = (0, _multer.default)({ dest: uploadsDir }); const app = (0, _express.default)(); const server = _http.default.createServer(app); const wss = new _ws.Server({ server }); const clients = new Map(); const AuthSchema = _zod.z.object({ command: _zod.z.literal("auth"), user: _zod.z.literal("admin"), token: _zod.z.string().regex(/^[a-zA-Z0-9]{32}$/) }); wss.on("connection", (ws)=>{ const clientId = Date.now().toString(); _nodeconsole.log(`[PORT ${port}] New WebSocket connection established`); let authenticated = false; let messageInterval = null; ws.on("message", async (message)=>{ try { const msgString = message.toString(); if (msgString.includes("throttle")) { _nodeconsole.log(`Throttle message received ${msgString}`); return; } const json = JSON.parse(msgString); if (!authenticated) { if (!json) { _nodeconsole.log(`[PORT ${port}] Invalid message JSON format`); ws.close(); return; } try { const parts = json.auth.split(":"); AuthSchema.parse({ command: "auth", user: parts[0], token: parts[1] }); _nodeconsole.log(`[PORT ${port}] Authentication successful for admin`); authenticated = true; clients.set(clientId, { ws, authenticated }); setTimeout(async ()=>{ try { ws.send(JSON.stringify(_wsmessages.currentMessage)); } catch (fileError) { _nodeconsole.error(`[PORT ${port}] Error reading message file:`, fileError); ws.send(JSON.stringify({ error: "Failed to read message content" })); } }, 500); messageInterval = setupMessageInterval(ws, clientId, WS_MESSAGE_INTERVAL); } catch (validationError) { _nodeconsole.log(`[PORT ${port}] Authentication failed: Invalid token format ${JSON.stringify(json)} ${validationError}`); ws.close(); } } else { _nodeconsole.log(`[PORT ${port}] Received message from authenticated client`); } } catch (error) { _nodeconsole.error(`[PORT ${port}] Error processing message:`, error); ws.close(); } }); ws.on("close", ()=>{ _nodeconsole.log(`[PORT ${port}] WebSocket connection closed ${clientId}`); if (messageInterval) { clearInterval(messageInterval); messageInterval = null; } clients.delete(clientId); }); const setupMessageInterval = (ws, clientId, baseInterval)=>{ if (messageInterval) { clearInterval(messageInterval); messageInterval = null; } const getRandomizedInterval = (baseMs)=>{ const variationFactor = 0.85 + Math.random() * 0.3; return Math.floor(baseMs * variationFactor); }; const sendPeriodicMessage = ()=>{ try { if (ws.readyState === ws.OPEN) { ws.send(JSON.stringify(_wsmessages.currentMessage)); _nodeconsole.log(`[PORT ${port}] Periodic message sent to client (ID: ${clientId})`); messageInterval = setTimeout(sendPeriodicMessage, getRandomizedInterval(baseInterval)); } else { _nodeconsole.log(`[PORT ${port}] Stopped periodic messages for client (ID: ${clientId})`); messageInterval = null; } } catch (error) { _nodeconsole.error(`[PORT ${port}] Error sending periodic message:`, error); messageInterval = null; } }; messageInterval = setTimeout(sendPeriodicMessage, getRandomizedInterval(baseInterval)); return messageInterval; }; }); app.get("/api/version", async (req, res)=>{ res.send({ server: "1", api: "2", text: "3" }); }); app.get("/api/currentuser", async (req, res)=>{ res.send({ name: "admin", permissions: [], groups: [] }); }); app.get("/api/printer", async (req, res)=>{ _nodeconsole.log(`[PORT ${port}] ${req.method.toUpperCase()} ${req.url} request`); res.status(200).json(_apimessages.printerHistorySuccessResponse); }); app.get("/api/connection", async (req, res)=>{ _nodeconsole.log(`[PORT ${port}] ${req.method.toUpperCase()} ${req.url} request`); res.status(200).json(_apimessages.connectionSuccessResponse); }); app.get("/api/job", async (req, res)=>{ _nodeconsole.log(`[PORT ${port}] ${req.method.toUpperCase()} ${req.url} request`); res.status(200).json(_apimessages.jobSuccessResponse); }); app.get("/api/files/local", async (req, res)=>{ _nodeconsole.log(`[PORT ${port}] ${req.method.toUpperCase()} ${req.url} request`); res.status(200).json(_apimessages.filesSuccessResponse); }); app.post("/api/login", async (req, res)=>{ res.send({ _is_external_client: true, _login_mechanism: "apikey", session: "123123abc123123abc123123abc123ab" }); }); app.post("/api/files/local", upload.single("file"), (req, res)=>{ const { select, print } = req.body; const file = req.file; if (!file) { return res.status(400).json({ error: "Missing file" }); } if (typeof select === "undefined" || typeof print === "undefined") { return res.status(400).json({ error: "Missing required fields: select, print" }); } if (Object.keys(req.body).length !== 2) { return res.status(400).json({ error: "Only fields 'select' and 'print' are allowed" }); } if (![ "true", "false" ].includes(select) || ![ "true", "false" ].includes(print)) { return res.status(400).json({ error: "Fields 'select' and 'print' must be boolean values (true/false)" }); } _nodeconsole.log(`[PORT ${port}] Received file:`, file.originalname); _nodeconsole.log(`[PORT ${port}] File size:`, file.size); _nodeconsole.log(`[PORT ${port}] MIME type:`, file.mimetype); _nodeconsole.log(`[PORT ${port}] select:`, select, `\n[PORT ${port}] print:`, print, `\n-----`); res.json({ message: "File received successfully" }); }); app.use((req, res)=>{ _nodeconsole.log(`[PORT ${port}] ${req.method.toUpperCase()} ${req.url} Not found`); res.status(404).json({ error: "Not Found" }); }); server.listen(port, ()=>{ _nodeconsole.log(`[PORT ${port}] Server is running on http://localhost:${port}`); _nodeconsole.log(`[PORT ${port}] WebSocket server is available on ws://localhost:${port}`); }); process.on("SIGINT", ()=>{ _nodeconsole.log("\n[PORT ${port}] Received SIGINT signal (Ctrl+C). Shutting down gracefully..."); cleanupUploads(); server.close(()=>{ _nodeconsole.log("[PORT ${port}] Server closed"); process.exit(0); }); }); process.on("SIGTERM", ()=>{ _nodeconsole.log("\n[PORT ${port}] Received SIGTERM signal. Shutting down gracefully..."); cleanupUploads(); server.close(()=>{ _nodeconsole.log("[PORT ${port}] Server closed"); process.exit(0); }); }); function cleanupUploads() { try { _nodeconsole.log(`[PORT ${port}] Cleaning up uploads folder...`); const files = _fs.default.readdirSync(uploadsDir); for (const file of files){ const filePath = _path.default.join(uploadsDir, file); _fs.default.unlinkSync(filePath); _nodeconsole.log(`[PORT ${port}] Deleted: ${filePath}`); } _nodeconsole.log(`[PORT ${port}] Uploads folder cleanup complete`); } catch (error) { _nodeconsole.error(`[PORT ${port}] Error cleaning up uploads folder:`, error); } } //# sourceMappingURL=mock-octoprint.server.js.map