@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.
- package/package.json +10 -9
- package/src/background/background-dynamo-log-table-handler.ts +44 -0
- package/src/background/background-entry.ts +4 -0
- package/src/background/background-execution-event-type.ts +9 -0
- package/src/background/background-execution-event.ts +9 -0
- package/src/background/background-execution-listener.ts +6 -0
- package/src/background/background-handler.ts +352 -0
- package/src/background/background-http-adapter-handler.ts +166 -0
- package/src/background/background-meta-response-internal.ts +5 -0
- package/src/background/background-process-handling.ts +6 -0
- package/src/background/background-process-log-table-entry.ts +11 -0
- package/src/background/background-queue-response-internal.ts +9 -0
- package/src/background/background-validator.ts +105 -0
- package/src/background/epsilon-background-process-error.ts +110 -0
- package/src/background/internal-background-entry.ts +10 -0
- package/src/background/manager/abstract-background-manager.ts +120 -0
- package/src/background/manager/aws-large-payload-s3-sqs-sns-background-manager.ts +87 -0
- package/src/background/manager/aws-sqs-sns-background-manager.ts +201 -0
- package/src/background/manager/background-manager-like.ts +44 -0
- package/src/background/manager/background-manager.spec.ts +89 -0
- package/src/background/manager/single-thread-local-background-manager.ts +58 -0
- package/src/background/s3-background-transaction-logger.ts +65 -0
- package/src/build/ratchet-epsilon-common-info.ts +19 -0
- package/src/built-in/background/echo-processor.ts +17 -0
- package/src/built-in/background/log-and-enqueue-echo-processor.ts +14 -0
- package/src/built-in/background/log-message-background-error-processor.ts +10 -0
- package/src/built-in/background/no-op-processor.ts +12 -0
- package/src/built-in/background/retry-processor.ts +51 -0
- package/src/built-in/background/sample-delay-processor.ts +15 -0
- package/src/built-in/background/sample-input-validated-processor-data.ts +4 -0
- package/src/built-in/background/sample-input-validated-processor.ts +14 -0
- package/src/built-in/built-in-trace-id-generators.ts +22 -0
- package/src/built-in/daemon/daemon-authorizer-function.ts +4 -0
- package/src/built-in/daemon/daemon-config.ts +9 -0
- package/src/built-in/daemon/daemon-group-selection-function.ts +3 -0
- package/src/built-in/daemon/daemon-handler.ts +87 -0
- package/src/built-in/daemon/daemon-process-state-list.ts +9 -0
- package/src/built-in/http/apollo/apollo-util.ts +43 -0
- package/src/built-in/http/apollo/default-epsilon-apollo-context.ts +11 -0
- package/src/built-in/http/apollo/epsilon-apollo-context-builder-options.ts +5 -0
- package/src/built-in/http/apollo/epsilon-lambda-apollo-context-function-argument.ts +6 -0
- package/src/built-in/http/apollo/epsilon-lambda-apollo-options.ts +11 -0
- package/src/built-in/http/apollo-filter.ts +151 -0
- package/src/built-in/http/built-in-auth-filters.ts +73 -0
- package/src/built-in/http/built-in-authorizers.ts +22 -0
- package/src/built-in/http/built-in-filters.spec.ts +26 -0
- package/src/built-in/http/built-in-filters.ts +300 -0
- package/src/built-in/http/built-in-handlers.ts +85 -0
- package/src/built-in/http/log-level-manipulation-filter.ts +26 -0
- package/src/built-in/http/run-handler-as-filter.spec.ts +67 -0
- package/src/built-in/http/run-handler-as-filter.ts +102 -0
- package/src/cli/ratchet-cli-handler.ts +23 -0
- package/src/cli/run-background-process-from-command-line.ts +32 -0
- package/src/config/background/background-aws-config.ts +8 -0
- package/src/config/background/background-config.ts +15 -0
- package/src/config/background/background-error-processor.ts +5 -0
- package/src/config/background/background-processor.ts +14 -0
- package/src/config/background/background-transaction-log.ts +9 -0
- package/src/config/background/background-transaction-logger.ts +6 -0
- package/src/config/cron/abstract-cron-entry.ts +17 -0
- package/src/config/cron/cron-background-entry.ts +17 -0
- package/src/config/cron/cron-config.ts +10 -0
- package/src/config/dynamo-db-config.ts +6 -0
- package/src/config/epsilon-config.ts +30 -0
- package/src/config/epsilon-lambda-event-handler.ts +12 -0
- package/src/config/epsilon-logger-config.ts +23 -0
- package/src/config/espilon-server-mode.ts +10 -0
- package/src/config/generic-aws-event-handler-function.ts +1 -0
- package/src/config/http/authorizer-function.ts +9 -0
- package/src/config/http/epsilon-authorization-context.ts +5 -0
- package/src/config/http/epsilon-cors-approach.ts +7 -0
- package/src/config/http/extended-api-gateway-event.ts +8 -0
- package/src/config/http/filter-chain-context.ts +15 -0
- package/src/config/http/filter-function.ts +3 -0
- package/src/config/http/handler-function.ts +4 -0
- package/src/config/http/http-config.ts +27 -0
- package/src/config/http/http-processing-config.ts +23 -0
- package/src/config/http/mapped-http-processing-config.ts +12 -0
- package/src/config/http/null-returned-object-handling.ts +7 -0
- package/src/config/inter-api/inter-api-aws-config.ts +5 -0
- package/src/config/inter-api/inter-api-config.ts +7 -0
- package/src/config/inter-api/inter-api-process-mapping.ts +11 -0
- package/src/config/local-server/local-server-event-logging-style.ts +8 -0
- package/src/config/local-server/local-server-http-method-handling.ts +7 -0
- package/src/config/local-server/local-server-options.ts +12 -0
- package/src/config/logging-trace-id-generator.ts +3 -0
- package/src/config/no-handlers-found-error.ts +6 -0
- package/src/config/open-api/open-api-document-components.ts +4 -0
- package/src/config/open-api/open-api-document.ts +7 -0
- package/src/config/s3-config.ts +8 -0
- package/src/config/sns-config.ts +7 -0
- package/src/config/sqs-config.ts +7 -0
- package/src/epsilon-build-properties.ts +21 -0
- package/src/epsilon-constants.ts +62 -0
- package/src/epsilon-global-handler.ts +238 -0
- package/src/epsilon-instance.ts +20 -0
- package/src/epsilon-logging-extension-processor.ts +19 -0
- package/src/http/auth/api-gateway-adapter-authentication-handler.ts +95 -0
- package/src/http/auth/auth0-web-token-manipulator.ts +69 -0
- package/src/http/auth/basic-auth-token.ts +7 -0
- package/src/http/auth/google-web-token-manipulator.spec.ts +15 -0
- package/src/http/auth/google-web-token-manipulator.ts +80 -0
- package/src/http/auth/jwt-ratchet-local-web-token-manipulator.ts +37 -0
- package/src/http/auth/local-web-token-manipulator.spec.ts +34 -0
- package/src/http/auth/local-web-token-manipulator.ts +114 -0
- package/src/http/auth/web-token-manipulator.ts +9 -0
- package/src/http/error/bad-gateway.ts +11 -0
- package/src/http/error/bad-request-error.ts +11 -0
- package/src/http/error/conflict-error.ts +12 -0
- package/src/http/error/forbidden-error.ts +12 -0
- package/src/http/error/gateway-timeout.ts +12 -0
- package/src/http/error/method-not-allowed-error.ts +12 -0
- package/src/http/error/misconfigured-error.ts +12 -0
- package/src/http/error/not-found-error.ts +12 -0
- package/src/http/error/not-implemented.ts +12 -0
- package/src/http/error/request-timeout-error.ts +12 -0
- package/src/http/error/service-unavailable.ts +12 -0
- package/src/http/error/too-many-requests-error.ts +12 -0
- package/src/http/error/unauthorized-error.ts +12 -0
- package/src/http/event-util.spec.ts +190 -0
- package/src/http/event-util.ts +272 -0
- package/src/http/response-util.spec.ts +117 -0
- package/src/http/response-util.ts +164 -0
- package/src/http/route/epsilon-router.ts +9 -0
- package/src/http/route/extended-auth-response-context.ts +7 -0
- package/src/http/route/route-and-parse.ts +8 -0
- package/src/http/route/route-mapping.ts +21 -0
- package/src/http/route/route-validator-config.ts +5 -0
- package/src/http/route/router-util.spec.ts +33 -0
- package/src/http/route/router-util.ts +314 -0
- package/src/http/web-handler.spec.ts +99 -0
- package/src/http/web-handler.ts +157 -0
- package/src/http/web-v2-handler.ts +34 -0
- package/src/inter-api/inter-api-entry.ts +8 -0
- package/src/inter-api/inter-api-util.spec.ts +77 -0
- package/src/inter-api/inter-api-util.ts +71 -0
- package/src/inter-api-manager.ts +75 -0
- package/src/lambda-event-handler/cron-epsilon-lambda-event-handler.spec.ts +130 -0
- package/src/lambda-event-handler/cron-epsilon-lambda-event-handler.ts +132 -0
- package/src/lambda-event-handler/dynamo-epsilon-lambda-event-handler.ts +42 -0
- package/src/lambda-event-handler/generic-sns-epsilon-lambda-event-handler.ts +38 -0
- package/src/lambda-event-handler/generic-sqs-epsilon-lambda-event-handler.ts +43 -0
- package/src/lambda-event-handler/inter-api-epsilon-lambda-event-handler.ts +33 -0
- package/src/lambda-event-handler/s3-epsilon-lambda-event-handler.ts +50 -0
- package/src/local-container-server.ts +128 -0
- package/src/local-server.spec.ts +16 -0
- package/src/local-server.ts +426 -0
- package/src/open-api-util/open-api-doc-modifications.ts +9 -0
- package/src/open-api-util/open-api-doc-modifier.spec.ts +22 -0
- package/src/open-api-util/open-api-doc-modifier.ts +90 -0
- package/src/open-api-util/yaml-combiner.spec.ts +26 -0
- package/src/open-api-util/yaml-combiner.ts +35 -0
- package/src/sample/sample-server-components-with-apollo.ts +87 -0
- package/src/sample/sample-server-components.ts +183 -0
- package/src/sample/sample-server-static-files.ts +614 -0
- package/src/sample/test-error-server.ts +140 -0
- package/src/util/aws-util.ts +89 -0
- package/src/util/context-global-data.ts +13 -0
- package/src/util/context-util.ts +156 -0
- package/src/util/cron-util.spec.ts +190 -0
- package/src/util/cron-util.ts +86 -0
- package/src/util/epsilon-config-parser.ts +90 -0
- package/src/util/epsilon-server-util.spec.ts +18 -0
- 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&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,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
|
+
});
|