@bitblit/epsilon
Version:
Tiny adapter to simplify building API gateway Lambda APIS
129 lines • 7.63 kB
JavaScript
;
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.ApolloFilter = void 0;
const common_1 = require("@bitblit/ratchet/common");
const request_timeout_error_1 = require("../../http/error/request-timeout-error");
const server_1 = require("@apollo/server");
const context_util_1 = require("../../util/context-util");
const apollo_util_1 = require("./apollo-util");
const epsilon_apollo_cors_method_1 = require("./epsilon-apollo-cors-method");
const built_in_filters_1 = require("../../built-in/http/built-in-filters");
class ApolloFilter {
static handlePathWithApollo(fCtx, apolloPathRegex, apolloServer, options) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
let rval = false;
if (((_a = fCtx.event) === null || _a === void 0 ? void 0 : _a.path) && apolloPathRegex && apolloPathRegex.test(fCtx.event.path)) {
fCtx.result = yield ApolloFilter.processApolloRequest(fCtx.event, fCtx.context, apolloServer, options);
if (options === null || options === void 0 ? void 0 : options.corsMethod) {
switch (options.corsMethod) {
case epsilon_apollo_cors_method_1.EpsilonApolloCorsMethod.All:
yield built_in_filters_1.BuiltInFilters.addAllowEverythingCORSHeaders(fCtx);
break;
case epsilon_apollo_cors_method_1.EpsilonApolloCorsMethod.Reflective:
yield built_in_filters_1.BuiltInFilters.addAllowReflectionCORSHeaders(fCtx);
break;
default:
// Do nothing
}
}
}
else {
// Not handled by apollo
rval = true;
}
return rval;
});
}
static processApolloRequest(event, context, apolloServer, options) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
common_1.Logger.silly('Processing event with apollo: %j', event);
let rval = null;
common_1.RequireRatchet.notNullOrUndefined(apolloServer, 'apolloServer');
apolloServer.assertStarted('Cannot process with apollo - instance not started');
const headerMap = new server_1.HeaderMap();
for (const headersKey in event.headers) {
headerMap.set(headersKey, event.headers[headersKey]);
}
const eventMethod = common_1.StringRatchet.trimToEmpty(event.httpMethod).toUpperCase();
let body = null;
if (common_1.StringRatchet.trimToNull(event.body)) {
const bodyString = event.isBase64Encoded ? common_1.Base64Ratchet.base64StringToString(event.body) : event.body;
body = JSON.parse(bodyString);
}
const aRequest = {
method: eventMethod,
headers: headerMap,
search: eventMethod === 'GET' && (event === null || event === void 0 ? void 0 : event.queryStringParameters)
? Object.keys(event.queryStringParameters)
.map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(event.queryStringParameters[k]))
.join('&')
: null,
body: body,
};
// We do this because fully timing out on Lambda is never a good thing
const timeoutMS = (_a = options === null || options === void 0 ? void 0 : options.timeoutMS) !== null && _a !== void 0 ? _a : context.getRemainingTimeInMillis() - 500;
//const defaultContextFn: ContextFunction<[EpsilonLambdaApolloContextFunctionArgument], any> = async () => ({});
const contextFn = (_b = options === null || options === void 0 ? void 0 : options.context) !== null && _b !== void 0 ? _b : apollo_util_1.ApolloUtil.emptyContext;
const apolloPromise = apolloServer.executeHTTPGraphQLRequest({
httpGraphQLRequest: aRequest,
context: () => contextFn({ lambdaContext: context, lambdaEvent: event }),
});
let result = null;
if (timeoutMS) {
result = yield common_1.PromiseRatchet.timeout(apolloPromise, 'Apollo timed out after ' + timeoutMS + ' ms.', timeoutMS);
}
else {
common_1.Logger.warn('No timeout set even after defaulting for Apollo');
result = yield apolloPromise;
}
if (common_1.TimeoutToken.isTimeoutToken(result)) {
result.writeToLog();
throw new request_timeout_error_1.RequestTimeoutError('Timed out');
}
// If we reach here we didn't time out
const httpGraphQLResponse = result; // TODO: Use typeguard here instead
const outHeaders = {};
for (const [headersKey, headersValue] of httpGraphQLResponse.headers.entries()) {
outHeaders[headersKey] = headersValue;
}
if (httpGraphQLResponse.body.kind === 'chunked') {
// This is legal according to https://www.apollographql.com/docs/apollo-server/integrations/building-integrations/
throw new common_1.RestfulApiHttpError('Apollo returned chunked result').withHttpStatusCode(500).withRequestId(context_util_1.ContextUtil.currentRequestId());
}
const bodyAsString = common_1.StringRatchet.trimToEmpty((_c = httpGraphQLResponse === null || httpGraphQLResponse === void 0 ? void 0 : httpGraphQLResponse.body) === null || _c === void 0 ? void 0 : _c.string);
rval = {
body: common_1.Base64Ratchet.generateBase64VersionOfString(bodyAsString),
headers: outHeaders,
multiValueHeaders: {},
isBase64Encoded: true,
statusCode: httpGraphQLResponse.status || 200,
};
// Finally, a double check to set the content type correctly if the browser page was shown
// Since otherwise Apollo defaults it to application/json for some reason
if (eventMethod === 'GET' && rval.headers['content-type'] !== 'text/html' && bodyAsString.indexOf('<!DOCTYPE html>') >= 0) {
common_1.Logger.info('Forcing content type to html for the sandbox page');
rval.headers = rval.headers || {};
rval.headers['content-type'] = 'text/html';
}
return rval;
});
}
static addApolloFilterToList(filters, apolloPathRegex, apolloServer, options) {
if (filters) {
filters.push((fCtx) => ApolloFilter.handlePathWithApollo(fCtx, apolloPathRegex, apolloServer, options));
}
}
}
exports.ApolloFilter = ApolloFilter;
//# sourceMappingURL=apollo-filter.js.map