@bigdigital/kiosk-content-sdk
Version:
A Firebase-powered Content Management System SDK optimized for kiosks with offline support, template management, and real-time connection monitoring
163 lines • 5.88 kB
JavaScript
export class KioskConnection {
constructor(options) {
this.options = options;
this.ws = null;
this.isConnected = false;
this.eventHandlers = new Map();
this.retryCount = 0;
this.retryTimeout = null;
this.isShuttingDown = false;
this.maxRetries = options.maxRetries ?? 5;
this.initialRetryDelay = options.retryDelay ?? 1000;
this.debug = options.debug ?? false;
}
log(...args) {
if (this.debug && !this.isShuttingDown) {
console.log(...args);
}
}
logError(...args) {
if (this.debug && !this.isShuttingDown) {
console.error(...args);
}
}
connect() {
if (this.ws) {
this.disconnect();
}
try {
this.log('Attempting to connect to:', this.options.url);
const wsUrl = new URL(this.options.url);
wsUrl.searchParams.set('kioskId', this.options.kioskId);
this.ws = new WebSocket(wsUrl.toString());
this.ws.onopen = () => {
this.log('WebSocket connection established');
this.isConnected = true;
this.retryCount = 0;
this.options.onConnectionUpdate?.(true);
this.emit('connected');
};
this.ws.onclose = (event) => {
this.log(`WebSocket closed with code ${event.code}`);
this.handleDisconnect('Connection closed', event);
};
this.ws.onerror = (error) => {
this.logError('WebSocket error:', error);
this.handleDisconnect('Connection error', error);
};
this.ws.onmessage = (event) => {
if (event.data === 'ping') {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send('pong');
this.emit('ping');
}
}
else {
try {
const data = JSON.parse(event.data.toString());
this.emit('message', data);
}
catch (error) {
this.logError('Error parsing message:', error);
}
}
};
}
catch (error) {
this.logError('Error creating WebSocket:', error);
this.handleDisconnect('Failed to create connection', error);
}
}
handleDisconnect(reason, error) {
if (!this.isConnected && this.retryCount > 0)
return;
this.isConnected = false;
if (!this.isShuttingDown) {
this.options.onConnectionUpdate?.(false);
this.emit('disconnected', { reason, error });
}
if (this.retryTimeout) {
clearTimeout(this.retryTimeout);
this.retryTimeout = null;
}
if (!this.isShuttingDown && this.retryCount < this.maxRetries) {
const delay = this.initialRetryDelay * Math.pow(2, this.retryCount);
this.log(`Attempting to reconnect in ${delay}ms (attempt ${this.retryCount + 1}/${this.maxRetries})`);
this.retryTimeout = setTimeout(() => {
this.retryCount++;
this.connect();
}, delay);
}
else if (!this.isShuttingDown) {
this.logError('Max retry attempts reached');
this.emit('maxRetriesExceeded');
}
}
disconnect() {
return new Promise((resolve) => {
this.isShuttingDown = true;
// Clear any pending retry attempts
if (this.retryTimeout) {
clearTimeout(this.retryTimeout);
this.retryTimeout = null;
}
// If there's no WebSocket or it's already closed, cleanup and resolve immediately
if (!this.ws || this.ws.readyState === WebSocket.CLOSED) {
this.cleanup();
resolve();
return;
}
// Set up one-time close handler for cleanup and resolution
const handleClose = () => {
this.cleanup();
resolve();
};
const currentWs = this.ws;
currentWs.addEventListener('close', handleClose, { once: true });
// If the socket is open or connecting, close it
if (currentWs.readyState === WebSocket.OPEN || currentWs.readyState === WebSocket.CONNECTING) {
currentWs.close();
}
});
}
cleanup() {
if (this.ws) {
// Remove all event listeners
this.ws.onopen = null;
this.ws.onclose = null;
this.ws.onerror = null;
this.ws.onmessage = null;
}
this.ws = null;
this.isConnected = false;
this.retryCount = 0;
if (!this.isShuttingDown) {
this.options.onConnectionUpdate?.(false);
this.emit('disconnected', { reason: 'Manual disconnect' });
}
}
on(event, callback) {
if (!this.eventHandlers.has(event)) {
this.eventHandlers.set(event, new Set());
}
this.eventHandlers.get(event)?.add(callback);
return this;
}
emit(event, ...args) {
const handlers = this.eventHandlers.get(event);
if (!handlers || this.isShuttingDown)
return;
handlers.forEach(callback => {
try {
callback(...args);
}
catch (error) {
this.logError('Error in event handler:', error);
}
});
}
isConnectionOpen() {
return this.isConnected && this.ws?.readyState === WebSocket.OPEN;
}
}
//# sourceMappingURL=KioskConnection.js.map