191 lines
5.5 KiB
TypeScript
191 lines
5.5 KiB
TypeScript
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 };
|