@intuitionrobotics/thunderstorm
Version:
186 lines • 7.14 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HeaderKey = exports.RouteResolver = exports.HttpServer_Class = void 0;
const compression = require("compression");
const cors = require("cors");
const fs = require("fs");
const ts_common_1 = require("@intuitionrobotics/ts-common");
const server_api_1 = require("./server-api");
const exceptions_1 = require("../../exceptions");
const express = require("express");
const _imports_1 = require("../_imports");
const DefaultHeaders = [
'content-type',
'content-encoding',
'x-session-id',
'authorization'
];
const ExposedHeaders = [
_imports_1.HeaderKey_FunctionExecutionId,
_imports_1.HeaderKey_JWT
];
class HttpServer_Class extends ts_common_1.Module {
constructor(_express, configElement) {
super("HttpServer");
this.express = _express;
this.setConfig(configElement);
}
static addMiddleware(middleware) {
HttpServer_Class.expressMiddleware.push(middleware);
return this;
}
getRoutes() {
return this.routes;
}
getBaseUrl() {
return this.config.baseUrl;
}
init() {
this.setMinLevel(server_api_1.ServerApi.isDebug ? ts_common_1.LogLevel.Verbose : ts_common_1.LogLevel.Info);
const baseUrl = this.config.baseUrl;
if (baseUrl) {
if (baseUrl.endsWith("/"))
this.config.baseUrl = baseUrl.substring(0, baseUrl.length - 1);
this.config.baseUrl = baseUrl.replace(/\/\//g, "/");
}
this.express.use((req, res, next) => {
if (req)
req.url = req.url.replace(/\/\//g, "/");
next();
});
const parserLimit = this.config.bodyParserLimit;
if (parserLimit)
this.express.use(express.json({ limit: parserLimit }));
this.express.use(compression());
const myCors = this.config.cors || {};
if (!myCors.exposedHeaders)
myCors.exposedHeaders = ExposedHeaders;
myCors.headers = DefaultHeaders.reduce((toRet, item) => {
if (!toRet.includes(item))
(0, ts_common_1.addItemToArray)(toRet, item);
return toRet;
}, myCors.headers || []);
const corsOptions = {
origin: (requestOrigin, callback) => callback(null, myCors.origins),
optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
exposedHeaders: myCors.exposedHeaders
};
const corsWithOptions = cors(corsOptions);
this.express.use(corsWithOptions);
for (const middleware of HttpServer_Class.expressMiddleware) {
this.express.use(middleware);
}
this.express.options('*', corsWithOptions);
}
printRoutes(prefix) {
if (!server_api_1.ServerApi.isDebug)
return;
const label = `Printing ${this.routes.length} Routes`;
console.time(label);
this.routes.forEach(route => this.logVerbose(`${JSON.stringify(route.methods)} ${prefix}${route.path}`));
console.timeEnd(label);
}
resolveApi(routeResolver, urlPrefix, apis) {
console.time('Resolving Apis');
const baseUrl = this.getBaseUrl();
// Load from those passed by init
routeResolver.routeApis(apis, urlPrefix, baseUrl, this.express);
// Load from folder structure recursively
routeResolver.resolveApi(urlPrefix, baseUrl, this.express);
const resolveRoutes = (stack) => {
return stack.map((layer) => {
if (layer.route && typeof layer.route.path === "string") {
let methods = Object.keys(layer.route.methods);
if (methods.length > 20)
methods = ["ALL"];
return { methods: methods, path: layer.route.path };
}
if (layer.name === 'router')
return resolveRoutes(layer.handle.stack);
}).filter(route => route);
};
const routes = resolveRoutes(this.express._router.stack);
this.routes = routes.reduce((toRet, route) => {
const toAdd = Array.isArray(route) ? route : [route];
(0, ts_common_1.addAllItemToArray)(toRet, toAdd);
return toRet;
}, []);
console.timeEnd('Resolving Apis');
}
}
exports.HttpServer_Class = HttpServer_Class;
HttpServer_Class.expressMiddleware = [];
class RouteResolver {
constructor(require, rootDir, apiFolder) {
this.middlewares = [];
this.require = require;
this.rootDir = rootDir;
this.apiFolder = apiFolder || "";
}
setMiddlewares(...middlewares) {
this.middlewares = middlewares;
return this;
}
resolveApi(urlPrefix, baseUrl, _exp) {
this.resolveApiImpl(urlPrefix, this.rootDir + "/" + this.apiFolder, baseUrl, _exp);
}
resolveApiImpl(urlPrefix, workingDir, baseUrl, _exp) {
fs.readdirSync(workingDir).forEach((file) => {
if (fs.statSync(`${workingDir}/${file}`).isDirectory()) {
this.resolveApiImpl(`${urlPrefix}/${file}`, `${workingDir}/${file}`, baseUrl, _exp);
return;
}
if (file.endsWith(".d.ts"))
return;
if (!file.endsWith(".js"))
return;
if (file.startsWith("_"))
return;
const relativePathToFile = `.${workingDir.replace(this.rootDir, "")}/${file}`;
if (file.startsWith("&")) {
let routeResolver;
try {
routeResolver = this.require(relativePathToFile);
}
catch (e) {
console.log(`could not reference RouteResolver for: ${workingDir}/${relativePathToFile}`, e);
throw e;
}
routeResolver.resolveApi(urlPrefix, baseUrl, _exp);
return;
}
let content;
try {
content = this.require(relativePathToFile);
}
catch (e) {
console.log(`could not reference ServerApi for: ${workingDir}/${relativePathToFile}`, e);
throw e;
}
if (!Array.isArray(content))
content = [content];
this.routeApis(content, urlPrefix, baseUrl, _exp);
});
}
routeApis(apis, urlPrefix, baseUrl, _exp) {
apis.forEach(api => {
api.addMiddlewares(...this.middlewares);
api.route(_exp, urlPrefix, baseUrl);
});
}
}
exports.RouteResolver = RouteResolver;
class HeaderKey {
constructor(key, responseCode = 400) {
this.key = key;
this.responseCode = responseCode;
}
get(request) {
const value = request.header(this.key);
if (!value)
throw new exceptions_1.ApiException(this.responseCode, `Missing expected header: ${this.key}`);
return value;
}
}
exports.HeaderKey = HeaderKey;
//# sourceMappingURL=HttpServer.js.map