u
This commit is contained in:
@@ -32,8 +32,15 @@ const agentClients = new Map(); // devicePrefix -> Set<ws>
|
||||
function validateApiKey(apiKey) {
|
||||
if (!db) return null;
|
||||
try {
|
||||
const stmt = db.prepare('SELECT * FROM api_keys WHERE key = ? AND enabled = 1');
|
||||
return stmt.get(apiKey);
|
||||
const stmt = db.prepare('SELECT id, name, device_prefix FROM api_keys WHERE key = ?');
|
||||
const result = stmt.get(apiKey);
|
||||
|
||||
if (result) {
|
||||
// Update last_used_at timestamp
|
||||
db.prepare("UPDATE api_keys SET last_used_at = datetime('now') WHERE id = ?").run(result.id);
|
||||
}
|
||||
|
||||
return result || null;
|
||||
} catch (err) {
|
||||
console.error('[WS] Error validating API key:', err.message);
|
||||
return null;
|
||||
@@ -43,59 +50,77 @@ function validateApiKey(apiKey) {
|
||||
function insertReadingsSmart(devicePrefix, readings) {
|
||||
if (!db) throw new Error('Database not connected');
|
||||
|
||||
let inserted = 0;
|
||||
let updated = 0;
|
||||
const isoTimestamp = new Date().toISOString();
|
||||
|
||||
const insertStmt = db.prepare(`
|
||||
INSERT INTO sensor_events (timestamp, channel, device, value, data, data_type)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
const stmtLast = db.prepare(`
|
||||
SELECT id, value, data, data_type
|
||||
FROM sensor_events
|
||||
WHERE device = ? AND channel = ?
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 1
|
||||
`);
|
||||
|
||||
const updateUntilStmt = db.prepare(`
|
||||
const stmtUpdate = db.prepare(`
|
||||
UPDATE sensor_events SET until = ? WHERE id = ?
|
||||
`);
|
||||
|
||||
const getLastStmt = db.prepare(`
|
||||
SELECT id, value, data FROM sensor_events
|
||||
WHERE device = ? AND channel = ?
|
||||
ORDER BY timestamp DESC LIMIT 1
|
||||
const stmtInsert = db.prepare(`
|
||||
INSERT INTO sensor_events (timestamp, until, device, channel, value, data, data_type)
|
||||
VALUES (?, NULL, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const transaction = db.transaction((items) => {
|
||||
let inserted = 0;
|
||||
let updated = 0;
|
||||
|
||||
for (const reading of readings) {
|
||||
const device = `${devicePrefix}${reading.device}`;
|
||||
const channel = reading.channel;
|
||||
const value = reading.value ?? null;
|
||||
const data = reading.data ? JSON.stringify(reading.data) : null;
|
||||
const dataType = value !== null ? 'number' : 'json';
|
||||
for (const reading of items) {
|
||||
const fullDevice = `${devicePrefix}${reading.device}`;
|
||||
const channel = reading.channel;
|
||||
|
||||
// Check last value for RLE
|
||||
const last = getLastStmt.get(device, channel);
|
||||
// Determine type and values
|
||||
let dataType = 'number';
|
||||
let value = null;
|
||||
let data = null;
|
||||
|
||||
if (last) {
|
||||
const lastValue = last.value;
|
||||
const lastData = last.data;
|
||||
|
||||
// If same value, just update 'until'
|
||||
if (value !== null && lastValue === value) {
|
||||
updateUntilStmt.run(now, last.id);
|
||||
updated++;
|
||||
continue;
|
||||
if (reading.value !== undefined && reading.value !== null) {
|
||||
dataType = 'number';
|
||||
value = reading.value;
|
||||
} else if (reading.data !== undefined) {
|
||||
dataType = 'json';
|
||||
data = typeof reading.data === 'string' ? reading.data : JSON.stringify(reading.data);
|
||||
} else {
|
||||
continue; // Skip invalid
|
||||
}
|
||||
if (data !== null && lastData === data) {
|
||||
updateUntilStmt.run(now, last.id);
|
||||
|
||||
// Check last reading for RLE
|
||||
const last = stmtLast.get(fullDevice, channel);
|
||||
let isDuplicate = false;
|
||||
|
||||
if (last && last.data_type === dataType) {
|
||||
if (dataType === 'number') {
|
||||
if (Math.abs(last.value - value) < Number.EPSILON) {
|
||||
isDuplicate = true;
|
||||
}
|
||||
} else {
|
||||
// Compare JSON strings
|
||||
if (last.data === data) {
|
||||
isDuplicate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isDuplicate) {
|
||||
stmtUpdate.run(isoTimestamp, last.id);
|
||||
updated++;
|
||||
continue;
|
||||
} else {
|
||||
stmtInsert.run(isoTimestamp, fullDevice, channel, value, data, dataType);
|
||||
inserted++;
|
||||
}
|
||||
}
|
||||
return { inserted, updated };
|
||||
});
|
||||
|
||||
// Insert new reading
|
||||
insertStmt.run(now, channel, device, value, data, dataType);
|
||||
inserted++;
|
||||
}
|
||||
|
||||
return { inserted, updated };
|
||||
return transaction(readings);
|
||||
}
|
||||
|
||||
function createAgentWebSocketServer() {
|
||||
@@ -108,9 +133,17 @@ function createAgentWebSocketServer() {
|
||||
const clientState = {
|
||||
authenticated: false,
|
||||
devicePrefix: null,
|
||||
name: null
|
||||
name: null,
|
||||
lastPong: Date.now()
|
||||
};
|
||||
|
||||
// Set up ping/pong for keepalive
|
||||
ws.isAlive = true;
|
||||
ws.on('pong', () => {
|
||||
ws.isAlive = true;
|
||||
clientState.lastPong = Date.now();
|
||||
});
|
||||
|
||||
ws.on('message', (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data.toString());
|
||||
@@ -133,6 +166,22 @@ function createAgentWebSocketServer() {
|
||||
});
|
||||
});
|
||||
|
||||
// Ping interval to detect dead connections
|
||||
const pingInterval = setInterval(() => {
|
||||
wss.clients.forEach((ws) => {
|
||||
if (ws.isAlive === false) {
|
||||
console.log('[WS] Terminating unresponsive client');
|
||||
return ws.terminate();
|
||||
}
|
||||
ws.isAlive = false;
|
||||
ws.ping();
|
||||
});
|
||||
}, 30000);
|
||||
|
||||
wss.on('close', () => {
|
||||
clearInterval(pingInterval);
|
||||
});
|
||||
|
||||
console.log(`[WS] WebSocket server listening on port ${WS_PORT}`);
|
||||
return wss;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user