import log4js from 'log4js'; // Load configuration import { loadConfig } from '../config.js'; import { createAuthMiddleware, KeycloakAuth } from '../auth/auth.js'; import { generateHostOTP } from '../utils.js'; import { ActivityManager } from '../activity/activitymanager.js'; import { UpdateManager } from '../updates/updatemanager.js'; import { ActionManager } from '../actions/actionmanager.js'; import { EventManager } from '../events/eventmanager.js'; import { StatsManager } from '../stats/statsmanager.js'; import { NotificationManager } from '../notification/notificationmanager.js'; import { ServerManager } from '../server/servermanager.js'; import { UserSettingsManager } from '../usersettings/usersettingsmanager.js'; const config = loadConfig(); const logger = log4js.getLogger('Socket User'); logger.level = config.server.logLevel; export class SocketUser { constructor(socket, socketManager) { this.socket = socket; this.authenticated = false; this.socketId = socket.id; this.id = null; this.user = null; this.socketManager = socketManager; this.activityManager = new ActivityManager(this); this.trackedActivities = new Set(); this.updateManager = new UpdateManager(this); this.actionManager = new ActionManager(this); this.eventManager = new EventManager(this); this.statsManager = new StatsManager(this); this.notificationManager = new NotificationManager(this); this.serverManager = new ServerManager(this); this.userSettingsManager = new UserSettingsManager(this); this.keycloakAuth = new KeycloakAuth(); this.setupSocketEventHandlers(); } setupSocketEventHandlers() { this.socket.use(createAuthMiddleware(this)); this.socket.on('authenticate', this.handleAuthenticateEvent.bind(this)); this.socket.on('setActivity', this.handleSetActivityEvent.bind(this)); this.socket.on('clearActivity', this.handleClearActivityEvent.bind(this)); this.socket.on('getActivities', this.handleGetActivitiesEvent.bind(this)); this.socket.on( 'subscribeToObjectActivity', this.handleSubscribeToObjectActivityEvent.bind(this) ); this.socket.on( 'unsubscribeObjectActivity', this.handleUnsubscribeObjectActivityEvent.bind(this) ); this.socket.on( 'subscribeToObjectTypeUpdate', this.handleSubscribeToObjectTypeUpdateEvent.bind(this) ); this.socket.on( 'subscribeToObjectUpdate', this.handleSubscribeToObjectUpdateEvent.bind(this) ); this.socket.on( 'subscribeToObjectEvent', this.handleSubscribeToObjectEventEvent.bind(this) ); this.socket.on( 'unsubscribeObjectTypeUpdate', this.handleUnsubscribeToObjectTypeUpdateEvent.bind(this) ); this.socket.on( 'unsubscribeObjectUpdate', this.handleUnsubscribeToObjectUpdateEvent.bind(this) ); this.socket.on( 'unsubscribeObjectEvent', this.handleUnsubscribeObjectEventEvent.bind(this) ); this.socket.on( 'subscribeToModelStats', this.handleSubscribeToStatsEvent.bind(this) ); this.socket.on( 'unsubscribeModelStats', this.handleUnsubscribeToStatsEvent.bind(this) ); this.socket.on( 'generateHostOtp', this.handleGenerateHostOtpEvent.bind(this) ); this.socket.on( 'getServerVersion', this.handleGetServerVersionEvent.bind(this) ); this.socket.on( 'getUserSettings', this.handleGetUserSettingsEvent.bind(this) ); this.socket.on( 'updateUserSettings', this.handleUpdateUserSettingsEvent.bind(this) ); this.socket.on('objectAction', this.handleObjectActionEvent.bind(this)); this.socket.on('disconnect', this.handleDisconnect.bind(this)); } async handleAuthenticateEvent(data, callback) { const token = data.token || undefined; logger.info('Authenticating user with token...'); if (token) { const result = await this.keycloakAuth.verifyToken(token); if (result.valid == true) { logger.info('User authenticated and valid.'); this.user = result.user; this.id = this.user._id.toString(); this.authenticated = true; await this.notificationManager.subscribe(); } else { logger.warn('User is not authenticated.'); } callback(result); } } trackActivity(type, id) { this.trackedActivities.add(`${type}|${id}`); } untrackActivity(type, id) { this.trackedActivities.delete(`${type}|${id}`); } async handleSetActivityEvent(data, callback) { const respond = result => { if (typeof callback === 'function') { callback(result); } else { this.socket.emit('setActivity_result', result); } }; if (!data || !data._id || !data.type || !data.mode) { respond({ success: false, error: 'Invalid setActivity event data' }); return; } data = { ...data, user: this.user._id.toString() }; try { const result = await this.activityManager.setActivity(data); if (result?.success === false) { respond(result); return; } this.trackActivity(data.type, data._id); respond(result); } catch (err) { logger.error('Set activity event error:', err); respond({ success: false, error: err.message }); } } async handleClearActivityEvent(data) { if (!data || !data._id || !data.type) { this.socket.emit('clearActivity_result', { success: false, error: 'Invalid clearActivity event data' }); return; } data = { ...data, user: this.user._id.toString() }; try { await this.activityManager.clearActivity(data); this.untrackActivity(data.type, data._id); } catch (err) { logger.error('Clear activity event error:', err); this.socket.emit('clearActivity_result', { success: false, error: err.message }); } } async handleGetActivitiesEvent(data, callback) { if (!data || !data._id || !data.type) { callback({ error: 'Invalid getActivities event data' }); return; } try { const activities = await this.activityManager.getObjectActivities(data); callback({ activities }); } catch (err) { logger.error('GetActivities event error:', err); callback({ error: err.message }); } } async handleSubscribeToObjectActivityEvent(data, callback) { if (!data || !data._id || !data.objectType) { if (typeof callback === 'function') { callback({ success: false, error: 'Invalid subscribeToObjectActivity event data' }); } return; } const result = await this.activityManager.subscribeToObjectActivity( data._id, data.objectType ); if (typeof callback === 'function') { callback(result); } } async handleUnsubscribeObjectActivityEvent(data) { await this.activityManager.removeObjectActivityListener( data._id, data.objectType ); } async handleSubscribeToObjectTypeUpdateEvent(data, callback) { await this.updateManager.subscribeToObjectNew(data.objectType, data.filter); await this.updateManager.subscribeToObjectDelete( data.objectType, data.filter ); if (typeof callback === 'function') { callback({ success: true }); } } async handleSubscribeToObjectUpdateEvent(data, callback) { const result = await this.updateManager.subscribeToObjectUpdate( data._id, data.objectType ); if (typeof callback === 'function') { callback(result); } } async handleSubscribeToObjectEventEvent(data) { await this.eventManager.subscribeToObjectEvent( data._id, data.objectType, data.eventType ); } async handleUnsubscribeToObjectTypeUpdateEvent(data) { await this.updateManager.removeObjectNewListener( data.objectType, data.filter ); await this.updateManager.removeObjectDeleteListener( data.objectType, data.filter ); } async handleUnsubscribeToObjectUpdateEvent(data) { await this.updateManager.removeObjectUpdateListener( data._id, data.objectType ); } async handleUnsubscribeObjectEventEvent(data) { await this.eventManager.removeObjectEventsListener( data._id, data.objectType, data.eventType ); } async handleSubscribeToStatsEvent(data) { await this.statsManager.subscribeToStats(data.objectType); } async handleUnsubscribeToStatsEvent(data) { await this.statsManager.removeStatsListener(data.objectType); } async handleGenerateHostOtpEvent(data, callback) { const result = await generateHostOTP(data._id); callback(result); } async handleGetServerVersionEvent(data, callback) { const responseCallback = typeof callback === 'function' ? callback : data; responseCallback(this.serverManager.getServerVersion()); } async handleGetUserSettingsEvent(data, callback) { const responseCallback = typeof callback === 'function' ? callback : data; try { const settings = await this.userSettingsManager.getUserSettings(); responseCallback({ success: true, settings }); } catch (error) { logger.error('Get user settings error:', error); responseCallback({ success: false, error: error.message }); } } async handleUpdateUserSettingsEvent(data, callback) { try { const settings = await this.userSettingsManager.updateUserSettings(data); if (typeof callback === 'function') { callback({ success: true, settings }); } } catch (error) { logger.error('Update user settings error:', error); if (typeof callback === 'function') { callback({ success: false, error: error.message }); } } } async handleObjectActionEvent(data, callback) { await this.actionManager.sendObjectAction( data._id, data.objectType, data.action, callback ); } async handleDisconnect() { if (this.user?._id) { const userId = this.user._id.toString(); for (const activityKey of this.trackedActivities) { const separatorIndex = activityKey.indexOf('|'); const type = activityKey.slice(0, separatorIndex); const id = activityKey.slice(separatorIndex + 1); try { await this.activityManager.clearActivity({ _id: id, type, user: userId }); } catch (err) { logger.error('Error clearing activity on disconnect:', err); } } this.trackedActivities.clear(); } await this.activityManager.removeAllListeners(); await this.actionManager.removeAllListeners(); await this.updateManager.removeAllListeners(); await this.eventManager.removeAllListeners(); await this.statsManager.removeAllListeners(); await this.notificationManager.removeAllListeners(); if (this.user || this.socket.user) { logger.info( 'External user disconnected:', this.user.username || this.socket.user?.username ); } else { logger.info('External user disconnected.'); } } }