@zerooneit/expressive-tea
Version:
A REST API over Express and Typescript
196 lines (195 loc) • 8.71 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveProxy = void 0;
const $P = require("bluebird");
// tslint:disable-next-line:no-duplicate-imports
const express = require("express");
const fs = require("fs");
const http = require("http");
const https = require("https");
const MetaData_1 = require("../classes/MetaData");
const Settings_1 = require("../classes/Settings");
const BootLoaderExceptions_1 = require("../exceptions/BootLoaderExceptions");
const object_helper_1 = require("../helpers/object-helper");
const constants_1 = require("../libs/constants");
const WebSocket = require("ws");
const WebsocketService_1 = require("../services/WebsocketService");
/**
* Expressive Tea Application interface is the response from an started application, contains the express application
* and a node http server instance.
* @typedef {Object} ExpressiveTeaApplication
* @property {Express} application - Express Application Instance
* @property {HTTPServer} server - HTTP Server Object
* @summary Application Interface
*/
/**
* <b>Bootstrap Server Engine Class</b> is an abstract class to provide the Expressive Tea engine and bootstraps tools.
* This is containing the logic and full functionality of Expressive Tea and only can be extended.
*
* @abstract
* @class Boot
* @summary Bootstrap Engine Class
*/
class Boot {
constructor() {
/**
* Maintain a reference to Singleton instance of Settings, if settings still does not initialized it will created
* automatically when extended class create a new instance.
*
* @type {Settings}
* @public
* @summary Server Settings instance reference
*/
this.settings = new Settings_1.default();
/**
* Automatically create an Express application instance which will be user to configure over all the boot stages.
* @type {Express}
* @private
* @readonly
* @summary Express Application instance internal property.
*/
this.server = express();
this.settings.set('application', this.server);
}
/**
* Bootstrap and verify that all the required plugins are correctly configured and proceed to attach all the
* registered modules. <b>Remember</b> this is the unique method that must be decorated for the Register Module
* decorator.
* @summary Initialize and Bootstrap Server.
* @returns {Promise<ExpressiveTeaApplication>}
*/
async start() {
return new $P(async (resolver, rejector) => {
try {
const privateKey = this.settings.get('privateKey');
const certificate = this.settings.get('certificate');
const startWebsocket = this.settings.get('startWebsocket');
const detachWebsocket = this.settings.get('detachWebsocket');
const serverConfigQueue = [];
// tslint:disable-next-line:one-variable-per-declaration
let ws, wss;
const server = http.createServer(this.server);
const secureServer = privateKey && certificate && https.createServer({
cert: fs.readFileSync(certificate).toString('utf-8'),
key: fs.readFileSync(privateKey).toString('utf-8')
}, this.server);
if (startWebsocket) {
ws = new WebSocket.Server(detachWebsocket ? { noServer: true } : { server });
if (secureServer) {
wss = new WebSocket.Server(detachWebsocket ? { noServer: true } : { server: secureServer });
}
WebsocketService_1.default.init(ws, wss);
WebsocketService_1.default.getInstance().setHttpServer(server);
WebsocketService_1.default.getInstance().setHttpServer(secureServer);
}
await resolveProxyContainers(this);
await resolveDirectives(this, this.server);
await resolveStatic(this, this.server);
for (const stage of constants_1.BOOT_ORDER) {
await resolveStage(stage, this, this.server);
}
await resolveStage(constants_1.BOOT_STAGES.APPLICATION, this, this.server);
serverConfigQueue.push(new $P(resolve => server.listen(this.settings.get('port'), () => {
console.log(`Running HTTP Server on [${this.settings.get('port')}]`);
resolve();
})));
if (secureServer) {
serverConfigQueue.push(new $P(resolve => secureServer.listen(this.settings.get('securePort'), () => {
console.log(`Running Secure HTTP Server on [${this.settings.get('securePort')}]`);
resolve();
})));
}
await $P.all([
resolveStage(constants_1.BOOT_STAGES.AFTER_APPLICATION_MIDDLEWARES, this, this.server),
resolveStage(constants_1.BOOT_STAGES.ON_HTTP_CREATION, this, this.server, server, secureServer)
]);
$P.all(serverConfigQueue)
.then(() => {
return resolveStage(constants_1.BOOT_STAGES.START, this, this.server, server, secureServer);
})
.then(() => resolver({ application: this.server, server, secureServer }));
}
catch (e) {
return rejector(e);
}
});
}
}
async function resolveStage(stage, ctx, server, ...extraArgs) {
try {
await bootloaderResolve(stage, server, ctx, ...extraArgs);
if (stage === constants_1.BOOT_STAGES.APPLICATION) {
await resolveModules(ctx, server);
}
}
catch (e) {
checkIfStageFails(e);
}
}
async function resolveDirectives(instance, server) {
const registeredDirectives = MetaData_1.default.get(constants_1.REGISTERED_DIRECTIVES_KEY, (0, object_helper_1.getClass)(instance)) || [];
registeredDirectives.forEach((options) => {
server.set.call(server, options.name, ...options.settings);
});
}
async function resolveStatic(instance, server) {
const registeredStatic = MetaData_1.default.get(constants_1.REGISTERED_STATIC_KEY, (0, object_helper_1.getClass)(instance)) || [];
registeredStatic.forEach((staticOptions) => {
if (staticOptions.virtual) {
server.use(staticOptions.virtual, express.static(staticOptions.root, staticOptions.options));
}
else {
server.use(express.static(staticOptions.root, staticOptions.options));
}
});
}
async function resolveModules(instance, server) {
const registeredModules = MetaData_1.default.get(constants_1.REGISTERED_MODULE_KEY, instance, 'start') || [];
registeredModules.forEach(Module => {
const moduleInstance = new Module();
moduleInstance.__register(server);
});
}
async function bootloaderResolve(STAGE, server, instance, ...args) {
const bootLoader = MetaData_1.default.get(constants_1.BOOT_STAGES_KEY, (0, object_helper_1.getClass)(instance)) || constants_1.STAGES_INIT;
for (const loader of bootLoader[STAGE] || []) {
try {
await selectLoaderType(loader, server, ...args);
}
catch (e) {
shouldFailIfRequire(e, loader);
}
}
}
async function resolveProxyContainers(context) {
const ProxyContainers = MetaData_1.default.get(constants_1.ROUTER_PROXIES_KEY, (0, object_helper_1.getClass)(context)) || [];
for (const Container of ProxyContainers) {
resolveProxy(Container, context.server);
}
}
async function resolveProxy(ProxyContainer, server) {
const proxyContainer = new ProxyContainer();
proxyContainer.__register(server);
}
exports.resolveProxy = resolveProxy;
async function selectLoaderType(loader, server, ...args) {
return loader.method(server, ...args);
}
function checkIfStageFails(e) {
if (e instanceof BootLoaderExceptions_1.BootLoaderSoftExceptions) {
console.info(e.message);
}
else {
console.error(e.message);
// Re Throwing Error to Get it a top level.
throw e;
}
}
function shouldFailIfRequire(e, loader) {
const failMessage = `Failed [${loader.name}]: ${e.message}`;
if (!loader || loader.required) {
throw new BootLoaderExceptions_1.BootLoaderRequiredExceptions(failMessage);
}
throw new BootLoaderExceptions_1.BootLoaderSoftExceptions(`${failMessage} and will be not enabled`);
}
exports.default = Boot;
;