@creator.co/wapi 1.1.8 → 1.2.0-alpha2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/.eslintrc.cjs +22 -22
  2. package/README.md +1 -0
  3. package/dist/index.d.ts +3 -2
  4. package/dist/index.js +5 -3
  5. package/dist/index.js.map +1 -1
  6. package/dist/package.json +50 -0
  7. package/dist/src/API/Request.d.ts +4 -5
  8. package/dist/src/API/Request.js +3 -4
  9. package/dist/src/API/Request.js.map +1 -1
  10. package/dist/src/API/Response.d.ts +1 -1
  11. package/dist/src/API/Response.js.map +1 -1
  12. package/dist/src/BaseEvent/EventProcessor.js +0 -1
  13. package/dist/src/BaseEvent/EventProcessor.js.map +1 -1
  14. package/dist/src/BaseEvent/Process.d.ts +4 -7
  15. package/dist/src/BaseEvent/Process.js +1 -2
  16. package/dist/src/BaseEvent/Process.js.map +1 -1
  17. package/dist/src/BaseEvent/Transaction.d.ts +6 -6
  18. package/dist/src/BaseEvent/Transaction.js +2 -4
  19. package/dist/src/BaseEvent/Transaction.js.map +1 -1
  20. package/dist/src/Config/Configuration.d.ts +28 -0
  21. package/dist/src/Config/Configuration.js +93 -0
  22. package/dist/src/Config/Configuration.js.map +1 -0
  23. package/dist/src/Config/EnvironmentVar.d.ts +17 -0
  24. package/dist/src/Config/EnvironmentVar.js +155 -0
  25. package/dist/src/Config/EnvironmentVar.js.map +1 -0
  26. package/dist/src/Globals.d.ts +11 -0
  27. package/dist/src/Globals.js +12 -0
  28. package/dist/src/Globals.js.map +1 -1
  29. package/dist/src/Logger/Logger.js +42 -11
  30. package/dist/src/Logger/Logger.js.map +1 -1
  31. package/dist/src/Mailer/Mailer.js +50 -24
  32. package/dist/src/Mailer/Mailer.js.map +1 -1
  33. package/dist/src/Server/Router.d.ts +30 -0
  34. package/dist/src/Server/Router.js +21 -0
  35. package/dist/src/Server/Router.js.map +1 -0
  36. package/dist/src/Server/lib/ContainerServer.d.ts +11 -0
  37. package/dist/src/Server/lib/ContainerServer.js +100 -0
  38. package/dist/src/Server/lib/ContainerServer.js.map +1 -0
  39. package/dist/src/Server/lib/Server.d.ts +9 -0
  40. package/dist/src/Server/lib/Server.js +137 -0
  41. package/dist/src/Server/lib/Server.js.map +1 -0
  42. package/dist/src/Server/lib/container/GenericHandler.d.ts +4 -0
  43. package/dist/src/Server/lib/container/GenericHandler.js +138 -0
  44. package/dist/src/Server/lib/container/GenericHandler.js.map +1 -0
  45. package/dist/src/Server/lib/container/GenericHandlerEvent.d.ts +14 -0
  46. package/dist/src/Server/lib/container/GenericHandlerEvent.js +164 -0
  47. package/dist/src/Server/lib/container/GenericHandlerEvent.js.map +1 -0
  48. package/dist/src/Server/lib/container/HealthHandler.d.ts +3 -0
  49. package/dist/src/Server/lib/container/HealthHandler.js +44 -0
  50. package/dist/src/Server/lib/container/HealthHandler.js.map +1 -0
  51. package/dist/src/Server/lib/container/Proxy.d.ts +15 -0
  52. package/dist/src/Server/lib/container/Proxy.js +153 -0
  53. package/dist/src/Server/lib/container/Proxy.js.map +1 -0
  54. package/dist/src/Server/lib/container/Utils.d.ts +6 -0
  55. package/dist/src/Server/lib/container/Utils.js +109 -0
  56. package/dist/src/Server/lib/container/Utils.js.map +1 -0
  57. package/dist/src/Validation/Validator.d.ts +5 -0
  58. package/dist/src/Validation/Validator.js +39 -0
  59. package/dist/src/Validation/Validator.js.map +1 -0
  60. package/index.ts +8 -6
  61. package/package.json +14 -3
  62. package/src/API/Request.ts +6 -12
  63. package/src/API/Response.ts +1 -1
  64. package/src/BaseEvent/EventProcessor.ts +3 -2
  65. package/src/BaseEvent/Process.ts +11 -10
  66. package/src/BaseEvent/Transaction.ts +24 -16
  67. package/src/Config/Configuration.ts +83 -0
  68. package/src/Config/EnvironmentVar.ts +94 -0
  69. package/src/Globals.ts +15 -0
  70. package/src/Server/Router.ts +51 -0
  71. package/src/Server/lib/ContainerServer.ts +27 -0
  72. package/src/Server/lib/Server.ts +66 -0
  73. package/src/Server/lib/container/GenericHandler.ts +63 -0
  74. package/src/Server/lib/container/GenericHandlerEvent.ts +133 -0
  75. package/src/Server/lib/container/HealthHandler.ts +5 -0
  76. package/src/Server/lib/container/Proxy.ts +107 -0
  77. package/src/Server/lib/container/Utils.ts +45 -0
  78. package/src/Validation/Validator.ts +35 -0
  79. package/tsconfig.json +2 -0
