UNPKG

@intuitionrobotics/thunderstorm

Version:
186 lines 7.14 kB
"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