UNPKG

jsm-core

Version:
483 lines (482 loc) 23.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); var _, done = false; for (var i = decorators.length - 1; i >= 0; i--) { var context = {}; for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; for (var p in contextIn.access) context.access[p] = contextIn.access[p]; context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); if (kind === "accessor") { if (result === void 0) continue; if (result === null || typeof result !== "object") throw new TypeError("Object expected"); if (_ = accept(result.get)) descriptor.get = _; if (_ = accept(result.set)) descriptor.set = _; if (_ = accept(result.init)) initializers.unshift(_); } else if (_ = accept(result)) { if (kind === "field") initializers.unshift(_); else descriptor[key] = _; } } if (target) Object.defineProperty(target, contextIn.name, descriptor); done = true; }; var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { var useValue = arguments.length > 2; for (var i = 0; i < initializers.length; i++) { value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); } return useValue ? value : void 0; }; var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) { if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : ""; return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SocketManager = exports.formatNotificationSpace = void 0; const jsm_logger_1 = __importStar(require("jsm-logger")); const typedi_1 = __importStar(require("typedi")); /** * Format the notification space with the provided information. * @param space The notification space string. * example: "Notification for [COMPANY] - [APP] - [OFFICE] - [STORE] - [USER] - [ROLE] - [ROLE_GROUP] - [PARCEL_STATUS]" * @param infos The information to replace in the space. * @returns The formatted notification space. */ const formatNotificationSpace = (infos) => { const rooms = []; const logger = (0, jsm_logger_1.default)(jsm_logger_1.LoggerContext.UTILITY, 'formatNotificationSpace'); logger.debug('🔄 Formatting notification space', { infos, hasInfos: Object.values(infos).some(val => !!val) }); if (!Object.values(infos).reduce((prev, curr) => prev || !!curr || (Array.isArray(curr) && curr.length > 0), false)) { logger.warn('⚠️ No valid infos provided for space formatting', { infos }); return []; } Object.entries(infos).forEach(([key, value]) => { if (value) { if (Array.isArray(value)) { value.forEach(val => { if (val) { const formatted = key + ':' + val; logger.debug('🔄 Adding formatted room', { key, value: val, formatted }); rooms.push(formatted); } }); } else if (typeof value === 'string' && value.trim()) { const formatted = key + ':' + value; logger.debug('🔄 Adding formatted room', { key, value, formatted }); rooms.push(formatted); } } }); return rooms; }; exports.formatNotificationSpace = formatNotificationSpace; let SocketManager = (() => { let _classDecorators = [(0, typedi_1.Service)()]; let _classDescriptor; let _classExtraInitializers = []; let _classThis; var SocketManager = _classThis = class { /** * Creates an instance of SocketManager. */ constructor() { this.logger = (0, jsm_logger_1.default)(jsm_logger_1.LoggerContext.MANAGER, this.constructor.name); this.logger.event('🏗️ Constructing SocketManager instance'); this.status = 'disconnected'; this.clients = new Set(); this.logger.debug('🔍 Attempting to get Socket.io instance from container'); // Create HTTP server if not provided this.io = typedi_1.default.get('io'); if (this.io) { this.logger.success('✅ Socket.io instance retrieved from container'); } else { this.logger.warn('⚠️ Socket.io instance not found in container during construction'); } this.init(); } /** * Initialize socket.io server (called on app startup) */ init() { return __awaiter(this, void 0, void 0, function* () { try { this.logger.event('🚀 Initializing SocketManager...'); this.status = 'connecting'; if (!this.io) { this.logger.error('❌ Socket.io server not found in container'); throw new Error('Socket.io server not found in container. Ensure it is initialized before SocketManager.'); } this.logger.success('✅ Socket.io server found in container'); // Log all server-level middleware this.io.use((socket, next) => { this.logger.event('🔌 Socket middleware - Client attempting connection', { socketId: socket.id, handshake: { address: socket.handshake.address, headers: socket.handshake.headers, query: socket.handshake.query, auth: socket.handshake.auth, time: socket.handshake.time } }); next(); }); this.io.on('connection', (socket) => { this.logger.success(`🔌 Socket connected: ${socket.id}`, { clientIP: socket.handshake.address, userAgent: socket.handshake.headers['user-agent'], namespace: socket.nsp.name, totalClients: this.clients.size + 1 }); this.status = 'connected'; this.clients.add(socket.id); // Log all incoming messages from this socket socket.onAny((eventName, ...args) => { this.logger.event('📥 INCOMING MESSAGE', { socketId: socket.id, event: eventName, data: args, timestamp: new Date().toISOString(), dataSize: JSON.stringify(args).length }); }); // Log all outgoing messages to this socket socket.onAnyOutgoing((eventName, ...args) => { this.logger.event('📤 OUTGOING MESSAGE', { socketId: socket.id, event: eventName, data: args, timestamp: new Date().toISOString(), dataSize: JSON.stringify(args).length }); }); // Enhanced disconnect logging socket.on('disconnect', (reason) => { this.logger.event(`🔌 Socket disconnected: ${socket.id}`, { reason, duration: Date.now() - new Date(socket.handshake.time).getTime(), remainingClients: this.clients.size - 1 }); this.clients.delete(socket.id); // Update status if no clients left if (this.clients.size === 0) { this.status = 'disconnected'; this.logger.warn('⚠️ No clients connected - status changed to disconnected'); } }); // Log connection errors socket.on('error', (error) => { this.logger.error(`❌ Socket error for ${socket.id}:`, { error: error.message, stack: error.stack, socketId: socket.id }); }); // Log when client joins/leaves rooms socket.on('join-room', (data) => { var _a, _b, _c; socket.join((_a = data.payload) === null || _a === void 0 ? void 0 : _a.room_id); this.logger.event(`🏠 Socket ${socket.id} joined room`, { data, socketId: socket.id, roomSize: ((_c = this.io.sockets.adapter.rooms.get((_b = data.payload) === null || _b === void 0 ? void 0 : _b.room_id)) === null || _c === void 0 ? void 0 : _c.size) || 0 }); }); socket.on('leave-room', (data) => { var _a, _b, _c; socket.leave((_a = data.payload) === null || _a === void 0 ? void 0 : _a.room_id); this.logger.event(`🚪 Socket ${socket.id} left room`, { data, socketId: socket.id, roomSize: ((_c = this.io.sockets.adapter.rooms.get((_b = data.payload) === null || _b === void 0 ? void 0 : _b.room_id)) === null || _c === void 0 ? void 0 : _c.size) || 0 }); }); // Log ping/pong for connection health socket.on('ping', () => { this.logger.debug(`🏓 Ping from ${socket.id}`); }); socket.on('pong', (latency) => { this.logger.debug(`🏓 Pong from ${socket.id}`, { latency }); }); }); // Log engine-level events this.io.engine.on('connection_error', (err) => { this.logger.error('❌ Socket.io connection error:', { message: err.message, description: err.description, context: err.context, type: err.type, timestamp: new Date().toISOString() }); }); this.logger.success('✅ SocketManager initialized successfully with comprehensive logging'); } catch (err) { this.status = 'error'; this.logger.error('❌ SocketManager failed to initialize', { error: err instanceof Error ? err.message : 'Unknown error', stack: err instanceof Error ? err.stack : undefined }); throw err; } }); } /** * Register a client manually (not needed for socket.io, but kept for API compatibility) */ registerClient(clientId) { this.logger.event(`📝 Manually registering client: ${clientId}`, { clientId, totalClients: this.clients.size + 1, timestamp: new Date().toISOString() }); this.clients.add(clientId); } /** * Unregister a client manually */ unregisterClient(clientId) { this.logger.event(`📝 Manually unregistering client: ${clientId}`, { clientId, remainingClients: this.clients.size - 1, timestamp: new Date().toISOString() }); this.clients.delete(clientId); } /** * Emit an event to a specific socket if provided or all clients * @param event The event name to emit * @param data The data to send with the event * @param socket Optional specific socket to emit to * @returns void */ emit(event, data, socket) { if (socket) { this.logger.event(`📢 EMITTING TO SPECIFIC SOCKET`, { event, data, socketId: socket.id, timestamp: new Date().toISOString(), dataSize: JSON.stringify(data).length }); socket.emit(event, data); } else { this.logger.event(`📢 BROADCAST TO ALL CLIENTS`, { event, data, clientCount: this.clients.size, timestamp: new Date().toISOString(), dataSize: JSON.stringify(data).length }); this.io.emit(event, data); } } /** * Listen for an event from clients */ on(event, handler) { this.logger.event(`👂 Registering listener for event: ${event}`, { event, timestamp: new Date().toISOString() }); this.io.on(event, handler); } /** * Register a handler for new client connections */ onConnection(handler) { this.logger.event(`👂 Registering connection handler`, { timestamp: new Date().toISOString() }); this.io.on('connection', handler); } /** * Register event handlers for all connected and future clients */ onClientEvent(event, handler) { this.logger.event(`👂 Registering client event handler for: ${event}`, { event, timestamp: new Date().toISOString() }); // Handle for existing connections this.io.sockets.sockets.forEach(socket => { socket.on(event, (...args) => handler(socket, ...args)); }); // Handle for future connections this.io.on('connection', (socket) => { socket.on(event, (...args) => handler(socket, ...args)); }); } /** * Get current connection status */ getStatus() { this.logger.debug(`📊 Status requested: ${this.status}`, { status: this.status, clientCount: this.clients.size, timestamp: new Date().toISOString() }); return this.status; } /** * Get all connected clients */ getClients() { const clients = Array.from(this.clients); this.logger.debug(`👥 Clients list requested`, { clientCount: clients.length, clients, timestamp: new Date().toISOString() }); return clients; } /** * Notify specific rooms or all clients with contextual room formatting */ notify(event, payload, relatedTo) { return __awaiter(this, void 0, void 0, function* () { try { if (!this.io) { this.logger.error('❌ Socket.io server not loaded in notify method'); throw new Error("Socket.io server not loaded"); } event = Array.isArray(event) ? event : [event]; // Format rooms with context replacements const formattedRooms = Object.keys(relatedTo || {}).length ? (0, exports.formatNotificationSpace)(relatedTo) : []; this.logger.event('🏠 Room formatting completed', { formattedRooms, filteredCount: formattedRooms.length }); if (formattedRooms.length) { this.logger.event('📤 SOCKET EMIT TO ROOMS', { rooms: formattedRooms, event, payload, timestamp: new Date().toISOString(), payloadSize: JSON.stringify(payload).length }); for (const _event of event) { this.io.to(formattedRooms).emit(_event, Object.assign(Object.assign({}, payload), { event: _event })); // for (const room of formattedRooms) { // this.io.emit(`${room}:${_event}`, { ...payload, event: _event }) // } } } else { this.logger.event('📢 SOCKET EMIT TO ALL (no valid rooms)', { event, payload, clientCount: this.clients.size, timestamp: new Date().toISOString(), payloadSize: JSON.stringify(payload).length }); for (const _event of event) { this.io.emit(_event, Object.assign(Object.assign({}, payload), { event: _event })); } } this.logger.success('✅ Notification sent successfully', { event, rooms: formattedRooms, targetType: formattedRooms.length > 0 ? 'rooms' : 'all', targetCount: formattedRooms.length > 0 ? formattedRooms.length : this.clients.size }); } catch (error) { this.logger.error('❌ NOTIFY ERROR', { error: error instanceof Error ? error.message : 'Unknown error', stack: error instanceof Error ? error.stack : undefined, event, payload, relatedTo, timestamp: new Date().toISOString() }); throw error; } }); } /** * Get detailed connection statistics and room information */ getStats() { const rooms = Array.from(this.io.sockets.adapter.rooms.entries()).map(([name, sockets]) => ({ name, clientCount: sockets.size, clients: Array.from(sockets) })); const stats = { status: this.status, clientCount: this.clients.size, connectedClients: Array.from(this.clients), rooms, roomCount: rooms.length, timestamp: new Date().toISOString() }; this.logger.event('📊 SOCKET STATS', stats); return stats; } }; __setFunctionName(_classThis, "SocketManager"); (() => { const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0; __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers); SocketManager = _classThis = _classDescriptor.value; if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); __runInitializers(_classThis, _classExtraInitializers); })(); return SocketManager = _classThis; })(); exports.SocketManager = SocketManager;