@bitblit/ratchet-epsilon-common 6.0.146-alpha → 6.0.147-alpha

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 (164) hide show
  1. package/package.json +10 -9
  2. package/src/background/background-dynamo-log-table-handler.ts +44 -0
  3. package/src/background/background-entry.ts +4 -0
  4. package/src/background/background-execution-event-type.ts +9 -0
  5. package/src/background/background-execution-event.ts +9 -0
  6. package/src/background/background-execution-listener.ts +6 -0
  7. package/src/background/background-handler.ts +352 -0
  8. package/src/background/background-http-adapter-handler.ts +166 -0
  9. package/src/background/background-meta-response-internal.ts +5 -0
  10. package/src/background/background-process-handling.ts +6 -0
  11. package/src/background/background-process-log-table-entry.ts +11 -0
  12. package/src/background/background-queue-response-internal.ts +9 -0
  13. package/src/background/background-validator.ts +105 -0
  14. package/src/background/epsilon-background-process-error.ts +110 -0
  15. package/src/background/internal-background-entry.ts +10 -0
  16. package/src/background/manager/abstract-background-manager.ts +120 -0
  17. package/src/background/manager/aws-large-payload-s3-sqs-sns-background-manager.ts +87 -0
  18. package/src/background/manager/aws-sqs-sns-background-manager.ts +201 -0
  19. package/src/background/manager/background-manager-like.ts +44 -0
  20. package/src/background/manager/background-manager.spec.ts +89 -0
  21. package/src/background/manager/single-thread-local-background-manager.ts +58 -0
  22. package/src/background/s3-background-transaction-logger.ts +65 -0
  23. package/src/build/ratchet-epsilon-common-info.ts +19 -0
  24. package/src/built-in/background/echo-processor.ts +17 -0
  25. package/src/built-in/background/log-and-enqueue-echo-processor.ts +14 -0
  26. package/src/built-in/background/log-message-background-error-processor.ts +10 -0
  27. package/src/built-in/background/no-op-processor.ts +12 -0
  28. package/src/built-in/background/retry-processor.ts +51 -0
  29. package/src/built-in/background/sample-delay-processor.ts +15 -0
  30. package/src/built-in/background/sample-input-validated-processor-data.ts +4 -0
  31. package/src/built-in/background/sample-input-validated-processor.ts +14 -0
  32. package/src/built-in/built-in-trace-id-generators.ts +22 -0
  33. package/src/built-in/daemon/daemon-authorizer-function.ts +4 -0
  34. package/src/built-in/daemon/daemon-config.ts +9 -0
  35. package/src/built-in/daemon/daemon-group-selection-function.ts +3 -0
  36. package/src/built-in/daemon/daemon-handler.ts +87 -0
  37. package/src/built-in/daemon/daemon-process-state-list.ts +9 -0
  38. package/src/built-in/http/apollo/apollo-util.ts +43 -0
  39. package/src/built-in/http/apollo/default-epsilon-apollo-context.ts +11 -0
  40. package/src/built-in/http/apollo/epsilon-apollo-context-builder-options.ts +5 -0
  41. package/src/built-in/http/apollo/epsilon-lambda-apollo-context-function-argument.ts +6 -0
  42. package/src/built-in/http/apollo/epsilon-lambda-apollo-options.ts +11 -0
  43. package/src/built-in/http/apollo-filter.ts +151 -0
  44. package/src/built-in/http/built-in-auth-filters.ts +73 -0
  45. package/src/built-in/http/built-in-authorizers.ts +22 -0
  46. package/src/built-in/http/built-in-filters.spec.ts +26 -0
  47. package/src/built-in/http/built-in-filters.ts +300 -0
  48. package/src/built-in/http/built-in-handlers.ts +85 -0
  49. package/src/built-in/http/log-level-manipulation-filter.ts +26 -0
  50. package/src/built-in/http/run-handler-as-filter.spec.ts +67 -0
  51. package/src/built-in/http/run-handler-as-filter.ts +102 -0
  52. package/src/cli/ratchet-cli-handler.ts +23 -0
  53. package/src/cli/run-background-process-from-command-line.ts +32 -0
  54. package/src/config/background/background-aws-config.ts +8 -0
  55. package/src/config/background/background-config.ts +15 -0
  56. package/src/config/background/background-error-processor.ts +5 -0
  57. package/src/config/background/background-processor.ts +14 -0
  58. package/src/config/background/background-transaction-log.ts +9 -0
  59. package/src/config/background/background-transaction-logger.ts +6 -0
  60. package/src/config/cron/abstract-cron-entry.ts +17 -0
  61. package/src/config/cron/cron-background-entry.ts +17 -0
  62. package/src/config/cron/cron-config.ts +10 -0
  63. package/src/config/dynamo-db-config.ts +6 -0
  64. package/src/config/epsilon-config.ts +30 -0
  65. package/src/config/epsilon-lambda-event-handler.ts +12 -0
  66. package/src/config/epsilon-logger-config.ts +23 -0
  67. package/src/config/espilon-server-mode.ts +10 -0
  68. package/src/config/generic-aws-event-handler-function.ts +1 -0
  69. package/src/config/http/authorizer-function.ts +9 -0
  70. package/src/config/http/epsilon-authorization-context.ts +5 -0
  71. package/src/config/http/epsilon-cors-approach.ts +7 -0
  72. package/src/config/http/extended-api-gateway-event.ts +8 -0
  73. package/src/config/http/filter-chain-context.ts +15 -0
  74. package/src/config/http/filter-function.ts +3 -0
  75. package/src/config/http/handler-function.ts +4 -0
  76. package/src/config/http/http-config.ts +27 -0
  77. package/src/config/http/http-processing-config.ts +23 -0
  78. package/src/config/http/mapped-http-processing-config.ts +12 -0
  79. package/src/config/http/null-returned-object-handling.ts +7 -0
  80. package/src/config/inter-api/inter-api-aws-config.ts +5 -0
  81. package/src/config/inter-api/inter-api-config.ts +7 -0
  82. package/src/config/inter-api/inter-api-process-mapping.ts +11 -0
  83. package/src/config/local-server/local-server-event-logging-style.ts +8 -0
  84. package/src/config/local-server/local-server-http-method-handling.ts +7 -0
  85. package/src/config/local-server/local-server-options.ts +12 -0
  86. package/src/config/logging-trace-id-generator.ts +3 -0
  87. package/src/config/no-handlers-found-error.ts +6 -0
  88. package/src/config/open-api/open-api-document-components.ts +4 -0
  89. package/src/config/open-api/open-api-document.ts +7 -0
  90. package/src/config/s3-config.ts +8 -0
  91. package/src/config/sns-config.ts +7 -0
  92. package/src/config/sqs-config.ts +7 -0
  93. package/src/epsilon-build-properties.ts +21 -0
  94. package/src/epsilon-constants.ts +62 -0
  95. package/src/epsilon-global-handler.ts +238 -0
  96. package/src/epsilon-instance.ts +20 -0
  97. package/src/epsilon-logging-extension-processor.ts +19 -0
  98. package/src/http/auth/api-gateway-adapter-authentication-handler.ts +95 -0
  99. package/src/http/auth/auth0-web-token-manipulator.ts +69 -0
  100. package/src/http/auth/basic-auth-token.ts +7 -0
  101. package/src/http/auth/google-web-token-manipulator.spec.ts +15 -0
  102. package/src/http/auth/google-web-token-manipulator.ts +80 -0
  103. package/src/http/auth/jwt-ratchet-local-web-token-manipulator.ts +37 -0
  104. package/src/http/auth/local-web-token-manipulator.spec.ts +34 -0
  105. package/src/http/auth/local-web-token-manipulator.ts +114 -0
  106. package/src/http/auth/web-token-manipulator.ts +9 -0
  107. package/src/http/error/bad-gateway.ts +11 -0
  108. package/src/http/error/bad-request-error.ts +11 -0
  109. package/src/http/error/conflict-error.ts +12 -0
  110. package/src/http/error/forbidden-error.ts +12 -0
  111. package/src/http/error/gateway-timeout.ts +12 -0
  112. package/src/http/error/method-not-allowed-error.ts +12 -0
  113. package/src/http/error/misconfigured-error.ts +12 -0
  114. package/src/http/error/not-found-error.ts +12 -0
  115. package/src/http/error/not-implemented.ts +12 -0
  116. package/src/http/error/request-timeout-error.ts +12 -0
  117. package/src/http/error/service-unavailable.ts +12 -0
  118. package/src/http/error/too-many-requests-error.ts +12 -0
  119. package/src/http/error/unauthorized-error.ts +12 -0
  120. package/src/http/event-util.spec.ts +190 -0
  121. package/src/http/event-util.ts +272 -0
  122. package/src/http/response-util.spec.ts +117 -0
  123. package/src/http/response-util.ts +164 -0
  124. package/src/http/route/epsilon-router.ts +9 -0
  125. package/src/http/route/extended-auth-response-context.ts +7 -0
  126. package/src/http/route/route-and-parse.ts +8 -0
  127. package/src/http/route/route-mapping.ts +21 -0
  128. package/src/http/route/route-validator-config.ts +5 -0
  129. package/src/http/route/router-util.spec.ts +33 -0
  130. package/src/http/route/router-util.ts +314 -0
  131. package/src/http/web-handler.spec.ts +99 -0
  132. package/src/http/web-handler.ts +157 -0
  133. package/src/http/web-v2-handler.ts +34 -0
  134. package/src/inter-api/inter-api-entry.ts +8 -0
  135. package/src/inter-api/inter-api-util.spec.ts +77 -0
  136. package/src/inter-api/inter-api-util.ts +71 -0
  137. package/src/inter-api-manager.ts +75 -0
  138. package/src/lambda-event-handler/cron-epsilon-lambda-event-handler.spec.ts +130 -0
  139. package/src/lambda-event-handler/cron-epsilon-lambda-event-handler.ts +132 -0
  140. package/src/lambda-event-handler/dynamo-epsilon-lambda-event-handler.ts +42 -0
  141. package/src/lambda-event-handler/generic-sns-epsilon-lambda-event-handler.ts +38 -0
  142. package/src/lambda-event-handler/generic-sqs-epsilon-lambda-event-handler.ts +43 -0
  143. package/src/lambda-event-handler/inter-api-epsilon-lambda-event-handler.ts +33 -0
  144. package/src/lambda-event-handler/s3-epsilon-lambda-event-handler.ts +50 -0
  145. package/src/local-container-server.ts +128 -0
  146. package/src/local-server.spec.ts +16 -0
  147. package/src/local-server.ts +426 -0
  148. package/src/open-api-util/open-api-doc-modifications.ts +9 -0
  149. package/src/open-api-util/open-api-doc-modifier.spec.ts +22 -0
  150. package/src/open-api-util/open-api-doc-modifier.ts +90 -0
  151. package/src/open-api-util/yaml-combiner.spec.ts +26 -0
  152. package/src/open-api-util/yaml-combiner.ts +35 -0
  153. package/src/sample/sample-server-components-with-apollo.ts +87 -0
  154. package/src/sample/sample-server-components.ts +183 -0
  155. package/src/sample/sample-server-static-files.ts +614 -0
  156. package/src/sample/test-error-server.ts +140 -0
  157. package/src/util/aws-util.ts +89 -0
  158. package/src/util/context-global-data.ts +13 -0
  159. package/src/util/context-util.ts +156 -0
  160. package/src/util/cron-util.spec.ts +190 -0
  161. package/src/util/cron-util.ts +86 -0
  162. package/src/util/epsilon-config-parser.ts +90 -0
  163. package/src/util/epsilon-server-util.spec.ts +18 -0
  164. package/src/util/epsilon-server-util.ts +16 -0