@@ -0,0 +1,83 @@
1
+ import * as MemCache from "node-cache"
2
+ import * as DurationParser from "parse-duration"
3
+
4
+ import EnvironmentVar, { EnvironmentType } from "./EnvironmentVar"
5
+
6
+ // Hold cached values at nodeVM level
7
+ const cacheStore = new MemCache()
8
+
9
+ export type ConfigurationSchema = {
10
+ [name: string]: {
11
+ type: EnvironmentType
12
+ required?: boolean
13
+ cachingPolicy?: string
14
+ }
15
+ }
16
+
17
+ // runtime type infer to ConfigurationSchema keys, which type is remote
18
+ type ExtractRemote<Type> = keyof {
19
+ [Property in keyof Type]: Type[Property] extends {
20
+ type: EnvironmentType.PlainRemote | EnvironmentType.SecureRemote
21
+ }
22
+ ? Property
23
+ : never
24
+ }
25
+ // runtime type infer to ConfigurationSchema keys, which type is local
26
+ type ExtractLocal<Type> = keyof {
27
+ [Property in keyof Type]: Type[Property] extends {
28
+ type: EnvironmentType.Local
29
+ }
30
+ ? Property
31
+ : never
32
+ }
33
+
34
+ export default class Configuration<T extends ConfigurationSchema> {
35
+ private readonly schema: T
36
+ private readonly remotePrefix: string
37
+ constructor(schema: T, remotePrefix: string) {
38
+ this.schema = schema
39
+ this.remotePrefix = remotePrefix
40
+ }
41
+
42
+ public get(propName: ExtractLocal<T>): any {
43
+ propName = propName as string // TODO: find a better way, this seems to come from keyof
44
+ const propSchema = this.schema[propName]
45
+ let v = this.getCachedValue(propName)
46
+ v =
47
+ v ||
48
+ new EnvironmentVar<string>(
49
+ propName,
50
+ propSchema.type,
51
+ propSchema.required,
52
+ this.remotePrefix,
53
+ ).syncResolve()
54
+ this.cacheValue(propName, v, propSchema.cachingPolicy)
55
+ return v
56
+ }
57
+ public async asyncGet(propName: ExtractRemote<T>): Promise<any> {
58
+ propName = propName as string // TODO: find a better way, this seems to come from keyof
59
+ const propSchema = this.schema[propName]
60
+ let v = this.getCachedValue(propName)
61
+ v =
62
+ v ||
63
+ (await new EnvironmentVar<string>(
64
+ propName,
65
+ propSchema.type,
66
+ propSchema.required,
67
+ this.remotePrefix,
68
+ ).resolve())
69
+ this.cacheValue(propName, v, propSchema.cachingPolicy)
70
+ return v
71
+ }
72
+ // caching layer
73
+ private getCachedValue(valueKey: string): any {
74
+ return cacheStore.get(valueKey)
75
+ }
76
+ private cacheValue(
77
+ valueKey: string,
78
+ value: any,
79
+ policy: string = "1d",
80
+ ): void {
81
+ cacheStore.set(valueKey, value, DurationParser(policy, "s"))
82
+ }
83
+ }
@@ -0,0 +1,94 @@
1
+ import {
2
+ SecretsManagerClient,
3
+ GetSecretValueCommand,
4
+ } from "@aws-sdk/client-secrets-manager"
5
+ import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm"
6
+ // important for dev env to load .env file
7
+ import * as dotenv from "dotenv"
8
+ dotenv.config()
9
+
10
+ // Explicity setting VM level variables in order to persist
11
+ // client across different important and instances.
12
+ // eslint-disable-next-line no-var
13
+ var ssmClient = null,
14
+ secretsClient = null
15
+
16
+ export enum EnvironmentType {
17
+ PlainRemote,
18
+ SecureRemote,
19
+ Local,
20
+ }
21
+ export default class EnvironmentVar<T> {
22
+ private readonly paramName: string
23
+ private readonly type: EnvironmentType
24
+ private readonly optional: boolean
25
+ private readonly paramPrefix: string
26
+ constructor(
27
+ paramName: string,
28
+ type: EnvironmentType,
29
+ optional?: boolean,
30
+ paramPrefix = `/creatorco-api-${process.env.STAGE}/env/` /* only allowed process.env */,
31
+ ) {
32
+ this.paramName = paramName
33
+ this.type = type
34
+ this.optional = !!optional
35
+ this.paramPrefix = paramPrefix
36
+ }
37
+
38
+ public syncResolve(): T {
39
+ if (this.type == EnvironmentType.Local) return this.getLocalValue()
40
+ else {
41
+ throw new Error(
42
+ "EnvironmentVar syncResolve method can only be called for environments of type 'Local'",
43
+ )
44
+ }
45
+ }
46
+ public async resolve() {
47
+ if (this.type == EnvironmentType.Local) return this.getLocalValue()
48
+ else if (this.type == EnvironmentType.SecureRemote)
49
+ return this.getSecureValue()
50
+ else return this.getPlainValue()
51
+ }
52
+
53
+ private getLocalValue(): T {
54
+ const value = process.env[this.paramName]
55
+ if (!value && !this.optional) {
56
+ throw new Error(
57
+ `Local environment ${this.paramName} is not defined, please reconfigure the application and redeploy!`,
58
+ )
59
+ }
60
+ return value as T
61
+ }
62
+ private async getPlainValue(): Promise<T> {
63
+ if (!ssmClient) ssmClient = new SSMClient()
64
+ const pName = `${this.paramPrefix}${this.paramName}`
65
+ try {
66
+ const data = await ssmClient.send(
67
+ new GetParameterCommand({ Name: pName }),
68
+ )
69
+ if (data.Parameter && data.Parameter.Value)
70
+ return data.Parameter.Value as T
71
+ } catch (error) {
72
+ console.error(error)
73
+ }
74
+ if (!this.optional) {
75
+ throw new Error(`Unable to retrieve plain remote env value: ${pName}`)
76
+ }
77
+ }
78
+ private async getSecureValue(): Promise<T> {
79
+ if (!secretsClient) secretsClient = new SecretsManagerClient()
80
+ const pName = `${this.paramPrefix}${this.paramName}`
81
+ try {
82
+ const data = await secretsClient.send(
83
+ new GetSecretValueCommand({ SecretId: pName }),
84
+ )
85
+ if (data.Parameter && data.Parameter.Value)
86
+ return data.Parameter.Value as T
87
+ } catch (error) {
88
+ console.error(error)
89
+ }
90
+ if (!this.optional) {
91
+ throw new Error(`Unable to retrieve secure remote env: ${pName}`)
92
+ }
93
+ }
94
+ }
package/src/Globals.ts CHANGED
@@ -11,4 +11,19 @@ export default class Globals {
11
11
  public static ErrorCode_InvalidInput = "INVALID_INPUT"
12
12
  public static ErrorCode_APIError = "API_ERROR"
13
13
  public static ErrorCode_NoRecords = "EMPTY_EVENT"
14
+
15
+ public static Listener_HTTP_DefaultPort = 80
16
+ public static Listener_HTTP_DefaultHost = "localhost"
17
+ public static Listener_HTTP_ProxyRoute = "*"
18
+ public static Listener_HTTP_DefaultTimeout = 30000
19
+ public static Listener_HTTP_DefaultHealthCheckRoute = "/health"
20
+ //Resps
21
+ public static Resp_MSG_EXCEPTION =
22
+ "[Proxy]: Exception during request execution!"
23
+ public static Resp_CODE_EXCEPTION = "EXEC_EXCEPTION"
24
+ public static Resp_STATUSCODE_EXCEPTION = 502
25
+
26
+ public static Resp_MSG_INVALIDRESP = "[Proxy]: Invalid response from server!"
27
+ public static Resp_CODE_INVALIDRESP = "EMPTY_RESPONSE"
28
+ public static Resp_STATUSCODE_INVALIDRESP = 400
14
29
  }
