UNPKG

@bitblit/epsilon

Version:

Tiny adapter to simplify building API gateway Lambda APIS

254 lines 13.3 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()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); var logger_1 = require("@bitblit/ratchet/dist/common/logger"); var map_ratchet_1 = require("@bitblit/ratchet/dist/common/map-ratchet"); var zlib = require("zlib"); var epsilon_constants_1 = require("../epsilon-constants"); var string_ratchet_1 = require("@bitblit/ratchet/dist/common/string-ratchet"); var ResponseUtil = /** @class */ (function () { function ResponseUtil() { } // Prevent instantiation ResponseUtil.errorResponse = function (errorMessages, statusCode, reqId) { var body = { errors: errorMessages, httpStatusCode: statusCode, requestId: reqId || 'MISSING' }; var errorResponse = { statusCode: statusCode, isBase64Encoded: false, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }; return errorResponse; }; ResponseUtil.redirect = function (target, code, queryParams) { if (code === void 0) { code = 301; } if (queryParams === void 0) { queryParams = null; } if (code !== 301 && code !== 302) { throw new Error('Code must be 301 or 302 for a redirect'); } var redirectTarget = target; if (queryParams) { var keys = Object.keys(queryParams); if (keys.length > 0) { logger_1.Logger.silly('Applying params to input target : %j', queryParams); redirectTarget += redirectTarget.indexOf('?') === -1 ? '?' : '&'; for (var i = 0; i < keys.length; i++) { var k = keys[i]; // TODO: make sure not double encoding redirectTarget += k + '=' + encodeURIComponent(queryParams[k]); if (i < keys.length - 1) { redirectTarget += '&'; } } } } return { statusCode: code, body: '{"redirect-target":"' + redirectTarget + '}', headers: { 'Content-Type': 'application/json', Location: redirectTarget } }; }; ResponseUtil.buildHttpError = function (errorMessage, statusCode) { var rval = new Error(errorMessage); rval['statusCode'] = statusCode; return rval; }; ResponseUtil.errorToProxyResult = function (error, reqId, defaultErrorMessage) { if (defaultErrorMessage === void 0) { defaultErrorMessage = null; } var code = error['statusCode'] || 500; var errorMessages = null; if (!!string_ratchet_1.StringRatchet.trimToNull(defaultErrorMessage) && code === 500) { // Basically the 'Internal Server Error' info hiding use case errorMessages = [defaultErrorMessage]; } else { errorMessages = error['messages'] && error['messages'].length > 0 ? error['messages'] : null; errorMessages = errorMessages ? errorMessages : [error.message || JSON.stringify(error)]; } return ResponseUtil.errorResponse(errorMessages, code, reqId); }; ResponseUtil.coerceToProxyResult = function (input) { var rval = null; if (input != null) { if (typeof input === 'object') { if (input.statusCode && input.body !== undefined) { rval = Object.assign({}, input); if (typeof input.body === 'string') { // Do Nothing } else if (Buffer.isBuffer(input.body)) { rval.body = input.body.toString('base64'); rval.headers = input.headers || {}; rval.headers['Content-Type'] = input.body.contentType; // TODO: Does this work? rval.isBase64Encoded = true; } } else { // Its a generic object var headers = input.headers || {}; headers['Content-Type'] = 'application/json'; rval = ResponseUtil.coerceToProxyResult({ statusCode: 200, body: JSON.stringify(input), headers: headers, isBase64Encoded: false }); } } else if (typeof input === 'string' || Buffer.isBuffer(input)) { rval = ResponseUtil.coerceToProxyResult({ statusCode: 200, body: input }); } else { // boolean , number, etc - other top level types // See : https://stackoverflow.com/questions/18419428/what-is-the-minimum-valid-json var headers = input.headers || {}; headers['Content-Type'] = 'application/json'; rval = ResponseUtil.coerceToProxyResult({ statusCode: 200, body: JSON.stringify(input), headers: headers, isBase64Encoded: false }); } } return rval; }; // Public so it can be used in auth-web-handler ResponseUtil.addCORSToProxyResult = function (input, cfg, srcEvent) { input.headers = input.headers || {}; srcEvent.headers = srcEvent.headers || {}; // Matching the request is mainly here to support old safari browsers var targetOrigin = cfg.corsAllowedOrigins !== epsilon_constants_1.EpsilonConstants.CORS_MATCH_REQUEST_FLAG ? cfg.corsAllowedOrigins : ResponseUtil.buildReflectCorsAllowOrigin(srcEvent, '*'); var targetHeaders = cfg.corsAllowedHeaders !== epsilon_constants_1.EpsilonConstants.CORS_MATCH_REQUEST_FLAG ? cfg.corsAllowedHeaders : ResponseUtil.buildReflectCorsAllowHeaders(srcEvent, '*'); var targetMethod = cfg.corsAllowedMethods !== epsilon_constants_1.EpsilonConstants.CORS_MATCH_REQUEST_FLAG ? cfg.corsAllowedMethods : ResponseUtil.buildReflectCorsAllowMethods(srcEvent, '*'); logger_1.Logger.silly('Adding CORS to proxy result tOrigin: %s tHeaders: %s tMethod: %s', targetOrigin, targetHeaders, targetMethod); if (string_ratchet_1.StringRatchet.trimToNull(string_ratchet_1.StringRatchet.safeString(input.headers['Access-Control-Allow-Origin'])) === null && !!targetOrigin) { input.headers['Access-Control-Allow-Origin'] = targetOrigin; } if (string_ratchet_1.StringRatchet.trimToNull(string_ratchet_1.StringRatchet.safeString(input.headers['Access-Control-Allow-Methods'])) === null && !!targetMethod) { input.headers['Access-Control-Allow-Methods'] = targetMethod; } if (string_ratchet_1.StringRatchet.trimToNull(string_ratchet_1.StringRatchet.safeString(input.headers['Access-Control-Allow-Headers'])) === null && !!targetHeaders) { input.headers['Access-Control-Allow-Headers'] = targetHeaders; } return input; }; ResponseUtil.buildReflectCorsAllowOrigin = function (srcEvent, defaultValue) { if (defaultValue === void 0) { defaultValue = '*'; } var rval = map_ratchet_1.MapRatchet.caseInsensitiveAccess(srcEvent.headers, 'Origin') || defaultValue; return rval; }; ResponseUtil.buildReflectCorsAllowHeaders = function (srcEvent, defaultValue) { if (defaultValue === void 0) { defaultValue = '*'; } var rval = map_ratchet_1.MapRatchet.caseInsensitiveAccess(srcEvent.headers, 'Access-Control-Request-Headers') || Object.keys(srcEvent.headers).join(', ') || '*'; return rval; }; ResponseUtil.buildReflectCorsAllowMethods = function (srcEvent, defaultValue) { if (defaultValue === void 0) { defaultValue = '*'; } var rval = map_ratchet_1.MapRatchet.caseInsensitiveAccess(srcEvent.headers, 'Access-Control-Request-Method') || srcEvent.httpMethod.toUpperCase() || '*'; return rval; }; ResponseUtil.applyGzipIfPossible = function (encodingHeader, proxyResult) { return __awaiter(this, void 0, void 0, function () { var rval, bigEnough, contentType, exemptContent, asBuffer, zipped, zipped64; return __generator(this, function (_a) { switch (_a.label) { case 0: rval = proxyResult; if (!(encodingHeader && encodingHeader.toLowerCase().indexOf('gzip') > -1)) return [3 /*break*/, 4]; bigEnough = proxyResult.body.length > 1400; contentType = map_ratchet_1.MapRatchet.extractValueFromMapIgnoreCase(proxyResult.headers, 'content-type') || ''; contentType = contentType.toLowerCase(); exemptContent = contentType === 'application/pdf' || contentType === 'application/zip' || contentType.startsWith('image/'); if (!(bigEnough && !exemptContent)) return [3 /*break*/, 2]; asBuffer = proxyResult.isBase64Encoded ? Buffer.from(proxyResult.body, 'base64') : Buffer.from(proxyResult.body); return [4 /*yield*/, this.gzip(asBuffer)]; case 1: zipped = _a.sent(); logger_1.Logger.silly('Comp from %d to %d bytes', asBuffer.length, zipped.length); zipped64 = zipped.toString('base64'); rval.body = zipped64; rval.isBase64Encoded = true; rval.headers = rval.headers || {}; rval.headers['Content-Encoding'] = 'gzip'; return [3 /*break*/, 3]; case 2: logger_1.Logger.silly('Not gzipping, too small or exempt content'); _a.label = 3; case 3: return [3 /*break*/, 5]; case 4: logger_1.Logger.silly('Not gzipping, not an accepted encoding'); _a.label = 5; case 5: return [2 /*return*/, rval]; } }); }); }; ResponseUtil.gzip = function (input) { var promise = new Promise(function (resolve, reject) { zlib.gzip(input, function (error, result) { if (!error) resolve(result); else reject(error); }); }); return promise; }; return ResponseUtil; }()); exports.ResponseUtil = ResponseUtil; //# sourceMappingURL=response-util.js.map