UNPKG

@intuitionrobotics/thunderstorm

Version:
351 lines 14.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ApiResponse = exports.ServerApi_Redirect = exports.ServerApi_Proxy = exports.ServerApi_Post = exports.ServerApi_Get = exports.ServerApi = void 0; const ts_common_1 = require("@intuitionrobotics/ts-common"); const url_1 = require("url"); // noinspection TypeScriptPreferShortImport const types_1 = require("../../../shared/types"); const to_be_removed_1 = require("../../utils/to-be-removed"); const exceptions_1 = require("../../exceptions"); const RemoteProxy_1 = require("../proxy/RemoteProxy"); const Storm_1 = require("../../core/Storm"); class ServerApi extends ts_common_1.Logger { constructor(method, relativePath, tag) { super(tag || relativePath); this.printResponse = true; this.headersToLog = []; this.sideEffects = []; this.scopes = []; this.assertProperty = to_be_removed_1.assertProperty; this.callWrapper = (req, res) => __awaiter(this, void 0, void 0, function* () { yield this.call(req, res); yield this.releaseSideEffects(); }); this.releaseSideEffects = () => __awaiter(this, void 0, void 0, function* () { try { yield Promise.all(this.sideEffects.map(sideEffect => sideEffect())); } catch (e) { this.logError("Something went wrong while performing the side effects", e); } finally { this.sideEffects = []; } }); this.call = (req, res) => __awaiter(this, void 0, void 0, function* () { const response = new ApiResponse(this, res); this.logInfo(`Intercepted Url: ${req.path}`); if (this.headersToLog.length > 0) { const headers = {}; for (const headerName of this.headersToLog) { headers[headerName] = req.header(headerName); } this.logDebug(`-- Headers: `, headers); } const reqQuery = (0, url_1.parse)(req.url, true).query; if (reqQuery && typeof reqQuery === "object" && Object.keys(reqQuery).length) this.logVerbose(`-- Url Params: `, reqQuery); else this.logVerbose(`-- No Params`); const body = req.body; if (body && ((typeof body === "object"))) this.logVerbose(`-- Body (Object): `, body); else if (body && body.length) this.logVerbose(`-- Body (String): `, body); else this.logVerbose(`-- No Body`); const requestData = { method: this.method, originalUrl: req.path, headers: req.headers, url: req.url, query: reqQuery, body: body }; try { this.bodyValidator && (0, ts_common_1.validate)(body, this.bodyValidator); this.queryValidator && (0, ts_common_1.validate)(reqQuery, this.queryValidator); const context = yield this.applyMiddlewares(req, requestData, response, this.scopes); const toReturn = yield this.process(req, response, reqQuery, body, context); if (response.isConsumed()) return; if (!toReturn) return yield response.end(200); // TODO need to handle stream and buffers // if (Buffer.isBuffer(toReturn)) // return response.stream(200, toReturn as Buffer); const responseType = typeof toReturn; if (responseType === "object") return yield response.json(200, toReturn); if (responseType === "string" && toReturn.toLowerCase().startsWith("<html")) return yield response.html(200, toReturn); return yield response.text(200, toReturn); } catch (err) { let dispatchError = true; let e = err; let severity = ts_common_1.ServerErrorSeverity.Warning; if (typeof e === "string") e = new ts_common_1.BadImplementationException(`String was thrown: ${e}`); if (!(e instanceof Error) && typeof e === "object") e = new ts_common_1.BadImplementationException(`Object instance was thrown: ${JSON.stringify(e)}`); try { this.logErrorBold(e); } catch (e2) { this.logErrorBold("Error while handling error on request...", e2); this.logErrorBold(`Original error thrown: ${JSON.stringify(e)}`); this.logErrorBold(`-- Someone was stupid... you MUST only throw an Error and not objects or strings!! --`); } if ((0, ts_common_1.isErrorOfType)(e, ts_common_1.ValidationException)) e = new exceptions_1.ApiException(400, "Validator exception", e); if (!(0, ts_common_1.isErrorOfType)(e, exceptions_1.ApiException)) e = new exceptions_1.ApiException(500, "Unexpected server error", e); const apiException = (0, ts_common_1.isErrorOfType)(e, exceptions_1.ApiException); if (!apiException) throw new ts_common_1.MUSTNeverHappenException("MUST NEVER REACH HERE!!!"); dispatchError = apiException.getDispatchError(); if (apiException.responseCode >= 500) severity = ts_common_1.ServerErrorSeverity.Error; else if (apiException.responseCode >= 400) severity = ts_common_1.ServerErrorSeverity.Warning; switch (apiException.responseCode) { case 401: severity = ts_common_1.ServerErrorSeverity.Debug; break; case 404: severity = ts_common_1.ServerErrorSeverity.Info; break; case 403: severity = ts_common_1.ServerErrorSeverity.Warning; break; case 500: severity = ts_common_1.ServerErrorSeverity.Critical; break; } if (dispatchError) { try { const storm = Storm_1.Storm.getInstance(); if (storm) { const message = yield storm.errorMessageComposer(requestData, apiException); yield ts_common_1.dispatch_onServerError.dispatchModuleAsync(severity, message); } } catch (e) { this.logError("Error while handing server error", e); } } if (apiException.responseCode === 500) return response.serverError(apiException); return response.exception(apiException); } }); this.setMinLevel(ServerApi.isDebug ? ts_common_1.LogLevel.Verbose : ts_common_1.LogLevel.Info); this.method = method; this.relativePath = `${relativePath}`; } shouldPrintResponse() { return this.printResponse; } ; addSideEffect(sideEffect) { this.sideEffects.push(sideEffect); return this; } setMiddlewares(...middlewares) { this.middlewares = middlewares; return this; } addMiddlewares(...middlewares) { this.middlewares = [...(this.middlewares || []), ...middlewares]; return this; } setScopes(...scopes) { this.scopes = [...scopes]; return this; } getScopes() { return this.scopes; } addHeaderToLog(...headersToLog) { this.headersToLog.push(...headersToLog); } setBodyValidator(bodyValidator) { this.bodyValidator = bodyValidator; } setQueryValidator(queryValidator) { this.queryValidator = queryValidator; } asProxy() { return new ServerApi_Proxy(this); } getUrl() { return this.url; } dontPrintResponse() { this.printResponse = false; } setMaxResponsePrintSize(printResponseMaxSizeBytes) { this.printResponse = printResponseMaxSizeBytes > -1; } route(router, prefixUrl, baseUrl) { this.baseUrl = baseUrl; const fullPath = `${prefixUrl ? prefixUrl : ""}/${this.relativePath}`; this.setTag(fullPath); router[this.method](fullPath, this.callWrapper); this.url = `${baseUrl}${fullPath}`; } applyMiddlewares(req, requestData, response, scopes) { return __awaiter(this, void 0, void 0, function* () { if (!this.middlewares) return {}; const contextList = yield Promise.all(this.middlewares.map((m) => __awaiter(this, void 0, void 0, function* () { return m(req, requestData, response, scopes); }))); return contextList.reduce((acc, c) => (0, ts_common_1.merge)(acc, c || {}), {}); }); } } exports.ServerApi = ServerApi; class ServerApi_Get extends ServerApi { constructor(apiName) { super(types_1.HttpMethod.GET, apiName); } } exports.ServerApi_Get = ServerApi_Get; class ServerApi_Post extends ServerApi { constructor(apiName) { super(types_1.HttpMethod.POST, apiName); } } exports.ServerApi_Post = ServerApi_Post; class ServerApi_Proxy extends ServerApi { constructor(api) { super(api.method, `${api.relativePath}/proxy`); this.api = api; this.setMiddlewares(RemoteProxy_1.RemoteProxy.Middleware); } process(request, response, queryParams, body, context) { return __awaiter(this, void 0, void 0, function* () { // @ts-ignore return this.api.process(request, response, queryParams, body, context); }); } } exports.ServerApi_Proxy = ServerApi_Proxy; class ServerApi_Redirect extends ServerApi { constructor(apiName, responseCode, redirectUrl) { super(types_1.HttpMethod.ALL, apiName); this.responseCode = responseCode; this.redirectUrl = redirectUrl; } process(request, response, queryParams, body) { return __awaiter(this, void 0, void 0, function* () { const query = queryParams ? (0, ts_common_1._keys)(queryParams).reduce((c, k) => c + '&' + k + '=' + queryParams[k], '?') : ''; response.redirect(this.responseCode, `${this.baseUrl}${this.redirectUrl}${query}`); }); } } exports.ServerApi_Redirect = ServerApi_Redirect; class ApiResponse { constructor(api, res) { this.consumed = false; this.api = api; this.res = res; } isConsumed() { return this.consumed; } consume() { if (this.consumed) { this.api.logError("This API was already satisfied!!", new Error()); return; } this.consumed = true; } stream(responseCode, stream, headers) { this.consume(); this.printHeaders(headers); this.res.set(headers); this.res.writeHead(responseCode); stream.pipe(this.res, { end: false }); stream.on('end', () => { this.res.end(); }); } printHeaders(headers) { if (!headers) return this.api.logVerbose(` -- No response headers`); this.api.logVerbose(` -- Response with headers: `, headers); } printResponse(response) { if (!response) return this.api.logVerbose(` -- No response body`); if (!this.api.shouldPrintResponse()) return this.api.logVerbose(` -- Response: -- Not Printing --`); this.api.logVerbose(` -- Response:`, response); } code(responseCode, headers) { this.printHeaders(headers); this.end(responseCode, "", headers); } text(responseCode, response, _headers) { const headers = (_headers || {}); headers["content-type"] = "text/plain"; this.end(responseCode, response, headers); } html(responseCode, response, _headers) { const headers = (_headers || {}); headers["content-type"] = "text/html"; this.end(responseCode, response, headers); } json(responseCode, response, _headers) { this._json(responseCode, response, _headers); } _json(responseCode, response, _headers) { const headers = (_headers || {}); headers["content-type"] = "application/json"; this.end(responseCode, response, headers); } end(responseCode, response, headers) { this.consume(); this.printHeaders(headers); this.printResponse(response); this.res.set(headers); this.res.writeHead(responseCode); this.res.end(typeof response !== "string" ? JSON.stringify(response, null, 2) : response); } setHeaders(headers) { this.res.header(headers); } setHeader(headerKey, value) { this.res.header(headerKey, value); } getHeader(headerKey) { return this.res.get(headerKey); } redirect(responseCode, url) { this.consume(); this.res.redirect(responseCode, url); } exception(exception, headers) { const responseBody = exception.responseBody; if (!ServerApi.isDebug) delete responseBody.debugMessage; this._json(exception.responseCode, responseBody, headers); } serverError(error, headers) { const stack = error.cause ? error.cause.stack : error.stack; const message = (error.cause ? error.cause.message : error.message) || ""; this.text(500, ServerApi.isDebug && stack ? stack : message, headers); } } exports.ApiResponse = ApiResponse; //# sourceMappingURL=server-api.js.map