@loopback/socketio
Version:
LoopBack's WebSocket server based on socket.io
201 lines • 7.66 kB
JavaScript
// Copyright IBM Corp. and LoopBack contributors 2019,2020. All Rights Reserved.
// Node module: @loopback/socketio
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
Object.defineProperty(exports, "__esModule", { value: true });
exports.SocketIoServer = exports.socketIoControllers = exports.getNamespaceKeyForName = void 0;
const tslib_1 = require("tslib");
const core_1 = require("@loopback/core");
const http_server_1 = require("@loopback/http-server");
const debug_1 = tslib_1.__importDefault(require("debug"));
const lodash_1 = require("lodash");
const socket_io_1 = require("socket.io");
const decorators_1 = require("./decorators");
const keys_1 = require("./keys");
const socketio_controller_factory_1 = require("./socketio-controller-factory");
const debug = (0, debug_1.default)('loopback:socketio:server');
const getNamespaceKeyForName = (name) => `socketio.namespace.${name}`;
exports.getNamespaceKeyForName = getNamespaceKeyForName;
/**
* A binding filter to match socket.io controllers
* @param binding - Binding object
*/
const socketIoControllers = binding => {
// It has to be tagged with `controller`
if (!binding.tagNames.includes(core_1.CoreTags.CONTROLLER))
return false;
// It can be explicitly tagged with `socket.io`
if (binding.tagNames.includes(keys_1.SocketIoTags.SOCKET_IO))
return true;
// Now inspect socket.io decorations
if (binding.valueConstructor) {
const cls = binding.valueConstructor;
const classMeta = core_1.MetadataInspector.getClassMetadata(decorators_1.SOCKET_IO_METADATA, cls);
if (classMeta != null) {
debug('SocketIo metadata found at class %s', cls.name);
return true;
}
const subscribeMeta = core_1.MetadataInspector.getAllMethodMetadata(decorators_1.SOCKET_IO_SUBSCRIBE_METADATA, cls.prototype);
if (subscribeMeta != null) {
debug('SocketIo subscribe metadata found at methods of %s', cls.name);
return true;
}
const connectMeta = core_1.MetadataInspector.getAllMethodMetadata(decorators_1.SOCKET_IO_CONNECT_METADATA, cls.prototype);
if (connectMeta != null) {
debug('SocketIo connect metadata found at methods of %s', cls.name);
return true;
}
}
return false;
};
exports.socketIoControllers = socketIoControllers;
/**
* A socketio server
*/
let SocketIoServer = class SocketIoServer extends core_1.Context {
constructor(app, options = {}) {
super(app);
this.app = app;
this.options = options;
if (!options.socketIoOptions) {
this.io = new socket_io_1.Server();
}
else {
this.io = new socket_io_1.Server(options.socketIoOptions);
}
app.bind(keys_1.SocketIoBindings.IO).to(this.io);
this.controllers = this.createView(exports.socketIoControllers);
}
get listening() {
return this.httpServer ? this.httpServer.listening : false;
}
/**
* Register a sock.io middleware function
* @param fn
*/
use(fn) {
return this.io.use(fn);
}
get url() {
var _a;
return (_a = this.httpServer) === null || _a === void 0 ? void 0 : _a.url;
}
/**
* Register a socketio controller
* @param controllerClass
* @param meta
*/
route(controllerClass, meta) {
if (meta instanceof RegExp || typeof meta === 'string') {
meta = { namespace: meta };
}
if (meta == null) {
meta = (0, decorators_1.getSocketIoMetadata)(controllerClass);
}
const nsp = (meta === null || meta === void 0 ? void 0 : meta.namespace) ? this.io.of(meta.namespace) : this.io;
if (meta === null || meta === void 0 ? void 0 : meta.name) {
this.app.bind((0, exports.getNamespaceKeyForName)(meta.name)).to(nsp);
}
nsp.on('connection', async (socket) => {
await this.createSocketHandler(controllerClass)(socket);
});
return nsp;
}
/**
* Create socket handler from the controller class
* @param controllerClass
*/
createSocketHandler(controllerClass) {
return async (socket) => {
debug('SocketIo connected: id=%s namespace=%s', socket.id, socket.nsp.name);
try {
await new socketio_controller_factory_1.SocketIoControllerFactory(this, controllerClass, socket).create();
}
catch (err) {
debug('SocketIo error: error creating controller instance con connection', err);
}
};
}
/**
* Register a socket.io controller
* @param controllerClass
*/
controller(controllerClass) {
debug('Adding controller %s', controllerClass.name);
const binding = (0, core_1.createBindingFromClass)(controllerClass, {
namespace: keys_1.SocketIoBindings.CONTROLLERS_NAMESPACE,
defaultScope: core_1.BindingScope.TRANSIENT,
}).tag(keys_1.SocketIoTags.SOCKET_IO, core_1.CoreTags.CONTROLLER);
this.add(binding);
debug('Controller binding: %j', binding);
return binding;
}
/**
* Discover all socket.io controllers and register routes
*/
discoverAndRegister() {
const bindings = this.controllers.bindings;
for (const binding of bindings) {
if (binding.valueConstructor) {
debug('Controller binding found: %s %s', binding.key, binding.valueConstructor.name);
this.route(binding.valueConstructor);
}
}
}
/**
* Start the socketio server
*/
async start() {
const requestListener = this.getSync(keys_1.SocketIoBindings.REQUEST_LISTENER);
const serverOptions = resolveHttpServerConfig(this.options.httpServerOptions);
this.httpServer = new http_server_1.HttpServer(requestListener, serverOptions);
await this.httpServer.start();
this.io.attach(this.httpServer.server, this.options.socketIoOptions);
}
/**
* Stop the socketio server
*/
async stop() {
const closePromise = new Promise((resolve, _reject) => {
this.io
.close(() => {
resolve();
})
.catch(err => { });
});
await closePromise;
if (this.httpServer)
await this.httpServer.stop();
}
};
exports.SocketIoServer = SocketIoServer;
exports.SocketIoServer = SocketIoServer = tslib_1.__decorate([
tslib_1.__param(0, (0, core_1.inject)(core_1.CoreBindings.APPLICATION_INSTANCE)),
tslib_1.__param(1, (0, core_1.config)({ fromBinding: core_1.CoreBindings.APPLICATION_INSTANCE })),
tslib_1.__metadata("design:paramtypes", [core_1.Application, Object])
], SocketIoServer);
const DEFAULT_CONFIG = {
port: 3000,
cors: {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204,
maxAge: 86400,
credentials: true,
},
};
function resolveHttpServerConfig(applicationConfig) {
const result = Object.assign((0, lodash_1.cloneDeep)(DEFAULT_CONFIG), applicationConfig);
// Can't check falsiness, 0 is a valid port.
if (result.port == null) {
result.port = 3000;
}
if (result.host == null) {
// Set it to '' so that the http server will listen on all interfaces
result.host = undefined;
}
return result;
}
//# sourceMappingURL=socketio.server.js.map
;