diff --git a/mock-api/mockServer.js b/mock-api/mockServer.js index 9951d31de..1f9245bd6 100644 --- a/mock-api/mockServer.js +++ b/mock-api/mockServer.js @@ -1,33 +1,24 @@ -// used to simulate -// - file uploads -// - EventSource (SSE) for log messages +// Mock server for development +// Simulates file uploads and EventSource (SSE) for log messages import formidable from 'formidable'; -function pad(number) { - let r = String(number); - if (r.length === 1) { - r = '0' + r; - } - return r; -} +// Optimized padding function +const pad = (number) => String(number).padStart(2, '0'); -// e.g. 2024-03-29 07:02:37.856 -Date.prototype.toISOString = function () { - return ( - this.getUTCFullYear() + - '-' + - pad(this.getUTCMonth() + 1) + - '-' + - pad(this.getUTCDate()) + - ' ' + - pad(this.getUTCHours()) + - ':' + - pad(this.getUTCMinutes()) + - ':' + - pad(this.getUTCSeconds()) + - '.' + - String((this.getUTCMilliseconds() / 1000).toFixed(3)).slice(2, 5) +// Cached date formatter to avoid prototype pollution +const formatDate = (date) => { + const year = date.getUTCFullYear(); + const month = pad(date.getUTCMonth() + 1); + const day = pad(date.getUTCDate()); + const hours = pad(date.getUTCHours()); + const minutes = pad(date.getUTCMinutes()); + const seconds = pad(date.getUTCSeconds()); + const milliseconds = String((date.getUTCMilliseconds() / 1000).toFixed(3)).slice( + 2, + 5 ); + + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`; }; export default () => { @@ -35,97 +26,129 @@ export default () => { name: 'vite:mockserver', configureServer: async (server) => { server.middlewares.use(async (req, res, next) => { - // catch any file uploads + // Handle file uploads if (req.url.startsWith('/rest/uploadFile')) { - // show progress + const fileSize = parseInt(req.headers['content-length'] || '0', 10); let progress = 0; - const file_size = req.headers['content-length']; - console.log('File size: ' + file_size); - req.on('data', async (chunk) => { + + // Track upload progress + req.on('data', (chunk) => { progress += chunk.length; - const percentage = (progress / file_size) * 100; - console.log(`Progress: ${Math.round(percentage)}%`); - // await new Promise((resolve) => setTimeout(() => resolve(), 3000)); // slow it down + if (fileSize > 0) { + const percentage = Math.round((progress / fileSize) * 100); + console.log(`Upload progress: ${percentage}%`); + } }); - const form = formidable({}); - let fields; - let files; try { - [fields, files] = await form.parse(req); - } catch (err) { - console.error('Not json form content'); - res.writeHead(err.httpCode || 400, { - 'Content-Type': 'text/plain' + const form = formidable({ + maxFileSize: 50 * 1024 * 1024, // 50MB limit + keepExtensions: true }); - res.end(String(err)); - return; - } - // only process when we have a file - if (Object.keys(files).length > 0) { - const uploaded_file = files.file[0]; - const file_name = uploaded_file.originalFilename; - const file_extension = file_name.substring( - file_name.lastIndexOf('.') + 1 + const [fields, files] = await form.parse(req); + + if (Object.keys(files).length === 0) { + res.statusCode = 400; + res.end('No file uploaded'); + return; + } + + const uploadedFile = files.file[0]; + const fileName = uploadedFile.originalFilename; + const fileExtension = fileName + .substring(fileName.lastIndexOf('.') + 1) + .toLowerCase(); + + console.log( + `File uploaded: ${fileName} (${fileExtension}, ${fileSize} bytes)` ); - console.log('Filename: ' + file_name); - console.log('Extension: ' + file_extension); - console.log('File size: ' + file_size); + // Validate file extension + const validExtensions = new Set(['bin', 'json', 'md5']); + if (!validExtensions.has(fileExtension)) { + res.statusCode = 406; + res.end('Invalid file extension'); + return; + } - if (file_extension === 'bin' || file_extension === 'json') { - console.log('File uploaded successfully!'); - } else if (file_extension === 'md5') { - console.log('MD5 hash generated successfully!'); + // Handle different file types + if (fileExtension === 'md5') { + res.setHeader('Content-Type', 'application/json'); res.end( JSON.stringify({ md5: 'ef4304fc4d9025a58dcf25d71c882d2c' }) ); } else { - res.statusCode = 406; - console.log('Invalid file extension!'); + console.log('File uploaded successfully!'); + res.end(); } + } catch (err) { + console.error('Upload error:', err.message); + res.statusCode = err.httpCode || 400; + res.setHeader('Content-Type', 'text/plain'); + res.end(err.message); } - - res.end(); } - // SSE Eventsource + // Handle Server-Sent Events (SSE) for log streaming else if (req.url.startsWith('/es/log')) { + // Set SSE headers res.writeHead(200, { Connection: 'keep-alive', 'Cache-Control': 'no-cache', - 'Content-Type': 'text/event-stream' + 'Content-Type': 'text/event-stream', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'Cache-Control' }); - let count = 0; - const interval = setInterval(() => { - let message = 'message #' + count; - if (count % 6 === 1) { + let messageCount = 0; + const logLevels = [3, 4, 5, 6, 7, 8]; // Different log levels + const logNames = ['system', 'ems', 'wifi', 'mqtt', 'ntp', 'api']; + + const sendLogMessage = () => { + const level = logLevels[messageCount % logLevels.length]; + const name = logNames[messageCount % logNames.length]; + let message = `Log message #${messageCount}`; + + // Add long message every 6th message + if (messageCount % 6 === 1) { message += - ' that is a long message that will be wrapped, to test if it gets truncated'; + ' - This is a longer message to test text wrapping and truncation behavior in the UI'; } - const data = { - t: new Date().toISOString(), - l: 3 + (count % 6), - i: count, - n: 'system', + + const logData = { + t: formatDate(new Date()), + l: level, + i: messageCount, + n: name, m: message }; - count++; - res.write(`data: ${JSON.stringify(data)}\n\n`); - }, 1000); - // if client closes connection - res.on('close', () => { - console.log('Closing ES connection'); + res.write(`data: ${JSON.stringify(logData)}\n\n`); + messageCount++; + }; + + // Send initial message + sendLogMessage(); + + // Set up interval for periodic messages + const interval = setInterval(sendLogMessage, 1000); + + // Clean up on connection close + const cleanup = () => { + console.log('SSE connection closed'); clearInterval(interval); - res.end(); - }); + if (!res.destroyed) { + res.end(); + } + }; + + res.on('close', cleanup); + res.on('error', cleanup); } else { - next(); // move on to the next middleware function in chain + next(); // Continue to next middleware } }); }