@@ -0,0 +1,51 @@
1
+ import { z } from "zod"
2
+
3
+ import ContainerServer from "./lib/ContainerServer"
4
+ import Server from "./lib/Server"
5
+ import Transaction, {
6
+ TransactionConfig,
7
+ TransactionExecution,
8
+ } from "../BaseEvent/Transaction"
9
+
10
+ // Route
11
+ export type Route<InputType = object> = {
12
+ path: string
13
+ method: string
14
+ handler: TransactionExecution<Transaction<InputType>>
15
+ inputValidation?: z.ZodObject<any>
16
+ outputValidation?: {
17
+ type: any
18
+ validationSchema: z.ZodObject<any>
19
+ }
20
+ }
21
+
22
+ // Config
23
+ export type RouterConfig = TransactionConfig & {
24
+ routes: Route[]
25
+ // container based configs - otherwise enforced by serverless engine
26
+ port?: number
27
+ timeout?: number
28
+ cors?: {
29
+ origin?: string | string[]
30
+ headers?: string[]
31
+ allowCredentials?: boolean
32
+ }
33
+ healthCheckRoute?: string
34
+ }
35
+
36
+ export default class Router {
37
+ private readonly config: RouterConfig
38
+ private readonly server: Server
39
+ constructor(config: RouterConfig) {
40
+ this.config = config
41
+ this.server = this.isContainer()
42
+ ? new ContainerServer(config)
43
+ : new Server(config)
44
+ }
45
+ public getExport(): CallableFunction {
46
+ return this.server.getExport()
47
+ }
48
+ private isContainer(): boolean {
49
+ return true
50
+ }
51
+ }
@@ -0,0 +1,27 @@
1
+ import Proxy from "./container/Proxy"
2
+ import Server from "./Server"
3
+ import { RouterConfig } from "../Router"
4
+
5
+ export default class ContainerServer extends Server {
6
+ protected readonly proxy: Proxy
7
+ constructor(config: RouterConfig) {
8
+ super(config)
9
+ this.proxy = new Proxy(config, this.handleServerlessEvent.bind(this))
10
+ this.listenProcessEvents()
11
+ }
12
+ public getExport(): CallableFunction {
13
+ return this.start
14
+ }
15
+ /* private */
16
+ public async start() {
17
+ await this.proxy.load()
18
+ }
19
+ public async stop(err) {
20
+ await this.proxy.unload(err)
21
+ }
22
+ private listenProcessEvents() {
23
+ // start process listeners
24
+ process.on("unhandledRejection", this.stop.bind(this)) // listen to exceptions
25
+ process.on("SIGINT", this.stop.bind(this)) // listen on SIGINT signal and gracefully stop the server
26
+ }
27
+ }
@@ -0,0 +1,66 @@
1
+ import { APIGatewayProxyEvent, Context } from "aws-lambda"
2
+ import { pathToRegexp } from "path-to-regexp"
3
+
4
+ import Response from "../../API/Response"
5
+ import Transaction from "../../BaseEvent/Transaction"
6
+ import Validator from "../../Validation/Validator"
7
+ import { RouterConfig } from "../Router"
8
+
9
+ export default class Server {
10
+ protected readonly config: RouterConfig
11
+ constructor(config: RouterConfig) {
12
+ this.config = config
13
+ }
14
+
15
+ public getExport(): CallableFunction {
16
+ return this.handleServerlessEvent
17
+ }
18
+
19
+ public async handleServerlessEvent(
20
+ event: APIGatewayProxyEvent,
21
+ context: Context,
22
+ ) {
23
+ // init transaction
24
+ await new Transaction(event, context, this.config).execute(
25
+ async (transaction) => {
26
+ for (const route of this.config.routes) {
27
+ // Check for possible paths
28
+ const handler = this.routeMatches(route.path, route.method, event)
29
+ if (handler) {
30
+ transaction.logger.log("Router accepted route:", route)
31
+ // Validate input?
32
+ if (route.inputValidation) {
33
+ const validationResp = Validator.validateSchema(
34
+ transaction.request.getBody(),
35
+ route.inputValidation,
36
+ )
37
+ if (validationResp && validationResp instanceof Response)
38
+ return validationResp
39
+ }
40
+ // Continue to route handler
41
+ return await route.handler(transaction)
42
+ }
43
+ }
44
+ //No route found :/
45
+ return new Response(404, { err: "Route not found!" })
46
+ },
47
+ )
48
+ }
49
+ // Helper function to match route
50
+ private routeMatches(
51
+ routePath: string,
52
+ routeMethod: string,
53
+ event: APIGatewayProxyEvent,
54
+ ): boolean {
55
+ const path = event.path
56
+ const keys = []
57
+ const result = pathToRegexp(routePath, keys).exec(path)
58
+ if (result) {
59
+ const reqMethod = event.httpMethod
60
+ if (reqMethod.toLowerCase() == routeMethod.toLowerCase()) {
61
+ return true
62
+ }
63
+ }
64
+ return false
65
+ }
66
+ }
@@ -0,0 +1,63 @@
1
+ import { Request, Response } from "express"
2
+
3
+ import GenericHandlerEvent, {
4
+ GenericHandlerEventResponse,
5
+ } from "./GenericHandlerEvent"
6
+ import Globals from "../../../Globals"
7
+ import Server from "../Server"
8
+
9
+ export default (serverlessHandler: Server["handleServerlessEvent"]) => {
10
+ return async (request: Request, res: Response) => {
11
+ const startTime = Date.now()
12
+ let resp = null
13
+ try {
14
+ // Generate event with request stuff (http to serverless translation)
15
+ const event = new GenericHandlerEvent(request, serverlessHandler)
16
+ // Invoke
17
+ const invokationResp = await event.invoke()
18
+ // Respond
19
+ resp = processServerlessResponse(invokationResp, res)
20
+ } catch (e) {
21
+ console.error("[Proxy] - Exception during execution!", e)
22
+ resp = res
23
+ .json({
24
+ ...e,
25
+ err: Globals.Resp_MSG_EXCEPTION,
26
+ errCode: Globals.Resp_CODE_EXCEPTION,
27
+ })
28
+ .status(Globals.Resp_STATUSCODE_EXCEPTION) //bad gateway
29
+ }
30
+ console.debug(`[Proxy] - Request took ${Date.now() - startTime}ms`)
31
+ return resp
32
+ }
33
+ }
34
+ const processServerlessResponse = (
35
+ invokation: GenericHandlerEventResponse,
36
+ res: Response,
37
+ ) => {
38
+ // translate answer to http layer
39
+ if (invokation && invokation.err) {
40
+ // err came
41
+ return res.json({ err: invokation.err }).status(400)
42
+ } else if (!invokation || !invokation.data) {
43
+ // invalid response came
44
+ return res
45
+ .json({
46
+ err: Globals.Resp_MSG_INVALIDRESP,
47
+ errCode: Globals.Resp_CODE_INVALIDRESP,
48
+ })
49
+ .status(Globals.Resp_STATUSCODE_INVALIDRESP)
50
+ } else {
51
+ // valid
52
+ const response = res
53
+ .json(invokation.data.body || {})
54
+ .status(invokation.data.statusCode)
55
+ // Check for headers
56
+ if (invokation?.data?.headers) {
57
+ for (const hKey of Object.keys(invokation.data.headers)) {
58
+ response.header(hKey, invokation.data.headers[hKey])
59
+ }
60
+ }
61
+ return response
62
+ }
63
+ }
@@ -0,0 +1,133 @@
1
+ import type { APIGatewayProxyEvent, Context } from "aws-lambda"
2
+ import * as cuid from "cuid"
3
+ import { Request } from "express"
4
+ import unflatten from "unflatten"
5
+
6
+ import {
7
+ parseMultiValueHeaders,
8
+ parseMultiValueQueryStringParameters,
9
+ parseQueryStringParameters,
10
+ nullIfEmpty,
11
+ } from "./Utils"
12
+ import Globals from "../../../Globals"
13
+ import Server from "../Server"
14
+ //
15
+ export type GenericHandlerEventResponse = { err?: Error | string; data?: any }
16
+ //
17
+ export default class GenericHandlerEvent {
18
+ public request: Request
19
+ public serverlessHandler: Server["handleServerlessEvent"]
20
+ constructor(
21
+ request: Request,
22
+ serverlessHandler: Server["handleServerlessEvent"],
23
+ ) {
24
+ this.request = request
25
+ this.serverlessHandler = serverlessHandler
26
+ }
27
+ public async invoke(): Promise<GenericHandlerEventResponse> {
28
+ // eslint-disable-next-line no-async-promise-executor
29
+ return new Promise(async (resolve, reject) => {
30
+ try {
31
+ // Build event and context
32
+ const event = this.buildEvent()
33
+ const context = this.buildContext(
34
+ event,
35
+ (err?: Error | string, data?: any) => {
36
+ resolve({ err, data })
37
+ },
38
+ )
39
+ // Invoke
40
+ await this.serverlessHandler(event, context)
41
+ } catch (e) {
42
+ reject(e) // forward rejection
43
+ }
44
+ })
45
+ }
46
+ /* private */
47
+ private buildEvent(): APIGatewayProxyEvent {
48
+ return {
49
+ body: this.request.body || null, //enforce key
50
+ headers: this.request.headers ? unflatten(this.request.headers, 2) : [],
51
+ httpMethod: this.request.method
52
+ ? this.request.method.toUpperCase()
53
+ : null,
54
+ isBase64Encoded: false,
55
+ multiValueHeaders: parseMultiValueHeaders(
56
+ this.request.headers ? this.request.headers || [] : [],
57
+ ),
58
+ multiValueQueryStringParameters: parseMultiValueQueryStringParameters(
59
+ this.request.url,
60
+ ),
61
+ path: this.request.path,
62
+ pathParameters: this.request.params
63
+ ? nullIfEmpty(this.request.params)
64
+ : null,
65
+ queryStringParameters: this.request.query
66
+ ? parseQueryStringParameters(this.request.query)
67
+ : null,
68
+ requestContext: {
69
+ accountId: process.env.AWS_ACCOUNT_ID || null,
70
+ apiId: null,
71
+ authorizer: null,
72
+ domainName: null,
73
+ domainPrefix: null,
74
+ extendedRequestId: cuid(),
75
+ httpMethod: this.request.method
76
+ ? this.request.method.toUpperCase()
77
+ : null,
78
+ identity: {
79
+ accessKey: null,
80
+ accountId: process.env.AWS_ACCOUNT_ID || null,
81
+ caller: null,
82
+ apiKey: null,
83
+ apiKeyId: null,
84
+ clientCert: null,
85
+ cognitoAuthenticationProvider: null,
86
+ cognitoAuthenticationType: null,
87
+ cognitoIdentityId: null,
88
+ cognitoIdentityPoolId: null,
89
+ principalOrgId: null,
90
+ sourceIp:
91
+ <string>this.request.headers?.["x-forwarded-for"] ||
92
+ this.request.socket.remoteAddress,
93
+ user: null,
94
+ userAgent: this.request.headers
95
+ ? this.request.headers["user-agent"]
96
+ : null,
97
+ userArn: null,
98
+ },
99
+ path: this.request.path,
100
+ protocol: "HTTP/1.1",
101
+ requestId: `${cuid()}-${cuid()}`,
102
+ requestTime: new Date().toISOString(),
103
+ requestTimeEpoch: Date.now(),
104
+ resourceId: null,
105
+ resourcePath: Globals.Listener_HTTP_ProxyRoute,
106
+ stage: process.env.STAGE,
107
+ },
108
+ resource: Globals.Listener_HTTP_ProxyRoute,
109
+ stageVariables: null,
110
+ }
111
+ }
112
+ private buildContext(
113
+ event: APIGatewayProxyEvent,
114
+ callback: (err?: Error | string, data?: any) => void,
115
+ ): Context {
116
+ return {
117
+ awsRequestId: event.requestContext.requestId,
118
+ callbackWaitsForEmptyEventLoop: true,
119
+ getRemainingTimeInMillis: () => {
120
+ return 0
121
+ },
122
+ done: (err, data) => callback(err, data),
123
+ fail: (err) => callback(err),
124
+ succeed: (res) => callback(null, res),
125
+ functionName: null,
126
+ functionVersion: "LATEST",
127
+ invokedFunctionArn: null,
128
+ memoryLimitInMB: "-1",
129
+ logGroupName: null,
130
+ logStreamName: null,
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,5 @@
1
+ import { Request, Response } from "express"
2
+
3
+ export default async (_request: Request, res: Response) => {
4
+ return res.send("Healthy!")
5
+ }
@@ -0,0 +1,107 @@
1
+ import { Server as HTTPServer, createServer } from "http"
2
+
3
+ import cors from "cors"
4
+ import * as express from "express"
5
+
6
+ import Server from "./../Server"
7
+ import GenericHandler from "./GenericHandler"
8
+ import HealthHandler from "./HealthHandler"
9
+ import { version as appVersion } from "../../../../package.json"
10
+ import Globals from "../../../Globals"
11
+ import { RouterConfig } from "../../Router"
12
+
13
+ export default class Proxy {
14
+ private stopping: boolean
15
+ private readonly config: RouterConfig
16
+ private readonly app: express.Express
17
+ private readonly serverlessHandler: Server["handleServerlessEvent"]
18
+ private listener: HTTPServer
19
+ constructor(
20
+ config: RouterConfig,
21
+ serverlessHandler: Server["handleServerlessEvent"],
22
+ ) {
23
+ this.stopping = false
24
+ this.config = config
25
+ this.serverlessHandler = serverlessHandler
26
+ this.app = express()
27
+ this.app.use(
28
+ cors(
29
+ this.config.cors
30
+ ? {
31
+ options: {
32
+ origin: this.config?.cors?.origin,
33
+ allowedHeaders: this.config?.cors?.headers,
34
+ credentials: !!this.config?.cors?.allowCredentials,
35
+ },
36
+ }
37
+ : {},
38
+ ),
39
+ )
40
+
41
+ // //This supposedly fix some 502 codes where nodejs socket would hang during
42
+ // //a request and if behind ALB, it would cause 502 codes. Had experiencied this
43
+ // //and 502 codes reduced dramastically, but still some appearances. Maybe this
44
+ // //is just a palliative work-around for the real issue; TODO: need to investigate
45
+ // //in the future.
46
+ // this.listener.listener.keepAliveTimeout = 120e3
47
+ // this.listener.listener.headersTimeout = 120e3
48
+ }
49
+ async load() {
50
+ await this.startListeners()
51
+ this.installRoutes()
52
+ }
53
+ async unload(err) {
54
+ await this.stopListeners(err)
55
+ }
56
+ /* lifecycle */
57
+ private async startListeners(): Promise<void> {
58
+ return new Promise((resolve) => {
59
+ const port = this.config.port || Globals.Listener_HTTP_DefaultPort
60
+ console.log(`[Proxy] - [STARTING] - v.${appVersion} - :${port}`)
61
+ // Create Server
62
+ this.listener = createServer(this.app)
63
+ // Set defaults
64
+ this.listener.setTimeout(
65
+ this.config.timeout || Globals.Listener_HTTP_DefaultTimeout,
66
+ )
67
+ // Start Server
68
+ this.listener.listen(port, () => {
69
+ console.log(`[Proxy] - [STARTED]`)
70
+ resolve()
71
+ })
72
+ })
73
+ }
74
+ private async stopListeners(err) {
75
+ if (this.stopping) return
76
+ this.stopping = true
77
+ console.debug("[Proxy] - [STOPPING]")
78
+ this.listener.close((_err) => {
79
+ if (err || _err) console.log("[Proxy] - exit output:", err || _err)
80
+ console.log("[Proxy] - [STOPPED]")
81
+ process.exit(err || _err ? 1 : 0)
82
+ })
83
+ }
84
+ /* routing */
85
+ private installRoutes() {
86
+ //Health check route -- This is a bypass route to only check if
87
+ //runtime proxy is working and responding to calls.
88
+ console.log(
89
+ `[Proxy] - [HEALTH-ROUTE] - ${
90
+ this.config.healthCheckRoute ||
91
+ Globals.Listener_HTTP_DefaultHealthCheckRoute
92
+ }`,
93
+ )
94
+ this.app
95
+ .route(
96
+ this.config.healthCheckRoute ||
97
+ Globals.Listener_HTTP_DefaultHealthCheckRoute,
98
+ )
99
+ .get(HealthHandler)
100
+ //Main route -- We use a wildcard route because is not the job of the runtime and neither
101
+ //the task to deny/constrain routes that invoked this task; all the job is done by the
102
+ //load balancer and we just foward everything we have to the function.
103
+ this.app
104
+ .route(Globals.Listener_HTTP_ProxyRoute)
105
+ .all(GenericHandler(this.serverlessHandler))
106
+ }
107
+ }