UNPKG

@bitblit/ratchet-epsilon-common

Version:

Tiny adapter to simplify building API gateway Lambda APIS

102 lines 5.07 kB
import { RequireRatchet } from '@bitblit/ratchet-common/lang/require-ratchet'; import { Logger } from '@bitblit/ratchet-common/logger/logger'; import { PromiseRatchet } from '@bitblit/ratchet-common/lang/promise-ratchet'; import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet'; import { TimeoutToken } from '@bitblit/ratchet-common/lang/timeout-token'; import { RestfulApiHttpError } from '@bitblit/ratchet-common/network/restful-api-http-error'; import { Base64Ratchet } from '@bitblit/ratchet-common/lang/base64-ratchet'; import { RequestTimeoutError } from '../../http/error/request-timeout-error.js'; import { HeaderMap } from '@apollo/server'; import { ContextUtil } from '../../util/context-util.js'; import { ApolloUtil } from './apollo/apollo-util.js'; import { BuiltInFilters } from './built-in-filters.js'; export class ApolloFilter { static async handlePathWithApollo(fCtx, apolloPathRegex, apolloServer, options) { let rval = false; if (fCtx.event?.path && apolloPathRegex && apolloPathRegex.test(fCtx.event.path)) { fCtx.result = await ApolloFilter.processApolloRequest(fCtx.event, fCtx.context, apolloServer, options); if (options?.corsMethod) { await BuiltInFilters.addCorsHeadersDynamically(fCtx, options.corsMethod); } } else { rval = true; } return rval; } static async processApolloRequest(event, context, apolloServer, options) { Logger.silly('Processing event with apollo: %j', event); let rval = null; RequireRatchet.notNullOrUndefined(apolloServer, 'apolloServer'); apolloServer.assertStarted('Cannot process with apollo - instance not started'); const headerMap = new HeaderMap(); for (const headersKey in event.headers) { headerMap.set(headersKey, event.headers[headersKey]); } const eventMethod = StringRatchet.trimToEmpty(event.httpMethod).toUpperCase(); let body = null; if (StringRatchet.trimToNull(event.body)) { const bodyString = event.isBase64Encoded ? Base64Ratchet.base64StringToString(event.body) : event.body; body = JSON.parse(bodyString); } const aRequest = { method: eventMethod, headers: headerMap, search: eventMethod === 'GET' && event?.queryStringParameters ? Object.keys(event.queryStringParameters) .map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(event.queryStringParameters[k])) .join('&') : null, body: body, }; const timeoutMS = options?.timeoutMS ?? context.getRemainingTimeInMillis() - 500; const contextFn = options?.context ?? ApolloUtil.emptyContext; const apolloPromise = apolloServer.executeHTTPGraphQLRequest({ httpGraphQLRequest: aRequest, context: () => contextFn({ lambdaContext: context, lambdaEvent: event }), }); let result = null; if (timeoutMS) { result = await PromiseRatchet.timeout(apolloPromise, 'Apollo timed out after ' + timeoutMS + ' ms.', timeoutMS); } else { Logger.warn('No timeout set even after defaulting for Apollo'); result = await apolloPromise; } if (TimeoutToken.isTimeoutToken(result)) { result.writeToLog(); throw new RequestTimeoutError('Timed out'); } const httpGraphQLResponse = result; const outHeaders = {}; for (const [headersKey, headersValue] of httpGraphQLResponse.headers.entries()) { outHeaders[headersKey] = headersValue; } if (httpGraphQLResponse.body.kind === 'chunked') { throw new RestfulApiHttpError('Apollo returned chunked result').withHttpStatusCode(500).withRequestId(ContextUtil.currentRequestId()); } const bodyAsString = StringRatchet.trimToEmpty(httpGraphQLResponse?.body?.string); rval = { body: Base64Ratchet.generateBase64VersionOfString(bodyAsString), headers: outHeaders, multiValueHeaders: {}, isBase64Encoded: true, statusCode: httpGraphQLResponse.status || 200, }; if (eventMethod === 'GET' && rval.headers['content-type'] !== 'text/html' && bodyAsString.indexOf('<!DOCTYPE html>') >= 0) { Logger.info('Forcing content type to html for the sandbox page'); rval.headers = rval.headers || {}; rval.headers['content-type'] = 'text/html'; } if (options.debugOutputCallback) { await options.debugOutputCallback(rval); } return rval; } static addApolloFilterToList(filters, apolloPathRegex, apolloServer, options) { if (filters) { filters.push((fCtx) => ApolloFilter.handlePathWithApollo(fCtx, apolloPathRegex, apolloServer, options)); } } } //# sourceMappingURL=apollo-filter.js.map