Files
picUploadApi/wss-client.js
sebseb7 ee1955c048 u
2025-12-27 20:09:42 +01:00

157 lines
4.7 KiB
JavaScript

import WebSocket from 'ws';
/**
* A simple client for the TischlerCtrl WebSocket API.
*/
export class TischlerClient {
/**
* @param {string} url - The WebSocket server URL (e.g., 'ws://localhost:3000')
* @param {string} apiKey - Your Agent API Key
*/
constructor(url, apiKey) {
this.url = url;
this.apiKey = apiKey;
this.ws = null;
this.authenticated = false;
this.onAuthenticated = null; // Callback
this.reconnectInterval = 1000;
this.maxReconnectInterval = 30000;
this.isReconnecting = false;
}
/**
* Connect to the WebSocket server.
* @returns {Promise<void>} Resolves when connected (but not yet authenticated)
*/
connect() {
return new Promise((resolve, reject) => {
this.ws = new WebSocket(this.url);
this.ws.on('open', () => {
console.log('[Client] Connected to server.');
this.isReconnecting = false;
this.reconnectInterval = 1000; // Reset backoff
this.authenticate();
resolve();
});
this.ws.on('message', (data) => this.handleMessage(data));
this.ws.on('error', (err) => {
console.error('[Client] Connection error:', err.message);
// Reject only if this is the initial connection attempt and not a reconnection
if (!this.isReconnecting) reject(err);
});
this.ws.on('close', () => {
console.log('[Client] Disconnected.');
this.authenticated = false;
this.scheduleReconnect();
});
});
}
scheduleReconnect() {
if (this.isReconnecting) return;
this.isReconnecting = true;
console.log(`[Client] Reconnecting in ${this.reconnectInterval / 1000}s...`);
setTimeout(() => {
// Reset flag BEFORE attempting, so a failed attempt can schedule another
this.isReconnecting = false;
console.log('[Client] Attempting to reconnect...');
this.connect().catch(() => {
// Connection failed - close handler will call scheduleReconnect again
});
// Increase backoff for next time
this.reconnectInterval = Math.min(this.reconnectInterval * 1.5, this.maxReconnectInterval);
}, this.reconnectInterval);
}
/**
* Send authentication message.
*/
authenticate() {
console.log('[Client] Authenticating...');
this.send({
type: 'auth',
apiKey: this.apiKey
});
}
/**
* Send sensor readings.
* @param {Array<Object>} readings - Array of reading objects
* @example
* client.sendReadings([
* { device: 'temp-sensor-1', channel: 'temp', value: 24.5 },
* { device: 'temp-sensor-1', channel: 'config', data: { mode: 'eco' } }
* ]);
*/
sendReadings(readings) {
if (!this.authenticated) {
console.warn('[Client] Cannot send data: Not authenticated.');
return;
}
console.log(`[Client] Sending ${readings.length} readings...`);
this.send({
type: 'data',
readings: readings
});
}
/**
* Handle incoming messages.
*/
handleMessage(data) {
try {
const message = JSON.parse(data.toString());
switch (message.type) {
case 'auth':
if (message.success) {
this.authenticated = true;
console.log(`[Client] Authenticated as "${message.name}" (Prefix: ${message.devicePrefix})`);
if (this.onAuthenticated) this.onAuthenticated();
} else {
console.error('[Client] Authentication failed:', message.error);
this.ws.close();
}
break;
case 'ack':
console.log(`[Client] Server acknowledged ${message.count} readings.`);
break;
case 'error':
console.error('[Client] Server error:', message.error);
break;
default:
console.log('[Client] Received:', message);
}
} catch (err) {
console.error('[Client] Failed to parse message:', err);
}
}
/**
* Helper to send JSON object.
*/
send(obj) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(obj));
}
}
/**
* Close connection.
*/
close() {
if (this.ws) this.ws.close();
}
}