@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
JavaScript
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;