import type { APIGatewayProxyEvent, Context } from 'aws-lambda' import cuid from 'cuid' import { Request } from 'express' import { parseMultiValueQueryStringParameters, parseQueryStringParameters } from './Utils.js' import Globals from '../../../Globals.js' import Server from '../Server.js' /** * Represents the response object returned by a generic event handler. * @typedef {Object} GenericHandlerEventResponse * @property {Error | string} [err] - An optional error object or error message. * @property {*} [data] - An optional data object. */ export type GenericHandlerEventResponse = { err?: Error | string; data?: any } type HasRawBody = { rawBody: Buffer } /** * Represents a generic handler event for serverless functions. */ export default class GenericHandlerEvent { /** * Represents an HTTP request. * @property {Request} request - The HTTP request object. */ public request: Request /** * The handler function for serverless events in a server. * @param {Server['handleServerlessEvent']} serverlessHandler - The function that handles serverless events. * @returns None */ public serverlessHandler: Server['handleServerlessEvent'] /** * Constructs a new instance of the class. * @param {Request} request - The request object. * @param {Server['handleServerlessEvent']} serverlessHandler - The serverless handler function. * @returns None */ constructor(request: Request, serverlessHandler: Server['handleServerlessEvent']) { this.request = request this.serverlessHandler = serverlessHandler } /** * Invokes the handler function asynchronously and returns a promise that resolves to a GenericHandlerEventResponse. * @returns {Promise} A promise that resolves to a GenericHandlerEventResponse. */ public async invoke(): Promise { // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { try { // Build event and context const event = this.buildEvent() const context = this.buildContext(event, (err?: Error | string, data?: any) => { resolve({ err, data }) }) // Invoke await this.serverlessHandler(event, context) } catch (e) { reject(e) // forward rejection } }) } /** * Builds and returns an APIGatewayProxyEvent object based on the current request. * @returns {APIGatewayProxyEvent} - The constructed APIGatewayProxyEvent object. */ private buildEvent(): APIGatewayProxyEvent & HasRawBody { return { body: this.request.body || null, //enforce key rawBody: this.request['rawBody'], headers: (this.request.headers || {}), httpMethod: this.request.method?.toUpperCase(), isBase64Encoded: false, multiValueHeaders: (this.request.headers || {}), multiValueQueryStringParameters: parseMultiValueQueryStringParameters(this.request.url), path: this.request.path, pathParameters: null, queryStringParameters: this.request.url ? parseQueryStringParameters(this.request.url) : {}, requestContext: { accountId: process.env.AWS_ACCOUNT_ID || 'undefined', apiId: '', authorizer: null, domainName: undefined, domainPrefix: undefined, extendedRequestId: cuid(), httpMethod: this.request.method ? this.request.method.toUpperCase() : '', identity: { accessKey: null, accountId: process.env.AWS_ACCOUNT_ID || null, caller: null, apiKey: null, apiKeyId: null, clientCert: null, cognitoAuthenticationProvider: null, cognitoAuthenticationType: null, cognitoIdentityId: null, cognitoIdentityPoolId: null, principalOrgId: null, sourceIp: this.request.headers?.['x-forwarded-for'] || this.request.socket?.remoteAddress || '', user: null, userAgent: this.request.headers?.['user-agent'] || null, userArn: null, }, path: this.request.path, protocol: 'HTTP/1.1', requestId: `${cuid()}-${cuid()}`, requestTime: new Date().toISOString(), requestTimeEpoch: Date.now(), resourceId: '', resourcePath: Globals.Listener_HTTP_ProxyRoute, stage: process.env.STAGE || '', }, resource: Globals.Listener_HTTP_ProxyRoute, stageVariables: null, } } /** * Builds and returns a context object for an AWS Lambda function. * @param {APIGatewayProxyEvent} event - The event object passed to the Lambda function. * @param {(err?: Error | string, data?: any) => void} callback - The callback function to be called when the Lambda function completes. * @returns {Context} - The context object for the Lambda function. */ private buildContext( event: APIGatewayProxyEvent, callback: (err?: Error | string, data?: any) => void ): Context { return { awsRequestId: event.requestContext.requestId, callbackWaitsForEmptyEventLoop: true, getRemainingTimeInMillis: () => { return 0 }, done: (err, data) => callback(err, data), fail: err => callback(err), succeed: res => callback(undefined, res), functionName: 'container-function', functionVersion: 'LATEST', invokedFunctionArn: 'none', memoryLimitInMB: '-1', logGroupName: 'undefined', logStreamName: 'undefined', } } }