UNPKG

@sodacore/ws

Version:

Sodacore ws is a core plugin that extends the Http plugin offering WebSocket support to the Sodacore framework.

173 lines (172 loc) 7.88 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; import { HttpService } from '@sodacore/http'; import { Autoload, BaseService, Service, Utils as CoreUtils, Events } from '@sodacore/core'; import { Inject, Utils } from '@sodacore/di'; import { Registry } from '@sodacore/registry'; import WsConnections from '../provider/ws-connections'; import WsContext from '../context/ws'; let WsService = class WsService extends BaseService { constructor() { super(...arguments); this.controllers = new Map(); this.middlewares = []; } async init() { // Notify console. this.logger.info('[PLUGIN/WS]: Attaching WebSocket listeners to the HTTP service.'); // Let's attach some listeners. this.httpService.addListener('open', this.handleOpen.bind(this)); this.httpService.addListener('close', this.handleClose.bind(this)); this.httpService.addListener('drain', this.handleDrain.bind(this)); this.httpService.addListener('message', this.handleMessage.bind(this)); // Let's collect our controllers. const modules = Registry.all(); for (const module of modules) { // Define the variables const type = Utils.getMeta('type', 'autowire')(module.constructor); const services = Utils.getMeta('services', 'controller')(module.constructor, undefined, []); // Check for valid type and service it is for. if (!type || !services.includes('ws')) continue; // If a middleware type. if (type === 'middleware') { this.middlewares.push(module); continue; } // If a controller type. if (type === 'controller') { // Define the namespace. const namespace = Utils.getMeta('namespace', 'ws')(module.constructor); const exposedMethods = Utils.getMeta('exposed', 'ws')(module.constructor, undefined, []); // Loop the exposed methods. for (const methodName of exposedMethods) { // Define the command. const command = `${namespace}:${methodName}`; // Check for a duplicate. if (this.controllers.has(command)) { this.logger.warn(`[PLUGIN/WS]: Duplicate command found: "${command}".`); continue; } // Check for a namespace. this.controllers.set(command, module[methodName].bind(module)); } } } // Notify console. this.logger.info(`[PLUGIN/WS]: Registered ${this.controllers.size} controller(s).`); // Setup the config items. const configKeys = Object.keys(this.config).filter(key => !['port', 'path'].includes(key)); configKeys.forEach(key => this.httpService.setConfig(key, this.config[key])); } async handleOpen(socket) { // Create a new WS context. const context = new WsContext(socket); const id = context.getId(); const remoteAddress = context.getRemoteAddress(); socket.data.uniqueId = id; // Notify console. this.logger.info(`[PLUGIN/WS]: New WebSocket connection from "${remoteAddress}" with ID: "${id}".`); // Add the connection. this.connections.addConnection(id, context); // Let's dispatch an event. this.events.dispatch('wsOpen', { id, remoteAddress, connection: context, }, 'ws'); } async handleClose(socket, code, reason) { // Let's get the ID and connection. const id = socket.data.uniqueId; const connection = this.connections.getConnection(id); if (!connection) throw new Error(`Connection not found for ID: ${id}`); // Let's remove the context. this.connections.removeConnection(id); // Notify console. this.logger.info(`[PLUGIN/WS]: WebSocket connection with ID: "${id}" closed with code: "${code}" and reason: "${reason}".`); // Let's dispatch an event. this.events.dispatch('wsClose', { id }, 'ws'); } async handleDrain(socket) { // Let's get the ID and connection. const id = socket.data.uniqueId; const connection = this.connections.getConnection(id); if (!connection) throw new Error(`Connection not found for ID: ${id}`); // Notify console. this.logger.info(`[PLUGIN/WS]: WebSocket connection with ID: "${id}" triggered a drain.`); // Let's dispatch an event. this.events.dispatch('wsDrain', { id, connection }, 'ws'); } async handleMessage(socket, message) { console.log('WsService::handleMessage', typeof socket, message); // Let's get the message to a string. const content = message.toString(); if (!CoreUtils.isJson(content)) { this.logger.warn(`[PLUGIN/WS]: Invalid JSON message received: "${content}", by connection: "${socket.data.uniqueId}".`); return; } // Get the connection. const connection = this.connections.getConnection(socket.data.uniqueId); if (!connection) { this.logger.warn(`[PLUGIN/WS]: Connection not found for ID: "${socket.data.uniqueId}".`); return; } // Let's parse the message. const { command, data } = JSON.parse(content); // Let's check for a valid command. if (!command || !this.controllers.has(command)) { this.logger.warn(`[PLUGIN/WS]: Invalid command received: "${command}", by connection: "${socket.data.uniqueId}".`); return; } // Execute the method.` const method = this.controllers.get(command); if (!method) throw new Error(`Method not found for command: "${command}" - this looks like a bug.`); // Add the data to the connection. connection.setData(data); // Execute the method. const result = await method(connection); // If no result, do nothing. if (typeof result === 'undefined' || result === null) return; // If result is of type number or string. if (['number', 'string', 'boolean'].includes(typeof result)) { return connection.send(command, { result: result }); } // If result is an object. if (typeof result === 'object') { return connection.send(command, result); } } }; __decorate([ Inject('@ws:config'), __metadata("design:type", Object) ], WsService.prototype, "config", void 0); __decorate([ Inject(), __metadata("design:type", HttpService) ], WsService.prototype, "httpService", void 0); __decorate([ Inject(), __metadata("design:type", WsConnections) ], WsService.prototype, "connections", void 0); __decorate([ Inject(), __metadata("design:type", Events) ], WsService.prototype, "events", void 0); WsService = __decorate([ Autoload(25), Service() ], WsService); export default WsService;