@intuitionrobotics/thunderstorm
Version:
351 lines • 14.8 kB
JavaScript
"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