@@ -0,0 +1,50 @@
1
+ import { EpsilonLambdaEventHandler } from '../config/epsilon-lambda-event-handler.js';
2
+ import { Context, ProxyResult, S3Event } from 'aws-lambda';
3
+ import { GenericAwsEventHandlerFunction } from '../config/generic-aws-event-handler-function.js';
4
+ import { Logger } from '@bitblit/ratchet-common/logger/logger';
5
+ import { AwsUtil } from '../util/aws-util.js';
6
+ import { EpsilonInstance } from '../epsilon-instance.js';
7
+ import { LambdaEventDetector } from '@bitblit/ratchet-aws/lambda/lambda-event-detector';
8
+
9
+ export class S3EpsilonLambdaEventHandler implements EpsilonLambdaEventHandler<S3Event> {
10
+ constructor(private _epsilon: EpsilonInstance) {}
11
+
12
+ public extractLabel(evt: S3Event, _context: Context): string {
13
+ return 'S3Evt:' + evt.Records[0].eventSource;
14
+ }
15
+
16
+ public handlesEvent(evt: any): boolean {
17
+ return LambdaEventDetector.isValidS3Event(evt);
18
+ }
19
+
20
+ public async processEvent(evt: S3Event, _context: Context): Promise<ProxyResult> {
21
+ let rval: any = null;
22
+ if (this._epsilon.config && this._epsilon.config.s3 && evt && evt.Records.length > 0) {
23
+ const finder: string = evt.Records[0].s3.bucket.name + '/' + evt.Records[0].s3.object.key;
24
+ const isRemoveEvent: boolean = evt.Records[0].eventName && evt.Records[0].eventName.startsWith('ObjectRemoved');
25
+
26
+ if (isRemoveEvent) {
27
+ const handler: GenericAwsEventHandlerFunction<S3Event> = AwsUtil.findInMap<GenericAwsEventHandlerFunction<S3Event>>(
28
+ finder,
29
+ this._epsilon.config.s3.removeHandlers,
30
+ );
31
+ if (handler) {
32
+ rval = await handler(evt);
33
+ } else {
34
+ Logger.info('Found no s3 create handler for : %s', finder);
35
+ }
36
+ } else {
37
+ const handler: GenericAwsEventHandlerFunction<S3Event> = AwsUtil.findInMap<GenericAwsEventHandlerFunction<S3Event>>(
38
+ finder,
39
+ this._epsilon.config.s3.createHandlers,
40
+ );
41
+ if (handler) {
42
+ rval = await handler(evt);
43
+ } else {
44
+ Logger.info('Found no s3 remove handler for : %s', finder);
45
+ }
46
+ }
47
+ }
48
+ return rval;
49
+ }
50
+ }
@@ -0,0 +1,128 @@
1
+ import { APIGatewayEvent, Context, ProxyResult, SNSEvent } from "aws-lambda";
2
+ import { IncomingMessage, Server, ServerResponse } from "http";
3
+ import { EventUtil } from "./http/event-util.js";
4
+ import fetch from "cross-fetch";
5
+ import { LocalServer } from "./local-server.js";
6
+ import { Logger } from "@bitblit/ratchet-common/logger/logger";
7
+ import { StringRatchet } from "@bitblit/ratchet-common/lang/string-ratchet";
8
+ import { LoggerLevelName } from "@bitblit/ratchet-common/logger/logger-level-name";
9
+ import { CliRatchet } from "@bitblit/ratchet-node-only/cli/cli-ratchet";
10
+ import { LocalServerOptions } from "./config/local-server/local-server-options.js";
11
+ import { LocalServerHttpMethodHandling } from "./config/local-server/local-server-http-method-handling.js";
12
+ import { BackgroundEntry } from "./background/background-entry.js";
13
+ import { LocalServerEventLoggingStyle } from "./config/local-server/local-server-event-logging-style.ts";
14
+
15
+ /**
16
+ * A simplistic server for testing your lambdas-in-container locally
17
+ */
18
+ export class LocalContainerServer {
19
+ private server: Server;
20
+ private aborted: boolean = false;
21
+ private options: LocalServerOptions;
22
+
23
+ constructor(
24
+ private containerUrl: string = 'http://localhost:9000/2015-03-31/functions/function/invocations',
25
+ inOpts?: LocalServerOptions
26
+ ) {
27
+ this.options = {
28
+ port: inOpts?.port ?? 8889,
29
+ https: inOpts?.https ?? false,
30
+ methodHandling: inOpts?.methodHandling ?? LocalServerHttpMethodHandling.Uppercase,
31
+ eventLoggingLevel: inOpts?.eventLoggingLevel ?? LoggerLevelName.debug,
32
+ eventLoggingStyle: inOpts?.eventLoggingStyle ?? LocalServerEventLoggingStyle.Summary,
33
+ graphQLIntrospectionEventLogLevel: inOpts?.graphQLIntrospectionEventLogLevel ?? LoggerLevelName.silly
34
+ };
35
+ }
36
+
37
+ public async runServer(): Promise<boolean> {
38
+ return new Promise<boolean>((res, rej) => {
39
+ try {
40
+ Logger.info('Starting Epsilon container-wrapper server on port %d calling to %s', this.options.port, this.containerUrl);
41
+ this.server = LocalServer.createNodeServer(this.options, this.requestHandler.bind(this));
42
+ Logger.info('Epsilon server is listening');
43
+
44
+ // Also listen for SIGINT
45
+ process.on('SIGINT', () => {
46
+ Logger.info('Caught SIGINT - shutting down test server...');
47
+ this.aborted = true;
48
+ res(true);
49
+ });
50
+ } catch (err) {
51
+ Logger.error('Local server failed : %s', err, err);
52
+ rej(err);
53
+ }
54
+ });
55
+ }
56
+
57
+ async requestHandler(request: IncomingMessage, response: ServerResponse): Promise<any> {
58
+ const context: Context = {
59
+ awsRequestId: 'LOCAL-' + StringRatchet.createType4Guid(),
60
+ getRemainingTimeInMillis(): number {
61
+ return 300000;
62
+ },
63
+ } as Context; //TBD
64
+ const evt: APIGatewayEvent = await LocalServer.messageToApiGatewayEvent(request, context, this.options);
65
+ const logEventLevel: LoggerLevelName = EventUtil.eventIsAGraphQLIntrospection(evt) ? LoggerLevelName.silly : LoggerLevelName.info;
66
+
67
+ Logger.logByLevel(logEventLevel, 'Processing event: %j', evt);
68
+
69
+ const respBodyText: string = null;
70
+ let result: ProxyResult;
71
+ if (evt.path == '/epsilon-poison-pill') {
72
+ this.aborted = true;
73
+ return true;
74
+ } else if (evt.path.startsWith('/epsilon-background-launcher')) {
75
+ // Shows a simple page for launching background tasks
76
+ Logger.info('Showing background launcher page');
77
+ response.end(LocalServer.buildBackgroundTriggerFormHtml());
78
+ return true;
79
+ } else if (evt.path.startsWith('/epsilon-background-trigger')) {
80
+ Logger.info('Running background trigger');
81
+ try {
82
+ const bgEntry: BackgroundEntry<any> = LocalServer.parseEpsilonBackgroundTriggerAsTask(evt);
83
+ const snsEvent: SNSEvent = LocalServer.createBackgroundSNSEvent(bgEntry);
84
+ const postResp: Response = await fetch(this.containerUrl, { method: 'POST', body: JSON.stringify(snsEvent) });
85
+ let postProxy: ProxyResult = await postResp.json();
86
+ if (!LocalServer.isProxyResult(postProxy)) {
87
+ Logger.error('Upstream returned something not a proxy result - forcing');
88
+ postProxy = {
89
+ statusCode: 500,
90
+ body: JSON.stringify({'failure':'Upstream did not return a proxyResult', contents: postProxy})
91
+ };
92
+ }
93
+
94
+ const written: boolean = await LocalServer.writeProxyResultToServerResponse(postProxy, response, evt, this.options);
95
+ return written;
96
+ } catch (err) {
97
+ response.end(`<html><body>BG TRIGGER FAILED : Error : ${err}</body></html>`);
98
+ return true;
99
+ }
100
+ } else {
101
+ try {
102
+ const postResp: Response = await fetch(this.containerUrl, { method: 'POST', body: JSON.stringify(evt) });
103
+ const postProxy: ProxyResult = await postResp.json();
104
+ const written: boolean = await LocalServer.writeProxyResultToServerResponse(postProxy, response, evt, this.options);
105
+ return written;
106
+ } catch (err) {
107
+ Logger.error('Failed: %s : Body was %s Response was : %j', err, respBodyText, result, err);
108
+ return '{"bad":true}';
109
+ }
110
+ }
111
+ }
112
+
113
+
114
+ public static async runFromCliArgs(_args: string[]): Promise<void> {
115
+ try {
116
+ Logger.setLevel(LoggerLevelName.debug);
117
+ Logger.debug('Running local container server : %j', process?.argv);
118
+ const _postArgs: string[] = CliRatchet.argsAfterCommand(['run-local-container-server']);
119
+ const testServer: LocalContainerServer = new LocalContainerServer();
120
+ await testServer.runServer();
121
+ Logger.info('Got res server');
122
+ process.exit(0);
123
+ } catch (err) {
124
+ Logger.error('Error : %s', err);
125
+ process.exit(1);
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,16 @@
1
+ import { LocalServer } from './local-server.js';
2
+ import { describe, expect, test } from 'vitest';
3
+
4
+ describe('#localServer', function () {
5
+ test('should not URL decode query string parameters', async () => {
6
+ const queryParams: Record<string, string> = LocalServer.parseQueryParamsFromUrlString(
7
+ 'http://localhost?test=fish+chips&test2=chicken%2bbeef&test3=ketchup%20mustard&test4=&test5=cat=dog',
8
+ );
9
+
10
+ expect(queryParams['test']).toBe('fish+chips');
11
+ expect(queryParams['test2']).toBe('chicken%2bbeef');
12
+ expect(queryParams['test3']).toBe('ketchup%20mustard');
13
+ expect(queryParams['test4']).toBe('');
14
+ expect(queryParams['test5']).toBe('cat=dog');
15
+ });
16
+ });
@@ -0,0 +1,426 @@
1
+ import { APIGatewayEvent, Context, ProxyResult, SNSEvent } from 'aws-lambda';
2
+ import { Logger } from '@bitblit/ratchet-common/logger/logger';
3
+ import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet';
4
+ import { LoggerLevelName } from '@bitblit/ratchet-common/logger/logger-level-name';
5
+ import { Base64Ratchet } from '@bitblit/ratchet-common/lang/base64-ratchet';
6
+ import { JwtTokenBase } from '@bitblit/ratchet-common/jwt/jwt-token-base';
7
+ import http, { IncomingMessage, RequestListener, Server, ServerResponse } from 'http';
8
+ import https from 'https';
9
+ import { DateTime } from 'luxon';
10
+ import { EventUtil } from './http/event-util.js';
11
+ import { EpsilonGlobalHandler } from './epsilon-global-handler.js';
12
+ import { SampleServerComponents } from './sample/sample-server-components.js';
13
+ import { LocalWebTokenManipulator } from './http/auth/local-web-token-manipulator.js';
14
+ import { LocalServerOptions } from './config/local-server/local-server-options.js';
15
+ import { LocalServerHttpMethodHandling } from './config/local-server/local-server-http-method-handling.js';
16
+ import { LocalServerCert } from '@bitblit/ratchet-node-only/http/local-server-cert';
17
+ import { EpsilonConstants } from './epsilon-constants.js';
18
+ import { InternalBackgroundEntry } from './background/internal-background-entry.js';
19
+ import { AbstractBackgroundManager } from './background/manager/abstract-background-manager.js';
20
+ import { BackgroundEntry } from './background/background-entry.js';
21
+ import { ErrorRatchet } from '@bitblit/ratchet-common/lang/error-ratchet';
22
+ import { ResponseUtil } from "./http/response-util.js";
23
+ import { NumberRatchet } from "@bitblit/ratchet-common/lang/number-ratchet";
24
+ import { LocalServerEventLoggingStyle } from "./config/local-server/local-server-event-logging-style.ts";
25
+
26
+ /**
27
+ * A simplistic server for testing your lambdas locally
28
+ */
29
+ export class LocalServer {
30
+ private server: Server;
31
+ private options: LocalServerOptions;
32
+
33
+ constructor(
34
+ private globalHandler: EpsilonGlobalHandler,
35
+ inOpts?: LocalServerOptions
36
+ ) {
37
+ this.options = {
38
+ port: inOpts?.port ?? 8888,
39
+ https: inOpts?.https ?? false,
40
+ methodHandling: inOpts?.methodHandling ?? LocalServerHttpMethodHandling.Lowercase,
41
+ eventLoggingLevel: inOpts?.eventLoggingLevel ?? LoggerLevelName.debug,
42
+ eventLoggingStyle: inOpts?.eventLoggingStyle ?? LocalServerEventLoggingStyle.Summary,
43
+ graphQLIntrospectionEventLogLevel: inOpts?.graphQLIntrospectionEventLogLevel ?? LoggerLevelName.silly
44
+ };
45
+ }
46
+
47
+ async runServer(): Promise<boolean> {
48
+ return new Promise<boolean>((res, rej) => {
49
+ try {
50
+ Logger.info('Starting Epsilon server on port %d', this.options.port);
51
+
52
+ this.server = LocalServer.createNodeServer(this.options, this.requestHandler.bind(this));
53
+
54
+ Logger.info('Epsilon server is listening');
55
+
56
+ // Also listen for SIGINT
57
+ process.on('SIGINT', () => {
58
+ Logger.info('Caught SIGINT - shutting down test server...');
59
+ this.server.close();
60
+ res(true);
61
+ });
62
+ } catch (err) {
63
+ Logger.error('Local server failed : %s', err, err);
64
+ rej(err);
65
+ }
66
+ });
67
+ }
68
+
69
+ public static createNodeServer(opts: LocalServerOptions, requestHandler: RequestListener): Server {
70
+ let rval: Server = null;
71
+ if (opts.https) {
72
+ const options = {
73
+ key: LocalServerCert.CLIENT_KEY_PEM,
74
+ cert: LocalServerCert.CLIENT_CERT_PEM,
75
+ };
76
+ Logger.info(
77
+ 'Starting https server - THIS SERVER IS NOT SECURE! The KEYS are in the code! Testing Server Only - Use at your own risk!',
78
+ );
79
+ rval = https.createServer(options, requestHandler).listen(opts.port);
80
+ } else {
81
+ rval = http.createServer(requestHandler).listen(opts.port);
82
+ }
83
+ return rval;
84
+ }
85
+
86
+ async requestHandler(request: IncomingMessage, response: ServerResponse): Promise<any> {
87
+ const context: Context = {
88
+ awsRequestId: 'LOCAL-' + StringRatchet.createType4Guid(),
89
+ getRemainingTimeInMillis(): number {
90
+ return 300000;
91
+ },
92
+ } as Context; //TBD
93
+ const evt: APIGatewayEvent = await LocalServer.messageToApiGatewayEvent(request, context, this.options);
94
+
95
+ if (evt.path.startsWith('/epsilon-poison-pill')) {
96
+ this.server.close(() => {
97
+ Logger.info('Server closed');
98
+ });
99
+ return true;
100
+ } else if (evt.path.startsWith('/epsilon-background-launcher')) {
101
+ // Shows a simple page for launching background tasks
102
+ Logger.info('Showing background launcher page');
103
+ const names: string[] = this.globalHandler.epsilon.backgroundHandler.validProcessorNames;
104
+ response.end(LocalServer.buildBackgroundTriggerFormHtml(names));
105
+ return true;
106
+ } else if (evt.path.startsWith('/epsilon-background-trigger')) {
107
+ Logger.info('Running background trigger');
108
+ try {
109
+ const entry: BackgroundEntry<any> = LocalServer.parseEpsilonBackgroundTriggerAsTask(evt);
110
+ const processed: boolean = await this.globalHandler.processSingleBackgroundEntry(entry);
111
+ response.end(`<html><body>BG TRIGGER VALID, returned ${processed} : task : ${entry.type} : data: ${entry.data}</body></html>`);
112
+ } catch (err) {
113
+ response.end(`<html><body>BG TRIGGER FAILED : Error : ${err}</body></html>`);
114
+ }
115
+ return true;
116
+ } else {
117
+ const result: ProxyResult = await this.globalHandler.lambdaHandler(evt, context);
118
+ const written: boolean = await LocalServer.writeProxyResultToServerResponse(result, response, evt, this.options);
119
+ return written;
120
+ }
121
+ }
122
+
123
+ public static parseEpsilonBackgroundTriggerAsTask(evt: APIGatewayEvent): BackgroundEntry<any> {
124
+ Logger.info('Running background trigger');
125
+ // Fires off the event as a SNS event instead of a HTTP event
126
+ const taskName: string = StringRatchet.trimToNull(evt.queryStringParameters['task']);
127
+ let dataJson: string = StringRatchet.trimToNull(evt.queryStringParameters['dataJson']);
128
+ let metaJson: string = StringRatchet.trimToNull(evt.queryStringParameters['metaJson']);
129
+ // Use this to match what AWS gateways do
130
+ dataJson = dataJson ? ResponseUtil.decodeUriComponentAndReplacePlus(dataJson) : dataJson;
131
+ metaJson = metaJson ? ResponseUtil.decodeUriComponentAndReplacePlus(metaJson) : metaJson;
132
+ let error: string = '';
133
+
134
+ error += taskName ? '' : 'No task provided';
135
+ let data: any = null;
136
+ let _meta: any = null;
137
+ try {
138
+ if (dataJson) {
139
+ data = JSON.parse(dataJson);
140
+ }
141
+ } catch (err) {
142
+ error += 'Data is not valid JSON : ' + err+ ' WAS: '+dataJson;
143
+ }
144
+ try {
145
+ if (metaJson) {
146
+ _meta = JSON.parse(metaJson);
147
+ }
148
+ } catch (err) {
149
+ error += 'Meta is not valid JSON : ' + err + ' WAS: '+metaJson;
150
+ }
151
+
152
+ if (error.length > 0) {
153
+ throw ErrorRatchet.throwFormattedErr('Errors %j', error);
154
+ }
155
+ const rval: BackgroundEntry<any> = {
156
+ type: taskName,
157
+ data: data,
158
+ //meta: meta
159
+ };
160
+ return rval;
161
+ }
162
+
163
+ public static async bodyAsBase64String(request: IncomingMessage): Promise<string> {
164
+ return new Promise<string>((res, _rej) => {
165
+ const body = [];
166
+ request.on('data', (chunk) => {
167
+ body.push(chunk);
168
+ });
169
+ request.on('end', () => {
170
+ const rval: string = Buffer.concat(body).toString('base64');
171
+ res(rval);
172
+ });
173
+ });
174
+ }
175
+
176
+ public static async messageToApiGatewayEvent(
177
+ request: IncomingMessage,
178
+ context: Context,
179
+ options: LocalServerOptions,
180
+ ): Promise<APIGatewayEvent> {
181
+ const bodyString: string = await LocalServer.bodyAsBase64String(request);
182
+ const stageIdx: number = request.url.indexOf('/', 1);
183
+ const stage: string = request.url.substring(1, stageIdx);
184
+ const path: string = request.url.substring(stageIdx + 1);
185
+
186
+ const reqTime: number = new Date().getTime();
187
+ const formattedTime: string = DateTime.utc().toFormat('dd/MMM/yyyy:hh:mm:ss ZZ');
188
+ const queryStringParams: Record<string, string> = LocalServer.parseQueryParamsFromUrlString(path);
189
+ const headers: any = Object.assign({}, request.headers);
190
+ headers['X-Forwarded-Proto'] = 'http'; // This server is always unencrypted
191
+
192
+ // Some want one way, some want the other!
193
+ let targetMethod: string = StringRatchet.trimToEmpty(request.method);
194
+ targetMethod = options?.methodHandling === LocalServerHttpMethodHandling.Lowercase ? targetMethod.toLowerCase() : targetMethod;
195
+ targetMethod = options?.methodHandling === LocalServerHttpMethodHandling.Uppercase ? targetMethod.toUpperCase() : targetMethod;
196
+
197
+ const rval: APIGatewayEvent = {
198
+ body: bodyString,
199
+ multiValueHeaders: {},
200
+ multiValueQueryStringParameters: {},
201
+ resource: '/{proxy+}',
202
+ path: request.url,
203
+ httpMethod: targetMethod,
204
+ isBase64Encoded: true,
205
+ queryStringParameters: queryStringParams,
206
+ pathParameters: {
207
+ proxy: path,
208
+ },
209
+ stageVariables: {
210
+ baz: 'qux',
211
+ },
212
+ headers: headers,
213
+ requestContext: {
214
+ accountId: '123456789012',
215
+ resourceId: '123456',
216
+ stage: stage,
217
+ requestId: context.awsRequestId,
218
+ requestTime: formattedTime, // '09/Apr/2015:12:34:56 +0000',
219
+ requestTimeEpoch: reqTime, //1428582896000,
220
+ identity: {
221
+ apiKeyId: null,
222
+ clientCert: null,
223
+ principalOrgId: null,
224
+ apiKey: null,
225
+ cognitoIdentityPoolId: null,
226
+ accountId: null,
227
+ cognitoIdentityId: null,
228
+ caller: null,
229
+ accessKey: null,
230
+ sourceIp: '127.0.0.1',
231
+ cognitoAuthenticationType: null,
232
+ cognitoAuthenticationProvider: null,
233
+ userArn: null,
234
+ userAgent: 'Custom User Agent String',
235
+ user: null,
236
+ vpcId: 'vpc1234',
237
+ vpceId: 'vpce1234'
238
+ },
239
+ path: request.url, // /prod/path/to/resource
240
+ domainName: request.headers['host'],
241
+ resourcePath: '/{proxy+}',
242
+ httpMethod: request.method.toLowerCase(),
243
+ apiId: '1234567890',
244
+ protocol: 'HTTP/1.1',
245
+ authorizer: null,
246
+ },
247
+ };
248
+
249
+ return rval;
250
+ }
251
+
252
+ public static createBackgroundSNSEvent(entry: BackgroundEntry<any>): SNSEvent {
253
+ const internal: InternalBackgroundEntry<any> = Object.assign({}, entry, {
254
+ createdEpochMS: new Date().getTime(),
255
+ guid: AbstractBackgroundManager.generateBackgroundGuid(),
256
+ traceId: 'FAKE-TRACE-' + StringRatchet.createType4Guid(),
257
+ traceDepth: 1,
258
+ });
259
+ const toWrite: any = {
260
+ type: EpsilonConstants.BACKGROUND_SNS_IMMEDIATE_RUN_FLAG,
261
+ backgroundEntry: internal,
262
+ };
263
+
264
+ const rval: SNSEvent = {
265
+ Records: [
266
+ {
267
+ EventVersion: '1.0',
268
+ EventSubscriptionArn: 'arn:aws:sns:us-east-1:123456789012:sns-lambda:21be56ed-a058-49f5-8c98-aedd2564c486',
269
+ EventSource: 'aws:sns',
270
+ Sns: {
271
+ SignatureVersion: '1',
272
+ Timestamp: '2019-01-02T12:45:07.000Z',
273
+ Signature: 'tcc6faL2yUC6dgZdmrwh1Y4cGa/ebXEkAi6RibDsvpi+tE/1+82j...65r==',
274
+ SigningCertUrl: 'https://sns.us-east-1.amazonaws.com/SimpleNotificationService-ac565b8b1a6c5d002d285f9598aa1d9b.pem',
275
+ MessageId: '95df01b4-ee98-5cb9-9903-4c221d41eb5e',
276
+ Message: JSON.stringify(toWrite),
277
+ MessageAttributes: {},
278
+ Type: 'Notification',
279
+ UnsubscribeUrl:
280
+ 'https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&amp;SubscriptionArn=arn:aws:sns:us-east-1:123456789012:test-lambda:21be56ed-a058-49f5-8c98-aedd2564c486',
281
+ TopicArn: 'arn:aws:sns:us-east-1:123456789012:sns-lambda',
282
+ Subject: 'EpsilonBackgroundInvoke',
283
+ },
284
+ },
285
+ ],
286
+ };
287
+ return rval;
288
+ }
289
+
290
+ public static isProxyResult(val: any): boolean {
291
+ // Other items are optional, but it must have a statusCode and a body
292
+ // Other are headers, isBase64Encoded, cookies
293
+ return val && NumberRatchet.safeNumber(val.statusCode)!==null && StringRatchet.trimToNull(val.body)!==null;
294
+ }
295
+
296
+ public static summarizeResponse(proxyResult: ProxyResult,sourceEvent: APIGatewayEvent): Record<string, any> {
297
+ const summary: Record <string,any> = {
298
+ srcPath: sourceEvent.path,
299
+ resultCode: proxyResult.statusCode,
300
+ resultIsBase64: proxyResult.isBase64Encoded,
301
+ resultBytes: (proxyResult.body ?? '').length
302
+ }
303
+ return summary;
304
+ }
305
+
306
+ public static async writeProxyResultToServerResponse(
307
+ proxyResult: ProxyResult,
308
+ response: ServerResponse,
309
+ sourceEvent: APIGatewayEvent, // Used to detect special logging cases
310
+ options: LocalServerOptions
311
+ ): Promise<boolean> {
312
+
313
+ const logEventLevel: LoggerLevelName = EventUtil.eventIsAGraphQLIntrospection(sourceEvent) ? options.graphQLIntrospectionEventLogLevel : options.eventLoggingLevel;
314
+
315
+ switch (options.eventLoggingStyle) {
316
+ case 'Full' : Logger.logByLevel(logEventLevel, 'Result: %j', proxyResult); break;
317
+ case 'FullWithBase64Decode':
318
+ if (proxyResult.isBase64Encoded) {
319
+ const dup: ProxyResult = structuredClone(proxyResult);
320
+ dup.body = Base64Ratchet.base64StringToString(dup.body);
321
+ dup.isBase64Encoded = false;
322
+ Logger.logByLevel(logEventLevel, 'Result (UB64): %j', dup);
323
+ } else {
324
+ Logger.logByLevel(logEventLevel, 'Result: %j', proxyResult);
325
+ }
326
+ break;
327
+ case 'Summary':
328
+ Logger.logByLevel(logEventLevel, 'Result (summary): %j', LocalServer.summarizeResponse(proxyResult, sourceEvent));
329
+ break;
330
+ case 'None':
331
+ // Do nothing
332
+ break;
333
+ default:
334
+ throw new Error('Should not happen - full enumeration');
335
+ }
336
+
337
+ response.statusCode = proxyResult.statusCode ?? 500
338
+ if (proxyResult.headers) {
339
+ Object.keys(proxyResult.headers).forEach((hk) => {
340
+ response.setHeader(hk, String(proxyResult.headers[hk]));
341
+ });
342
+ }
343
+ if (proxyResult.multiValueHeaders) {
344
+ Object.keys(proxyResult.multiValueHeaders).forEach((hk) => {
345
+ response.setHeader(hk, proxyResult.multiValueHeaders[hk].join(','));
346
+ });
347
+ }
348
+ const toWrite: Buffer = proxyResult.isBase64Encoded ? Buffer.from(proxyResult.body, 'base64') : Buffer.from(proxyResult.body);
349
+
350
+ response.end(toWrite);
351
+ return !!proxyResult.body;
352
+ }
353
+
354
+ /**
355
+ * Takes in a URL string and returns the parsed URL query params in the way the ALB / Lambda
356
+ * integration does.
357
+ * Note that it does not URL decode the values.
358
+ */
359
+ public static parseQueryParamsFromUrlString(urlString: string): Record<string, string> {
360
+ const rval: Record<string, string> = {};
361
+
362
+ const searchStringParts: string[] = urlString.split('?');
363
+ if (searchStringParts.length < 2) {
364
+ // No query string.
365
+ return rval;
366
+ }
367
+
368
+ const searchString: string = searchStringParts.slice(1).join('?');
369
+
370
+ const searchParts: string[] = searchString.split('&');
371
+ for (const eachKeyValueString of searchParts) {
372
+ const eachKeyValueStringParts: string[] = eachKeyValueString.split('=');
373
+ const eachKey: string = eachKeyValueStringParts[0];
374
+ const eachValue: string = eachKeyValueStringParts.slice(1).join('=');
375
+ rval[eachKey] = eachValue;
376
+ }
377
+
378
+ return rval;
379
+ }
380
+
381
+ public static async runSampleBatchOnlyServerFromCliArgs(_args: string[]): Promise<void> {
382
+ Logger.setLevel(LoggerLevelName.debug);
383
+ const handler: EpsilonGlobalHandler = await SampleServerComponents.createSampleBatchOnlyEpsilonGlobalHandler(
384
+ 'SampleBatchOnlyLocalServer-' + Date.now(),
385
+ );
386
+ const testServer: LocalServer = new LocalServer(handler);
387
+ const res: boolean = await testServer.runServer();
388
+ Logger.info('Res was : %s', res);
389
+ }
390
+
391
+ public static async runSampleLocalServerFromCliArgs(_args: string[]): Promise<void> {
392
+ Logger.setLevel(LoggerLevelName.debug);
393
+ const localTokenHandler: LocalWebTokenManipulator<JwtTokenBase> = new LocalWebTokenManipulator<JwtTokenBase>(
394
+ ['abcd1234'],
395
+ 'sample-server',
396
+ );
397
+ const token: string = await localTokenHandler.createJWTStringAsync('asdf', {}, ['USER'], 3600);
398
+
399
+ Logger.info('Use token: %s', token);
400
+ const handler: EpsilonGlobalHandler = await SampleServerComponents.createSampleEpsilonGlobalHandler('SampleLocalServer-' + Date.now());
401
+ const testServer: LocalServer = new LocalServer(handler, {port: 8888, https: true});
402
+ const res: boolean = await testServer.runServer();
403
+ Logger.info('Res was : %s', res);
404
+ }
405
+
406
+ public static buildBackgroundTriggerFormHtml(names?: string[]): string {
407
+ let html: string = '<html><head><title>Epsilon BG Launcher</title></head><body><div>';
408
+ html += '<h1>Epsilon Background Launcher</h1><form method="GET" action="/epsilon-background-trigger">';
409
+ html += '<div style="display: flex; flex-direction: column">';
410
+ if (names) {
411
+ html += '<label for="task">Task Name</label><select id="task" name="task">';
412
+ names.forEach((n) => {
413
+ html += `<option value="${n}">${n}</option>`;
414
+ });
415
+ html += '</select>';
416
+ } else {
417
+ html += '<label for="task">Task Name</label><input type="text" id="task" name="task"></input>';
418
+ }
419
+ html += '<label for="dataJson">Data JSON</label><textarea id="dataJson" name="dataJson">{}</textarea>';
420
+ html += '<label for="metaJson">Meta JSON</label><textarea id="metaJson" name="metaJson">{}</textarea>';
421
+ html += '<input type="submit" value="Submit">';
422
+ html += '</div></form></div></body></html>';
423
+
424
+ return html;
425
+ }
426
+ }
@@ -0,0 +1,9 @@
1
+ export interface OpenApiDocModifications {
2
+ newServerPath: string;
3
+ removeEndpoints: RegExp[]; // format is '{verb} {path}'
4
+ removeTags: string[];
5
+ removeSchemas: string[];
6
+
7
+ sortEndpoints: boolean;
8
+ sortSchemas: boolean;
9
+ }
@@ -0,0 +1,22 @@
1
+ import { OpenApiDocModifications } from './open-api-doc-modifications.js';
2
+ import { OpenApiDocModifier } from './open-api-doc-modifier.js';
3
+ import { SampleServerStaticFiles } from '../sample/sample-server-static-files.js';
4
+ import { describe, expect, test } from 'vitest';
5
+
6
+ describe('#openApiDocModifier', function () {
7
+ test('should exist', async () => {
8
+ const data: string = SampleServerStaticFiles.SAMPLE_OPEN_API_DOC;
9
+ expect(data).toBeTruthy();
10
+ expect(data.length).toBeGreaterThan(0);
11
+
12
+ const mods: OpenApiDocModifications = {
13
+ newServerPath: 'https://api.sample.com/cw',
14
+ removeTags: ['Neon', 'CORS'],
15
+ removeEndpoints: [new RegExp('options .*')],
16
+ } as OpenApiDocModifications;
17
+
18
+ const result: string = new OpenApiDocModifier(mods).modifyOpenApiDoc(data);
19
+ expect(result).toBeTruthy();
20
+ // Logger.info('G: \n\n%s', result);
21
+ }, 30000);
22
+ });