@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
JavaScript
;
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