import express from "express"; import { config } from "../config"; import { createDownloadLinkDb, deviceGroups, deviceTypes, findLatestReleaseDb, getDeviceByIdDb, getUpdateByIdDb, registerUpdateLogDb, saveDevice, } from "../data"; import { generateDeviceToken, sensorboxAuth, verifyDeviceToken } from "../auth"; const router = express.Router(); router.post("/", (req, res) => { const authorization = req.headers.authorization; const deviceId = String(req.body.deviceId || "").trim(); const type = String(req.body.type || "").trim(); const hardwareVersion = String(req.body.hardwareVersion || ""); const softwareVersion = String(req.body.softwareVersion || ""); const group = String("release") as (typeof deviceGroups)[number]; const token = authorization?.startsWith("Bearer ") ? authorization.slice(7) : String(req.body.token || ""); if (!deviceId || !type) { console.log("Missing deviceId or type in registration request"); res.status(400).send("deviceId and type are required"); return; } const deviceType = deviceTypes.find((entry) => entry.id === type); if (!deviceType) { console.log(`Unknown device type: ${type}`); res.status(400).send("Unknown device type"); return; } if (!deviceType.supportedGroups.includes(group)) { console.log(`Unsupported group: ${group}`); res.status(400).send("Unsupported group for device type"); return; } if (token !== config.sensorboxBootstrapToken) { const existingDevice = verifyDeviceToken(token); if (!existingDevice || existingDevice.id !== deviceId) { console.log("Invalid bootstrap token or device token"); res.status(401).send("Invalid bootstrap token or existing device token"); return; } console.log(`Device with ID ${deviceId} already registered, updating info`); // Regenerate token for existing device const jwtToken = generateDeviceToken(existingDevice.id); existingDevice.token = jwtToken; existingDevice.lastSeen = new Date().toISOString(); saveDevice(existingDevice); res.send(jwtToken); return; } const existingDevice = getDeviceByIdDb(deviceId); if (existingDevice) { console.log(`Device with ID ${deviceId} already exists`); res.status(409).send("Device already registered"); return; } const deviceRecord = { id: deviceId, type, group, token: "", registeredAt: new Date().toISOString(), currentVersion: softwareVersion || undefined, hardwareVersion: hardwareVersion || undefined, status: "registered", lastSeen: new Date().toISOString(), }; const jwtToken = generateDeviceToken(deviceRecord.id); deviceRecord.token = jwtToken; deviceRecord.type = type; deviceRecord.group = group; deviceRecord.currentVersion = softwareVersion || deviceRecord.currentVersion; deviceRecord.hardwareVersion = hardwareVersion || deviceRecord.hardwareVersion; deviceRecord.lastSeen = new Date().toISOString(); saveDevice(deviceRecord); // Add Content-Length header to prevent chunked encoding which can cause issues for some clients res.contentType("text/plain").send(jwtToken); }); router.get("/ota/check", sensorboxAuth, (req, res) => { const device = req.device!; const hardwareVersion = String( req.query.hardwareVersion || device.hardwareVersion || "", ); const softwareVersion = String( req.query.softwareVersion || device.currentVersion || "0.0.0", ); const group = String( req.query.group || device.group, ) as (typeof deviceGroups)[number]; if (!hardwareVersion) { res.status(400).send("hardwareVersion is required"); return; } device.lastSeen = new Date().toISOString(); device.currentVersion = softwareVersion; device.hardwareVersion = hardwareVersion; // Update the device saveDevice(device); const update = findLatestReleaseDb(device.type, group, hardwareVersion); if (!update || update.version === softwareVersion) { res.sendStatus(204); return; } const link = createDownloadLinkDb(device.id, update.id); const downloadUrl = `${config.baseUrl}/download/${link.id}`; // Get size of the update file to include in the response header const fs = require("fs"); let fileSize = 0; try { const stats = fs.statSync(update.filePath); fileSize = stats.size; } catch (err) { console.error("Error getting update file size:", err); } res .contentType("text/plain") .send( "" + update.id + "\n" + update.version + "\n" + downloadUrl + "\n" + new Date(link.expiresAt).toISOString() + "\n" + fileSize, ); }); router.post("/ota/report", sensorboxAuth, (req, res) => { const device = req.device!; const updateId = String(req.body.updateId || ""); const success = req.body.success === true; const message = String(req.body.message || ""); if (!updateId) { res.status(400).send("updateId is required"); return; } const update = getUpdateByIdDb(updateId); if (!update) { res.status(404).send("Update not found"); return; } registerUpdateLogDb(device.id, updateId, success, message); if (success) { device.currentVersion = update.version; device.hardwareVersion = update.hardwareVersion; device.status = "updated"; } else { device.status = "update_failed"; } device.lastSeen = new Date().toISOString(); res.send(true); }); router.get("/device", sensorboxAuth, (req, res) => { res.json({ device: req.device }); }); export { router as sensorboxRouter };