fix: OTA
This commit is contained in:
116
index.js
116
index.js
@@ -11,6 +11,15 @@ const PORT = process.env.PORT || 3001;
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// File upload middleware
|
||||
const fileUpload = require('express-fileupload');
|
||||
app.use(fileUpload({
|
||||
limits: { fileSize: 50 * 1024 * 1024 }, // 50MB limit
|
||||
abortOnLimit: true,
|
||||
responseOnLimit: 'File size limit has been reached',
|
||||
debug: false
|
||||
}));
|
||||
|
||||
// UDP discovery configuration
|
||||
const UDP_PORT = 4210;
|
||||
const DISCOVERY_MESSAGE = 'CLUSTER_DISCOVERY';
|
||||
@@ -409,7 +418,7 @@ app.get('/api/node/status/:ip', async (req, res) => {
|
||||
});
|
||||
|
||||
// File upload endpoint for firmware updates
|
||||
app.post('/api/node/update', express.raw({ type: 'multipart/form-data', limit: '50mb' }), async (req, res) => {
|
||||
app.post('/api/node/update', async (req, res) => {
|
||||
try {
|
||||
const nodeIp = req.query.ip || req.headers['x-node-ip'];
|
||||
|
||||
@@ -420,41 +429,51 @@ app.post('/api/node/update', express.raw({ type: 'multipart/form-data', limit: '
|
||||
});
|
||||
}
|
||||
|
||||
// Parse multipart form data manually
|
||||
const boundary = req.headers['content-type']?.split('boundary=')[1];
|
||||
if (!boundary) {
|
||||
return res.status(400).json({
|
||||
error: 'Invalid content type',
|
||||
message: 'Expected multipart/form-data with boundary'
|
||||
// Check if we have a file in the request
|
||||
if (!req.files || !req.files.file) {
|
||||
console.log('File upload request received but no file found:', {
|
||||
hasFiles: !!req.files,
|
||||
fileKeys: req.files ? Object.keys(req.files) : [],
|
||||
contentType: req.headers['content-type']
|
||||
});
|
||||
}
|
||||
|
||||
// Parse the multipart data to extract the file
|
||||
const fileData = parseMultipartData(req.body, boundary);
|
||||
|
||||
if (!fileData.file) {
|
||||
return res.status(400).json({
|
||||
error: 'No file data received',
|
||||
message: 'Please select a firmware file to upload'
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Uploading firmware to node ${nodeIp}, file size: ${fileData.file.data.length} bytes, filename: ${fileData.file.filename}`);
|
||||
const uploadedFile = req.files.file;
|
||||
console.log(`File upload received:`, {
|
||||
nodeIp: nodeIp,
|
||||
filename: uploadedFile.name,
|
||||
fileSize: uploadedFile.data.length,
|
||||
mimetype: uploadedFile.mimetype,
|
||||
encoding: uploadedFile.encoding
|
||||
});
|
||||
|
||||
// Create a temporary client for the specific node
|
||||
const nodeClient = new SporeApiClient(`http://${nodeIp}`);
|
||||
console.log(`Created SPORE client for node ${nodeIp}`);
|
||||
|
||||
// Send the firmware data to the node using multipart form data
|
||||
const updateResult = await nodeClient.updateFirmware(fileData.file.data, fileData.file.filename);
|
||||
// Send the firmware data to the node
|
||||
console.log(`Starting firmware upload to SPORE device ${nodeIp}...`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Firmware uploaded successfully',
|
||||
nodeIp: nodeIp,
|
||||
fileSize: fileData.file.data.length,
|
||||
filename: fileData.file.filename,
|
||||
result: updateResult
|
||||
});
|
||||
try {
|
||||
const updateResult = await nodeClient.updateFirmware(uploadedFile.data, uploadedFile.name);
|
||||
console.log(`Firmware upload to SPORE device ${nodeIp} completed successfully:`, updateResult);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Firmware uploaded successfully',
|
||||
nodeIp: nodeIp,
|
||||
fileSize: uploadedFile.data.length,
|
||||
filename: uploadedFile.name,
|
||||
result: updateResult
|
||||
});
|
||||
} catch (uploadError) {
|
||||
console.error(`Firmware upload to SPORE device ${nodeIp} failed:`, uploadError);
|
||||
throw new Error(`SPORE device upload failed: ${uploadError.message}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error uploading firmware:', error);
|
||||
@@ -501,54 +520,7 @@ app.get('/api/health', (req, res) => {
|
||||
res.status(statusCode).json(health);
|
||||
});
|
||||
|
||||
// Helper function to parse multipart form data
|
||||
function parseMultipartData(buffer, boundary) {
|
||||
const data = {};
|
||||
const boundaryBuffer = Buffer.from(`--${boundary}`);
|
||||
const endBoundaryBuffer = Buffer.from(`--${boundary}--`);
|
||||
|
||||
let start = 0;
|
||||
let end = 0;
|
||||
|
||||
while (true) {
|
||||
// Find the start of the next part
|
||||
start = buffer.indexOf(boundaryBuffer, end);
|
||||
if (start === -1) break;
|
||||
|
||||
// Find the end of this part
|
||||
end = buffer.indexOf(boundaryBuffer, start + boundaryBuffer.length);
|
||||
if (end === -1) {
|
||||
end = buffer.indexOf(endBoundaryBuffer, start + boundaryBuffer.length);
|
||||
if (end === -1) break;
|
||||
}
|
||||
|
||||
// Extract the part content
|
||||
const partBuffer = buffer.slice(start + boundaryBuffer.length + 2, end);
|
||||
|
||||
// Parse headers and content
|
||||
const headerEnd = partBuffer.indexOf('\r\n\r\n');
|
||||
if (headerEnd === -1) continue;
|
||||
|
||||
const headers = partBuffer.slice(0, headerEnd).toString();
|
||||
const content = partBuffer.slice(headerEnd + 4);
|
||||
|
||||
// Parse the Content-Disposition header to get field name and filename
|
||||
const nameMatch = headers.match(/name="([^"]+)"/);
|
||||
const filenameMatch = headers.match(/filename="([^"]+)"/);
|
||||
|
||||
if (nameMatch) {
|
||||
const fieldName = nameMatch[1];
|
||||
const filename = filenameMatch ? filenameMatch[1] : null;
|
||||
|
||||
data[fieldName] = {
|
||||
data: content,
|
||||
filename: filename
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
// Start the server
|
||||
const server = app.listen(PORT, () => {
|
||||
|
||||
Reference in New Issue
Block a user