Refactor socket management: replace SocketClient with SocketUser and SocketHost classes
- Removed SocketClient class and its associated functionality. - Introduced SocketUser and SocketHost classes to handle user and host socket connections respectively. - Updated SocketManager to manage user and host connections, including authentication and event handling. - Enhanced event handling for user actions such as locking, unlocking, and subscribing to updates.
This commit is contained in:
parent
03eb0a61c1
commit
3424c17ab3
@ -1,128 +0,0 @@
|
||||
import log4js from 'log4js';
|
||||
// Load configuration
|
||||
import { loadConfig } from '../config.js';
|
||||
import { userModel } from '../database/user.schema.js';
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
const logger = log4js.getLogger('Socket Client');
|
||||
logger.level = config.server.logLevel;
|
||||
|
||||
export class SocketClient {
|
||||
constructor(socket, socketManager) {
|
||||
this.socket = socket;
|
||||
this.user = null;
|
||||
this.socketManager = socketManager;
|
||||
this.lockManager = socketManager.lockManager;
|
||||
this.updateManager = socketManager.updateManager;
|
||||
}
|
||||
|
||||
async initUser() {
|
||||
if (this.socket?.user?.username) {
|
||||
try {
|
||||
const userDoc = await userModel
|
||||
.findOne({ username: this.socket.user.username })
|
||||
.lean();
|
||||
this.user = userDoc;
|
||||
logger.debug('ID:', this.user._id.toString());
|
||||
logger.debug('Name:', this.user.name);
|
||||
logger.debug('Username:', this.user.username);
|
||||
logger.debug('Email:', this.user.email);
|
||||
this.setupSocketEventHandlers();
|
||||
} catch (err) {
|
||||
logger.error('Error looking up user by username:', err);
|
||||
this.user = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupSocketEventHandlers() {
|
||||
this.socket.on('lock', this.handleLockEvent.bind(this));
|
||||
this.socket.on('unlock', this.handleUnlockEvent.bind(this));
|
||||
this.socket.on('getLock', this.handleGetLockEvent.bind(this));
|
||||
this.socket.on('update', this.handleUpdateEvent.bind(this));
|
||||
}
|
||||
|
||||
async handleLockEvent(data) {
|
||||
// data: { _id: string, params?: object }
|
||||
if (!data || !data._id) {
|
||||
this.socket.emit('lock_result', {
|
||||
success: false,
|
||||
error: 'Invalid lock event data'
|
||||
});
|
||||
return;
|
||||
}
|
||||
data = { ...data, user: this.user._id.toString() };
|
||||
try {
|
||||
await this.lockManager.lockObject(data);
|
||||
} catch (err) {
|
||||
logger.error('Lock event error:', err);
|
||||
this.socket.emit('lock_result', { success: false, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
async handleUnlockEvent(data) {
|
||||
// data: { _id: string }
|
||||
if (!data || !data._id) {
|
||||
this.socket.emit('unlock_result', {
|
||||
success: false,
|
||||
error: 'Invalid unlock event data'
|
||||
});
|
||||
return;
|
||||
}
|
||||
data = { ...data, user: this.user._id.toString() };
|
||||
try {
|
||||
await this.lockManager.unlockObject(data);
|
||||
} catch (err) {
|
||||
logger.error('Unlock event error:', err);
|
||||
this.socket.emit('unlock_result', { success: false, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
async handleGetLockEvent(data, callback) {
|
||||
// data: { _id: string }
|
||||
if (!data || !data._id) {
|
||||
callback({
|
||||
error: 'Invalid getLock event data'
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const lockEvent = await this.lockManager.getObjectLock(data);
|
||||
callback(lockEvent);
|
||||
} catch (err) {
|
||||
logger.error('GetLock event error:', err);
|
||||
callback({
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async handleUpdateEvent(data) {
|
||||
// data: { _id: string, type: string, ...otherProperties }
|
||||
if (!data || !data._id || !data.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Add user information to the update data
|
||||
const updateData = {
|
||||
...data,
|
||||
updatedAt: new Date()
|
||||
};
|
||||
|
||||
// Use the updateManager to handle the update
|
||||
if (this.updateManager) {
|
||||
await this.updateManager.updateObject(updateData);
|
||||
} else {
|
||||
throw new Error('UpdateManager not available');
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Update event error:', err);
|
||||
}
|
||||
}
|
||||
|
||||
handleDisconnect() {
|
||||
logger.info('External client disconnected:', this.socket.user?.username);
|
||||
}
|
||||
}
|
||||
117
src/socket/sockethost.js
Normal file
117
src/socket/sockethost.js
Normal file
@ -0,0 +1,117 @@
|
||||
import log4js from 'log4js';
|
||||
// Load configuration
|
||||
import { loadConfig } from '../config.js';
|
||||
import { CodeAuth, createAuthMiddleware } from '../auth/auth.js';
|
||||
import { editObject, getObject } from '../database/database.js';
|
||||
import { hostModel } from '../database/schemas/management/host.schema.js';
|
||||
import { UpdateManager } from '../updates/updatemanager.js';
|
||||
import { ActionManager } from '../actions/actionmanager.js';
|
||||
import { getModelByName } from '../utils.js';
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
const logger = log4js.getLogger('Socket Host');
|
||||
logger.level = config.server.logLevel;
|
||||
|
||||
export class SocketHost {
|
||||
constructor(socket, socketManager) {
|
||||
this.socket = socket;
|
||||
this.authenticated = false;
|
||||
this.socketId = socket.id;
|
||||
this.id = null;
|
||||
this.host = null;
|
||||
this.socketManager = socketManager;
|
||||
this.updateManager = new UpdateManager(this);
|
||||
this.actionManager = new ActionManager(this);
|
||||
this.codeAuth = new CodeAuth();
|
||||
this.setupSocketEventHandlers();
|
||||
}
|
||||
|
||||
setupSocketEventHandlers() {
|
||||
this.socket.use(createAuthMiddleware(this));
|
||||
this.socket.on('authenticate', this.handleAuthenticate.bind(this));
|
||||
this.socket.on('updateHost', this.handleUpdateHost.bind(this));
|
||||
this.socket.on('getObject', this.handleGetObject.bind(this));
|
||||
this.socket.on('disconnect', this.handleDisconnect.bind(this));
|
||||
}
|
||||
|
||||
async initializeHost() {
|
||||
this.actionManager.subscribeToObjectActions(this.id, 'host');
|
||||
}
|
||||
|
||||
async handleAuthenticate(data, callback) {
|
||||
logger.trace('handleAuthenticateEvent');
|
||||
const id = data.id || undefined;
|
||||
const authCode = data.authCode || undefined;
|
||||
const otp = data.otp || undefined;
|
||||
|
||||
if (id && authCode) {
|
||||
logger.info('Authenticating host with id + authCode...');
|
||||
const verifyResult = await this.codeAuth.verifyCode(id, authCode);
|
||||
if (verifyResult.valid == true) {
|
||||
logger.info('Host authenticated and valid.');
|
||||
this.host = verifyResult.host;
|
||||
this.id = this.host._id.toString();
|
||||
this.authenticated = true;
|
||||
await editObject({
|
||||
model: hostModel,
|
||||
id: this.host._id,
|
||||
updateData: { online: true, state: { type: 'online' } },
|
||||
owner: this.host,
|
||||
ownerType: 'host'
|
||||
});
|
||||
await this.initializeHost();
|
||||
}
|
||||
callback(verifyResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (otp) {
|
||||
logger.info('Authenticating host otp...');
|
||||
const verifyResult = await this.codeAuth.verifyOtp(otp);
|
||||
if (verifyResult.valid == true) {
|
||||
logger.info('Host authenticated and valid.');
|
||||
this.host = verifyResult.host;
|
||||
this.authenticated = true;
|
||||
}
|
||||
callback(verifyResult);
|
||||
return;
|
||||
}
|
||||
|
||||
callback({ valid: false, error: 'Missing params.' });
|
||||
}
|
||||
|
||||
async handleUpdateHost(data) {
|
||||
await editObject({
|
||||
model: hostModel,
|
||||
id: this.host._id,
|
||||
updateData: { ...data.host },
|
||||
owner: this.host,
|
||||
ownerType: 'host'
|
||||
});
|
||||
}
|
||||
|
||||
async handleGetObject(data, callback) {
|
||||
const object = await getObject({
|
||||
model: getModelByName(data.objectType),
|
||||
id: data._id,
|
||||
cached: true,
|
||||
populate: data.populate
|
||||
});
|
||||
callback(object);
|
||||
}
|
||||
|
||||
async handleDisconnect() {
|
||||
if (this.authenticated) {
|
||||
await editObject({
|
||||
model: hostModel,
|
||||
id: this.host._id,
|
||||
updateData: { online: false, state: { type: 'offline' } },
|
||||
owner: this.host,
|
||||
ownerType: 'host'
|
||||
});
|
||||
this.authenticated = false;
|
||||
}
|
||||
logger.info('External host disconnected. Socket ID:', this.id);
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,13 @@
|
||||
// server.js - HTTP and Socket.IO server setup
|
||||
import { Server } from 'socket.io';
|
||||
import { createAuthMiddleware } from '../auth/auth.js';
|
||||
import log4js from 'log4js';
|
||||
// Load configuration
|
||||
import { loadConfig } from '../config.js';
|
||||
import { SocketClient } from './socketclient.js';
|
||||
import { SocketUser } from './socketuser.js';
|
||||
import { LockManager } from '../lock/lockmanager.js';
|
||||
import { UpdateManager } from '../updates/updatemanager.js';
|
||||
import { TemplateManager } from '../templates/templatemanager.js';
|
||||
import { SocketHost } from './sockethost.js';
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
@ -14,10 +15,10 @@ const logger = log4js.getLogger('Socket Manager');
|
||||
logger.level = config.server.logLevel;
|
||||
|
||||
export class SocketManager {
|
||||
constructor(auth, server) {
|
||||
this.socketClientConnections = new Map();
|
||||
this.lockManager = new LockManager(this);
|
||||
this.updateManager = new UpdateManager(this);
|
||||
constructor(server) {
|
||||
this.socketUsers = new Map();
|
||||
this.socketHosts = new Map();
|
||||
this.templateManager = new TemplateManager(this);
|
||||
|
||||
// Use the provided HTTP server
|
||||
// Create Socket.IO server
|
||||
@ -28,55 +29,91 @@ export class SocketManager {
|
||||
}
|
||||
});
|
||||
|
||||
// Apply authentication middleware
|
||||
io.use(createAuthMiddleware(auth));
|
||||
|
||||
// Handle client connections
|
||||
// Handle user connections
|
||||
io.on('connection', async socket => {
|
||||
logger.info('External client connected:', socket.user?.username);
|
||||
await this.addClient(socket);
|
||||
const authType = socket.handshake?.auth?.type;
|
||||
if (authType == 'user') {
|
||||
await this.addUser(socket);
|
||||
} else if (authType == 'host') {
|
||||
await this.addHost(socket);
|
||||
}
|
||||
});
|
||||
|
||||
this.io = io;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
async addClient(socket) {
|
||||
const client = new SocketClient(socket, this, this.lockManager);
|
||||
await client.initUser();
|
||||
this.socketClientConnections.set(socket.id, client);
|
||||
logger.info('External client connected:', socket.user?.username);
|
||||
async addUser(socket) {
|
||||
const socketUser = new SocketUser(socket, this, this.lockManager);
|
||||
this.socketUsers.set(socketUser.id, socketUser);
|
||||
logger.info('External user connected. Socket ID:', socket.id);
|
||||
// Handle disconnection
|
||||
socket.on('disconnect', () => {
|
||||
logger.info('External client disconnected:', socket.user?.username);
|
||||
this.removeClient(socket.id);
|
||||
logger.info('External user disconnected. Socket ID:', socket.id);
|
||||
this.removeUser(socket.id);
|
||||
});
|
||||
}
|
||||
|
||||
removeClient(socketClientId) {
|
||||
const socketClient = this.socketClientConnections.get(socketClientId);
|
||||
if (socketClient) {
|
||||
this.socketClientConnections.delete(socketClientId);
|
||||
logger.info(
|
||||
'External client disconnected:',
|
||||
socketClient.socket.user?.username
|
||||
);
|
||||
async addHost(socket) {
|
||||
const socketHost = new SocketHost(socket, this, this.lockManager);
|
||||
this.socketHosts.set(socketHost.id, socketHost);
|
||||
logger.info('External host connected. Socket ID:', socket.id);
|
||||
// Handle disconnection
|
||||
socket.on('disconnect', () => {
|
||||
logger.info('External host disconnected. Socket ID:', socket.id);
|
||||
this.removeHost(socket.id);
|
||||
});
|
||||
}
|
||||
|
||||
removeUser(id) {
|
||||
const socketUser = this.socketUsers.get(id);
|
||||
if (socketUser) {
|
||||
this.socketUsers.delete(id);
|
||||
logger.info('External user disconnected. Socket ID:', id);
|
||||
}
|
||||
}
|
||||
|
||||
getSocketClient(clientId) {
|
||||
return this.socketClientConnections.get(clientId);
|
||||
removeHost(id) {
|
||||
const socketHost = this.socketHosts.get(id);
|
||||
if (socketHost) {
|
||||
this.socketHosts.delete(id);
|
||||
logger.info('External host disconnected. Socket ID:', id);
|
||||
}
|
||||
}
|
||||
|
||||
getAllSocketClients() {
|
||||
return Array.from(this.socketClientConnections.values());
|
||||
getSocketUser(userId) {
|
||||
return this.socketUserConnections.get(userId);
|
||||
}
|
||||
|
||||
broadcast(event, data, excludeClientId = null) {
|
||||
for (const [clientId, socketClient] of this.socketClientConnections) {
|
||||
if (excludeClientId !== clientId) {
|
||||
socketClient.socket.emit(event, data);
|
||||
getAllSocketUsers() {
|
||||
return Array.from(this.socketUserConnections.values());
|
||||
}
|
||||
|
||||
broadcast(event, data, excludeUserId = null) {
|
||||
for (const [userId, socketUser] of this.socketUserConnections) {
|
||||
if (excludeUserId !== userId) {
|
||||
socketUser.socket.emit(event, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to a specific user by their user ID
|
||||
* @param {string} userId - The user ID to send the message to
|
||||
* @param {string} event - The event name
|
||||
* @param {any} data - The data to send
|
||||
*/
|
||||
sendToUser(id, event, data) {
|
||||
let sentCount = 0;
|
||||
for (const [, socketUser] of this.socketUsers) {
|
||||
if (socketUser.user && socketUser.user._id.toString() === id) {
|
||||
socketUser.socket.emit(event, data);
|
||||
sentCount += 1;
|
||||
logger.debug(
|
||||
`Sent ${event} to user: ${id}, connection: ${socketUser.socket.id}`
|
||||
);
|
||||
}
|
||||
}
|
||||
logger.debug(`Sent to ${sentCount} active connection(s).`);
|
||||
}
|
||||
}
|
||||
|
||||
168
src/socket/socketuser.js
Normal file
168
src/socket/socketuser.js
Normal file
@ -0,0 +1,168 @@
|
||||
import log4js from 'log4js';
|
||||
// Load configuration
|
||||
import { loadConfig } from '../config.js';
|
||||
import { createAuthMiddleware, KeycloakAuth } from '../auth/auth.js';
|
||||
import { generateHostOTP } from '../utils.js';
|
||||
import { LockManager } from '../lock/lockmanager.js';
|
||||
import { UpdateManager } from '../updates/updatemanager.js';
|
||||
import { ActionManager } from '../actions/actionmanager.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.lockManager = new LockManager(this);
|
||||
this.updateManager = new UpdateManager(this);
|
||||
this.actionManager = new ActionManager(this);
|
||||
this.templateManager = socketManager.templateManager;
|
||||
this.keycloakAuth = new KeycloakAuth();
|
||||
this.setupSocketEventHandlers();
|
||||
}
|
||||
|
||||
setupSocketEventHandlers() {
|
||||
this.socket.use(createAuthMiddleware(this));
|
||||
this.socket.on('authenticate', this.handleAuthenticateEvent.bind(this));
|
||||
this.socket.on('lock', this.handleLockEvent.bind(this));
|
||||
this.socket.on('unlock', this.handleUnlockEvent.bind(this));
|
||||
this.socket.on('getLock', this.handleGetLockEvent.bind(this));
|
||||
this.socket.on(
|
||||
'subscribeToObjectTypeUpdate',
|
||||
this.handleSubscribeToObjectTypeUpdateEvent.bind(this)
|
||||
);
|
||||
this.socket.on(
|
||||
'subscribeToObjectUpdate',
|
||||
this.handleSubscribeToObjectUpdateEvent.bind(this)
|
||||
);
|
||||
this.socket.on(
|
||||
'previewTemplate',
|
||||
this.handlePreviewTemplateEvent.bind(this)
|
||||
);
|
||||
this.socket.on(
|
||||
'generateHostOtp',
|
||||
this.handleGenerateHostOtpEvent.bind(this)
|
||||
);
|
||||
this.socket.on('objectAction', this.handleObjectActionEvent.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;
|
||||
} else {
|
||||
logger.warn('User is not authenticated.');
|
||||
}
|
||||
callback(result);
|
||||
}
|
||||
}
|
||||
|
||||
async handleLockEvent(data) {
|
||||
// data: { _id: string, params?: object }
|
||||
if (!data || !data._id) {
|
||||
this.socket.emit('lock_result', {
|
||||
success: false,
|
||||
error: 'Invalid lock event data'
|
||||
});
|
||||
return;
|
||||
}
|
||||
data = { ...data, user: this.user._id.toString() };
|
||||
try {
|
||||
await this.lockManager.lockObject(data);
|
||||
} catch (err) {
|
||||
logger.error('Lock event error:', err);
|
||||
this.socket.emit('lock_result', { success: false, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
async handleUnlockEvent(data) {
|
||||
// data: { _id: string }
|
||||
if (!data || !data._id) {
|
||||
this.socket.emit('unlock_result', {
|
||||
success: false,
|
||||
error: 'Invalid unlock event data'
|
||||
});
|
||||
return;
|
||||
}
|
||||
data = { ...data, user: this.user._id.toString() };
|
||||
try {
|
||||
await this.lockManager.unlockObject(data);
|
||||
} catch (err) {
|
||||
logger.error('Unlock event error:', err);
|
||||
this.socket.emit('unlock_result', { success: false, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
async handleGetLockEvent(data, callback) {
|
||||
// data: { _id: string }
|
||||
if (!data || !data._id) {
|
||||
callback({
|
||||
error: 'Invalid getLock event data'
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const lockEvent = await this.lockManager.getObjectLock(data);
|
||||
callback(lockEvent);
|
||||
} catch (err) {
|
||||
logger.error('GetLock event error:', err);
|
||||
callback({
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async handleSubscribeToObjectTypeUpdateEvent(data, callback) {
|
||||
const result = this.updateManager.subscribeToObjectNew(data.objectType);
|
||||
callback(result);
|
||||
}
|
||||
|
||||
async handleSubscribeToObjectUpdateEvent(data, callback) {
|
||||
const result = this.updateManager.subscribeToObjectUpdate(
|
||||
data._id,
|
||||
data.objectType
|
||||
);
|
||||
callback(result);
|
||||
}
|
||||
|
||||
async handlePreviewTemplateEvent(data, callback) {
|
||||
const result = await this.templateManager.renderTemplate(
|
||||
data._id,
|
||||
data.content,
|
||||
data.testObject,
|
||||
data.scale
|
||||
);
|
||||
callback(result);
|
||||
}
|
||||
|
||||
async handleGenerateHostOtpEvent(data, callback) {
|
||||
const result = await generateHostOTP(data._id);
|
||||
callback(result);
|
||||
}
|
||||
|
||||
async handleObjectActionEvent(data, callback) {
|
||||
await this.actionManager.sendObjectAction(
|
||||
data._id,
|
||||
data.objectType,
|
||||
data.action,
|
||||
callback
|
||||
);
|
||||
}
|
||||
|
||||
handleDisconnect() {
|
||||
logger.info('External user disconnected:', this.socket.user?.username);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user