mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-09-13 13:44:10 +00:00
update
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
// Simulate EventSource for log messages in EMS-ESP standalone mode
|
||||
// Server-Sent Events (SSE)
|
||||
// EMS-ESP standalone mode to simulate
|
||||
// a REST server for file uploads
|
||||
// an EventSource (Server-Sent Events) for log messages in EMS-ESP standalone mode
|
||||
const ONE_SECOND_MS = 1000;
|
||||
|
||||
// padding function
|
||||
@@ -21,11 +22,113 @@ const formatDate = (date) => {
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`;
|
||||
};
|
||||
|
||||
const MOCK_REST_ORIGIN = 'http://localhost:3080';
|
||||
const UPLOAD_SIMULATED_KB_PER_SEC = 256;
|
||||
const UPLOAD_SLICE_BYTES = 16 * 1024;
|
||||
|
||||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
const readRequestThrottled = (req) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
let ended = false;
|
||||
let settled = false;
|
||||
|
||||
const settle = (settler, value) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
settler(value);
|
||||
};
|
||||
|
||||
req.on('error', (error) => settle(reject, error));
|
||||
req.on('end', () => {
|
||||
ended = true;
|
||||
void step();
|
||||
});
|
||||
|
||||
const step = async () => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const chunk = req.read(UPLOAD_SLICE_BYTES);
|
||||
if (chunk) {
|
||||
chunks.push(chunk);
|
||||
await sleep((chunk.length / (UPLOAD_SIMULATED_KB_PER_SEC * 1024)) * 1000);
|
||||
void step();
|
||||
return;
|
||||
}
|
||||
if (ended || req.readableEnded) {
|
||||
settle(resolve, Buffer.concat(chunks));
|
||||
return;
|
||||
}
|
||||
req.once('readable', () => {
|
||||
void step();
|
||||
});
|
||||
} catch (error) {
|
||||
settle(reject, error);
|
||||
}
|
||||
};
|
||||
|
||||
void step();
|
||||
});
|
||||
|
||||
const forwardThrottledUpload = async (req, res) => {
|
||||
const startedAt = Date.now();
|
||||
try {
|
||||
const body = await readRequestThrottled(req);
|
||||
console.log(
|
||||
`Upload received: ${body.length} bytes in ${((Date.now() - startedAt) / 1000).toFixed(1)}s`
|
||||
);
|
||||
const headers = {};
|
||||
if (req.headers['content-type']) {
|
||||
headers['content-type'] = req.headers['content-type'];
|
||||
}
|
||||
if (req.headers.authorization) {
|
||||
headers.authorization = req.headers.authorization;
|
||||
}
|
||||
headers['content-length'] = String(body.length);
|
||||
|
||||
const bunRes = await fetch(`${MOCK_REST_ORIGIN}${req.url}`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body
|
||||
});
|
||||
const buf = Buffer.from(await bunRes.arrayBuffer());
|
||||
res.statusCode = bunRes.status;
|
||||
bunRes.headers.forEach((value, key) => {
|
||||
if (key !== 'transfer-encoding' && key !== 'content-encoding') {
|
||||
res.setHeader(key, value);
|
||||
}
|
||||
});
|
||||
res.end(buf);
|
||||
} catch (error) {
|
||||
if (error?.code === 'ECONNRESET' || req.destroyed) {
|
||||
console.log('Upload cancelled by the client');
|
||||
} else {
|
||||
console.error('Throttled upload proxy failed', error);
|
||||
}
|
||||
if (!res.headersSent) {
|
||||
res.statusCode = 502;
|
||||
}
|
||||
if (!res.writableEnded) {
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return {
|
||||
name: 'vite:mockserver',
|
||||
name: 'vite:mockServer',
|
||||
configureServer: async (server) => {
|
||||
server.middlewares.use(async (req, res, next) => {
|
||||
if (req.url.startsWith('/rest/uploadFile') && req.method === 'POST') {
|
||||
await forwardThrottledUpload(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle Server-Sent Events (SSE) for log streaming
|
||||
if (req.url.startsWith('/es/log')) {
|
||||
// Set SSE headers
|
||||
+1
-49
@@ -242,15 +242,6 @@ let countHardwarePoll = 0; // for during an upload
|
||||
let pendingFirmwareMd5 = false; // set by a .md5 upload, cleared by the next .bin
|
||||
|
||||
const VALID_UPLOAD_EXTENSIONS = new Set(['bin', 'json', 'md5']);
|
||||
const UPLOAD_PROGRESS_LOG_STEP = 10; // log every 10%
|
||||
|
||||
// A real device takes a while to accept a firmware image, so uploads are read at
|
||||
// this rate instead of completing instantly. That keeps the WebUI progress bar on
|
||||
// screen long enough to see. Lower it to watch the progress bar for longer.
|
||||
const UPLOAD_SIMULATED_KB_PER_SEC = 2048;
|
||||
const UPLOAD_SLICE_BYTES = 64 * 1024; // granularity of the simulated transfer
|
||||
|
||||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
function parseMd5Digest(text: string): string | null {
|
||||
const token = text.trim().split(/\s+/)[0];
|
||||
@@ -260,45 +251,6 @@ function parseMd5Digest(text: string): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read the multipart body in slices at UPLOAD_SIMULATED_KB_PER_SEC, logging progress
|
||||
// as it goes, then hand the buffered bytes back as FormData so they parse normally.
|
||||
async function readUploadSlowly(request: Request): Promise<FormData> {
|
||||
const contentType = request.headers.get('content-type') || '';
|
||||
const expected = Number(request.headers.get('content-length') || '0');
|
||||
const reader = request.body?.getReader();
|
||||
const chunks: Uint8Array[] = [];
|
||||
let received = 0;
|
||||
let loggedPercentage = 0;
|
||||
|
||||
while (reader) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
chunks.push(value);
|
||||
|
||||
// A fast local client arrives in very few chunks, so pace each one slice by
|
||||
// slice to keep the reported progress gradual instead of jumping to 100%
|
||||
for (let offset = 0; offset < value.length; offset += UPLOAD_SLICE_BYTES) {
|
||||
const sliceLength = Math.min(UPLOAD_SLICE_BYTES, value.length - offset);
|
||||
await sleep((sliceLength / (UPLOAD_SIMULATED_KB_PER_SEC * 1024)) * 1000);
|
||||
received += sliceLength;
|
||||
|
||||
if (expected > 0) {
|
||||
const percentage = Math.round((received / expected) * 100);
|
||||
if (percentage >= loggedPercentage + UPLOAD_PROGRESS_LOG_STEP) {
|
||||
loggedPercentage = percentage;
|
||||
console.log(`Upload progress: ${percentage}%`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(new Blob(chunks as BlobPart[]), {
|
||||
headers: { 'content-type': contentType }
|
||||
}).formData();
|
||||
}
|
||||
|
||||
// DeviceTypes
|
||||
const enum ScheduleFlag {
|
||||
SCHEDULE_SUN = 1,
|
||||
@@ -4702,7 +4654,7 @@ router
|
||||
// SYSTEM and SETTINGS
|
||||
router
|
||||
.post(EMSESP_UPLOAD_FILE_ENDPOINT, async (request: Request) => {
|
||||
const formData = await readUploadSlowly(request);
|
||||
const formData = await request.formData();
|
||||
const uploaded = formData.get('file');
|
||||
if (!uploaded || typeof uploaded === 'string') {
|
||||
return status(400);
|
||||
|
||||
Reference in New Issue
Block a user