@expressive-tea/core 2.0.0
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/.gitattributes +4 -0
- package/.swcrc +61 -0
- package/LICENSE +201 -0
- package/README.md +627 -0
- package/banner.png +0 -0
- package/classes/Boot.d.ts +145 -0
- package/classes/Boot.js +223 -0
- package/classes/Engine.d.ts +63 -0
- package/classes/Engine.js +90 -0
- package/classes/EngineRegistry.d.ts +154 -0
- package/classes/EngineRegistry.js +247 -0
- package/classes/LoadBalancer.d.ts +8 -0
- package/classes/LoadBalancer.js +28 -0
- package/classes/ProxyRoute.d.ts +14 -0
- package/classes/ProxyRoute.js +40 -0
- package/classes/Settings.d.ts +128 -0
- package/classes/Settings.js +172 -0
- package/decorators/annotations.d.ts +91 -0
- package/decorators/annotations.js +132 -0
- package/decorators/env.d.ts +145 -0
- package/decorators/env.js +177 -0
- package/decorators/health.d.ts +115 -0
- package/decorators/health.js +124 -0
- package/decorators/module.d.ts +34 -0
- package/decorators/module.js +39 -0
- package/decorators/proxy.d.ts +28 -0
- package/decorators/proxy.js +60 -0
- package/decorators/router.d.ts +199 -0
- package/decorators/router.js +252 -0
- package/decorators/server.d.ts +92 -0
- package/decorators/server.js +247 -0
- package/engines/constants/constants.d.ts +2 -0
- package/engines/constants/constants.js +5 -0
- package/engines/health/index.d.ts +120 -0
- package/engines/health/index.js +179 -0
- package/engines/http/index.d.ts +12 -0
- package/engines/http/index.js +59 -0
- package/engines/index.d.ts +32 -0
- package/engines/index.js +112 -0
- package/engines/socketio/index.d.ts +7 -0
- package/engines/socketio/index.js +30 -0
- package/engines/teacup/index.d.ts +27 -0
- package/engines/teacup/index.js +136 -0
- package/engines/teapot/index.d.ts +32 -0
- package/engines/teapot/index.js +167 -0
- package/engines/websocket/index.d.ts +9 -0
- package/engines/websocket/index.js +39 -0
- package/eslint.config.mjs +138 -0
- package/exceptions/BootLoaderExceptions.d.ts +26 -0
- package/exceptions/BootLoaderExceptions.js +31 -0
- package/exceptions/RequestExceptions.d.ts +75 -0
- package/exceptions/RequestExceptions.js +89 -0
- package/helpers/boot-helper.d.ts +7 -0
- package/helpers/boot-helper.js +84 -0
- package/helpers/decorators.d.ts +1 -0
- package/helpers/decorators.js +15 -0
- package/helpers/promise-helper.d.ts +1 -0
- package/helpers/promise-helper.js +6 -0
- package/helpers/server.d.ts +35 -0
- package/helpers/server.js +141 -0
- package/helpers/teapot-helper.d.ts +18 -0
- package/helpers/teapot-helper.js +88 -0
- package/helpers/websocket-helper.d.ts +3 -0
- package/helpers/websocket-helper.js +20 -0
- package/images/announcement-01.png +0 -0
- package/images/logo-sticky-01.png +0 -0
- package/images/logo-wp-01.png +0 -0
- package/images/logo.png +0 -0
- package/images/zero-oneit.png +0 -0
- package/interfaces/index.d.ts +4 -0
- package/interfaces/index.js +2 -0
- package/inversify.config.d.ts +9 -0
- package/inversify.config.js +8 -0
- package/libs/classNames.d.ts +1 -0
- package/libs/classNames.js +4 -0
- package/libs/utilities.d.ts +21910 -0
- package/libs/utilities.js +420 -0
- package/mixins/module.d.ts +45 -0
- package/mixins/module.js +71 -0
- package/mixins/proxy.d.ts +46 -0
- package/mixins/proxy.js +86 -0
- package/mixins/route.d.ts +48 -0
- package/mixins/route.js +96 -0
- package/package.json +137 -0
- package/services/DependencyInjection.d.ts +159 -0
- package/services/DependencyInjection.js +201 -0
- package/services/WebsocketService.d.ts +18 -0
- package/services/WebsocketService.js +47 -0
- package/types/core.d.ts +14 -0
- package/types/core.js +2 -0
- package/types/injection-types.d.ts +6 -0
- package/types/injection-types.js +10 -0
- package/types/inversify.d.ts +5 -0
- package/types/inversify.js +3 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UnauthorizedException = exports.BadRequestException = exports.GenericRequestException = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* @namespace Exceptions
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* HTTP Generic Exception Use this Exception to easily return a HTTP Error on the endpoints handlers.
|
|
9
|
+
* @export
|
|
10
|
+
* @class GenericRequestException
|
|
11
|
+
* @extends {Error}
|
|
12
|
+
* @param {string} message='Server Error' Provide the Error message.
|
|
13
|
+
* @param {number} [statusCode=500] HTTP Response Code
|
|
14
|
+
* @summary HTTP Exception Helper
|
|
15
|
+
* @example
|
|
16
|
+
* {REPLACE-AT}Router('/')
|
|
17
|
+
* class ExceptionExampleController {
|
|
18
|
+
* {REPLACE-AT}Get('/exception')
|
|
19
|
+
* exceptionMethod(req, res, next) {
|
|
20
|
+
* try {
|
|
21
|
+
* throw new GenericRequestException('Page not found', 404);
|
|
22
|
+
* } catch (e) {
|
|
23
|
+
* res.status(e.code).send(e.message);
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
class GenericRequestException extends Error {
|
|
29
|
+
constructor(message, statusCode = 500) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.statusCode = 500;
|
|
32
|
+
this.message = 'Server Error';
|
|
33
|
+
this.statusCode = statusCode;
|
|
34
|
+
this.message = message;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.GenericRequestException = GenericRequestException;
|
|
38
|
+
/**
|
|
39
|
+
* Shortcut Exception for 400 HTTP Errors (Bad Request).
|
|
40
|
+
* @export
|
|
41
|
+
* @class BadRequestException
|
|
42
|
+
* @extends {GenericRequestException}
|
|
43
|
+
* @param {string} [message='Bad Request'] Provide the Error message.
|
|
44
|
+
* @summary 400 Exception
|
|
45
|
+
* @example
|
|
46
|
+
* {REPLACE-AT}Router('/')
|
|
47
|
+
* class ExceptionExampleController {
|
|
48
|
+
* {REPLACE-AT}Get('/exception')
|
|
49
|
+
* exceptionMethod(req, res, next) {
|
|
50
|
+
* try {
|
|
51
|
+
* throw new BadRequestException();
|
|
52
|
+
* } catch (e) {
|
|
53
|
+
* res.status(e.code).send(e.message);
|
|
54
|
+
* }
|
|
55
|
+
* }
|
|
56
|
+
* }
|
|
57
|
+
*/
|
|
58
|
+
class BadRequestException extends GenericRequestException {
|
|
59
|
+
constructor(message = 'Bad Request') {
|
|
60
|
+
super(message, 400);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.BadRequestException = BadRequestException;
|
|
64
|
+
/**
|
|
65
|
+
* Shortcut Exception for 401 HTTP Errors (Unauthorized Request).
|
|
66
|
+
* @export
|
|
67
|
+
* @class UnauthorizedException
|
|
68
|
+
* @extends {GenericRequestException}
|
|
69
|
+
* @param {string} [message='Unauthorized Request'] Provide the Error message.
|
|
70
|
+
* @summary 401 Exception
|
|
71
|
+
* @example
|
|
72
|
+
* {REPLACE-AT}Router('/')
|
|
73
|
+
* class ExceptionExampleController {
|
|
74
|
+
* {REPLACE-AT}Get('/exception')
|
|
75
|
+
* exceptionMethod(req, res, next) {
|
|
76
|
+
* try {
|
|
77
|
+
* throw new UnauthorizedException();
|
|
78
|
+
* } catch (e) {
|
|
79
|
+
* res.status(e.code).send(e.message);
|
|
80
|
+
* }
|
|
81
|
+
* }
|
|
82
|
+
* }
|
|
83
|
+
*/
|
|
84
|
+
class UnauthorizedException extends GenericRequestException {
|
|
85
|
+
constructor(message = 'Unauthorized Request') {
|
|
86
|
+
super(message, 401);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.UnauthorizedException = UnauthorizedException;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BOOT_STAGES } from '@expressive-tea/commons';
|
|
2
|
+
import { type Express } from 'express';
|
|
3
|
+
import type Boot from '../classes/Boot';
|
|
4
|
+
export declare function resolveStage(stage: BOOT_STAGES, ctx: Boot, server: Express, ...extraArgs: unknown[]): Promise<void>;
|
|
5
|
+
export declare function resolveDirectives(instance: typeof Boot | Boot, server: Express): void;
|
|
6
|
+
export declare function resolveStatic(instance: typeof Boot | Boot, server: Express): void;
|
|
7
|
+
export declare function resolveProxy(ProxyContainer: any, server: Express): void;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveStage = resolveStage;
|
|
4
|
+
exports.resolveDirectives = resolveDirectives;
|
|
5
|
+
exports.resolveStatic = resolveStatic;
|
|
6
|
+
exports.resolveProxy = resolveProxy;
|
|
7
|
+
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return */
|
|
8
|
+
const commons_1 = require("@expressive-tea/commons");
|
|
9
|
+
const express = require("express");
|
|
10
|
+
const commons_2 = require("@expressive-tea/commons");
|
|
11
|
+
const commons_3 = require("@expressive-tea/commons");
|
|
12
|
+
const BootLoaderExceptions_1 = require("../exceptions/BootLoaderExceptions");
|
|
13
|
+
const DependencyInjection_1 = require("../services/DependencyInjection");
|
|
14
|
+
async function resolveStage(stage, ctx, server, ...extraArgs) {
|
|
15
|
+
try {
|
|
16
|
+
await bootloaderResolve(stage, server, ctx, ...extraArgs);
|
|
17
|
+
if (stage === commons_1.BOOT_STAGES.APPLICATION) {
|
|
18
|
+
resolveModules(ctx, server);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
if (checkIfStageFails(e)) {
|
|
23
|
+
throw e;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function resolveDirectives(instance, server) {
|
|
28
|
+
const registeredDirectives = commons_2.Metadata.get(commons_1.REGISTERED_DIRECTIVES_KEY, (0, commons_3.getClass)(instance)) || [];
|
|
29
|
+
registeredDirectives.forEach((options) => {
|
|
30
|
+
// @ts-expect-error Settings can be any parameter
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
32
|
+
server.set(options.name, ...options.settings);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function resolveStatic(instance, server) {
|
|
36
|
+
const registeredStatic = commons_2.Metadata.get(commons_1.REGISTERED_STATIC_KEY, (0, commons_3.getClass)(instance)) || [];
|
|
37
|
+
registeredStatic.forEach((staticOptions) => {
|
|
38
|
+
if (staticOptions.virtual) {
|
|
39
|
+
server.use(staticOptions.virtual, express.static(staticOptions.root, staticOptions.options));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
server.use(express.static(staticOptions.root, staticOptions.options));
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function resolveProxy(ProxyContainer, server) {
|
|
47
|
+
const proxyContainer = new ProxyContainer();
|
|
48
|
+
proxyContainer.__register(server);
|
|
49
|
+
}
|
|
50
|
+
function resolveModules(instance, server) {
|
|
51
|
+
// Metadata is stored on the class by decorators, so we need to get the constructor
|
|
52
|
+
// If instance is already a class (typeof === 'function'), use it directly
|
|
53
|
+
// If instance is an object, get its constructor
|
|
54
|
+
const target = typeof instance === 'function' ? instance : instance.constructor;
|
|
55
|
+
const registeredModules = commons_2.Metadata.get(commons_1.REGISTERED_MODULE_KEY, target, 'start') || [];
|
|
56
|
+
for (const Module of registeredModules) {
|
|
57
|
+
const moduleInstance = (0, DependencyInjection_1.getInstanceOf)(Module);
|
|
58
|
+
moduleInstance.__register(server);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function bootloaderResolve(STAGE, server, instance, ...args) {
|
|
62
|
+
const bootLoader = commons_2.Metadata.get(commons_1.BOOT_STAGES_KEY, (0, commons_3.getClass)(instance)) || commons_1.STAGES_INIT;
|
|
63
|
+
for (const loader of bootLoader[STAGE] || []) {
|
|
64
|
+
try {
|
|
65
|
+
await selectLoaderType(loader, server, ...args);
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
shouldFailIfRequire(e, loader);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function selectLoaderType(loader, server, ...args) {
|
|
73
|
+
return loader.method(server, ...args);
|
|
74
|
+
}
|
|
75
|
+
function checkIfStageFails(e) {
|
|
76
|
+
return !(e instanceof BootLoaderExceptions_1.BootLoaderSoftExceptions);
|
|
77
|
+
}
|
|
78
|
+
function shouldFailIfRequire(e, loader) {
|
|
79
|
+
const failMessage = `Failed [${loader.name}]: ${e.message}`;
|
|
80
|
+
if (!loader || loader.required) {
|
|
81
|
+
throw new BootLoaderExceptions_1.BootLoaderRequiredExceptions(failMessage);
|
|
82
|
+
}
|
|
83
|
+
throw new BootLoaderExceptions_1.BootLoaderSoftExceptions(`${failMessage} and will be not enabled`);
|
|
84
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function addAnnotation(type: string, target: object, propertyKey: string | symbol, ...args: any[]): void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addAnnotation = addAnnotation;
|
|
4
|
+
const commons_1 = require("@expressive-tea/commons");
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
6
|
+
const commons_2 = require("@expressive-tea/commons");
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
function addAnnotation(type, target, propertyKey, ...args) {
|
|
9
|
+
const annotations = commons_1.Metadata.get(commons_2.ROUTER_ANNOTATIONS_KEY, target, propertyKey) || [];
|
|
10
|
+
annotations.unshift({
|
|
11
|
+
arguments: args,
|
|
12
|
+
type
|
|
13
|
+
});
|
|
14
|
+
commons_1.Metadata.set(commons_2.ROUTER_ANNOTATIONS_KEY, annotations, target, propertyKey);
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function delay(ms: number): Promise<void>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type NextFunction, type Request, type Response } from 'express';
|
|
2
|
+
import { type ExpressiveTeaAnnotations, type ExpressiveTeaArgumentOptions } from '@expressive-tea/commons';
|
|
3
|
+
import { type ExpressiveTeaHandlerOptionsWithInstrospectedArgs } from '../interfaces';
|
|
4
|
+
import { TFunction } from '../types/core';
|
|
5
|
+
import { type ExpressiveTeaServerProps } from '@expressive-tea/commons';
|
|
6
|
+
interface ExecuteRequestContext {
|
|
7
|
+
options: ExpressiveTeaHandlerOptionsWithInstrospectedArgs;
|
|
8
|
+
decoratedArguments: ExpressiveTeaArgumentOptions[];
|
|
9
|
+
annotations: ExpressiveTeaAnnotations[];
|
|
10
|
+
self: any;
|
|
11
|
+
}
|
|
12
|
+
export interface FileSettingsResult {
|
|
13
|
+
config: ExpressiveTeaServerProps;
|
|
14
|
+
source: string | null;
|
|
15
|
+
}
|
|
16
|
+
export declare function autoResponse(_: any, response: Response, annotations: ExpressiveTeaAnnotations[], responseResult?: any): void;
|
|
17
|
+
export declare function executeRequest(this: ExecuteRequestContext, request: Request, response: Response, next: NextFunction): Promise<void>;
|
|
18
|
+
export declare function mapArguments(decoratedArguments: ExpressiveTeaArgumentOptions[], request: Request, response: Response, next: NextFunction, introspectedArgs?: string[]): any[];
|
|
19
|
+
export declare function extractParameters(target: unknown, args?: string | string[], propertyName?: string): any;
|
|
20
|
+
export declare function generateRoute(route: string, verb: string, ...settings: any): (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
|
|
21
|
+
export declare function router(verb: string, route: string, target: any, handler: TFunction, propertyKey: string | symbol, settings?: any): void;
|
|
22
|
+
/**
|
|
23
|
+
* Load configuration from .expressive-tea files.
|
|
24
|
+
*
|
|
25
|
+
* Supports YAML (.yaml, .yml) and JSON formats with priority order:
|
|
26
|
+
* 1. .expressive-tea.yaml (highest)
|
|
27
|
+
* 2. .expressive-tea.yml
|
|
28
|
+
* 3. .expressive-tea (JSON, lowest)
|
|
29
|
+
*
|
|
30
|
+
* @returns Configuration object and source file path
|
|
31
|
+
* @throws {Error} If config file is invalid (JSON/YAML parse error)
|
|
32
|
+
* @since 2.0.1
|
|
33
|
+
*/
|
|
34
|
+
export declare function fileSettings(): FileSettingsResult;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.autoResponse = autoResponse;
|
|
4
|
+
exports.executeRequest = executeRequest;
|
|
5
|
+
exports.mapArguments = mapArguments;
|
|
6
|
+
exports.extractParameters = extractParameters;
|
|
7
|
+
exports.generateRoute = generateRoute;
|
|
8
|
+
exports.router = router;
|
|
9
|
+
exports.fileSettings = fileSettings;
|
|
10
|
+
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return */
|
|
11
|
+
const utilities_1 = require("../libs/utilities");
|
|
12
|
+
const commons_1 = require("@expressive-tea/commons");
|
|
13
|
+
const commons_2 = require("@expressive-tea/commons");
|
|
14
|
+
const commons_3 = require("@expressive-tea/commons");
|
|
15
|
+
const fs = require("node:fs");
|
|
16
|
+
const yaml = require("js-yaml");
|
|
17
|
+
const path = require("node:path");
|
|
18
|
+
function autoResponse(_, response, annotations, responseResult) {
|
|
19
|
+
const view = (0, utilities_1.find)(annotations, { type: 'view' });
|
|
20
|
+
if (view && view.arguments) {
|
|
21
|
+
response.render(view.arguments[0], responseResult);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
response.send((0, utilities_1.isNumber)(responseResult) ? responseResult.toString() : responseResult);
|
|
25
|
+
}
|
|
26
|
+
async function executeRequest(request, response, next) {
|
|
27
|
+
let isNextUsed = false;
|
|
28
|
+
const nextWrapper = (error) => {
|
|
29
|
+
if (error) {
|
|
30
|
+
isNextUsed = true;
|
|
31
|
+
return next(error);
|
|
32
|
+
}
|
|
33
|
+
isNextUsed = true;
|
|
34
|
+
next();
|
|
35
|
+
};
|
|
36
|
+
const result = await this.options.handler.apply(this.self, mapArguments(this.decoratedArguments, request, response, nextWrapper, this.options.introspectedArgs));
|
|
37
|
+
if (!response.headersSent && !isNextUsed) {
|
|
38
|
+
autoResponse(request, response, this.annotations, result);
|
|
39
|
+
}
|
|
40
|
+
// Express 5 handles async rejections automatically
|
|
41
|
+
}
|
|
42
|
+
function mapArguments(decoratedArguments, request, response, next, introspectedArgs = []) {
|
|
43
|
+
return (0, utilities_1.chain)(decoratedArguments)
|
|
44
|
+
.sortBy('index')
|
|
45
|
+
.map((argument) => {
|
|
46
|
+
switch (argument.type) {
|
|
47
|
+
case commons_2.ARGUMENT_TYPES.REQUEST:
|
|
48
|
+
return request;
|
|
49
|
+
case commons_2.ARGUMENT_TYPES.RESPONSE:
|
|
50
|
+
return response;
|
|
51
|
+
case commons_2.ARGUMENT_TYPES.NEXT:
|
|
52
|
+
return next;
|
|
53
|
+
case commons_2.ARGUMENT_TYPES.QUERY:
|
|
54
|
+
return extractParameters(request.query, argument.arguments, (0, utilities_1.get)(introspectedArgs, argument.index));
|
|
55
|
+
case commons_2.ARGUMENT_TYPES.BODY:
|
|
56
|
+
return extractParameters(request.body, argument.arguments, (0, utilities_1.get)(introspectedArgs, argument.index));
|
|
57
|
+
case commons_2.ARGUMENT_TYPES.GET_PARAM:
|
|
58
|
+
return extractParameters(request.params, argument.arguments, (0, utilities_1.get)(introspectedArgs, argument.index));
|
|
59
|
+
default:
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
.thru((args) => (0, utilities_1.size)(args) ? args : [request, response, next])
|
|
64
|
+
.value();
|
|
65
|
+
}
|
|
66
|
+
function extractParameters(target, args, propertyName) {
|
|
67
|
+
if (!args && !target) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (args && (0, utilities_1.size)(args)) {
|
|
71
|
+
if (Array.isArray(args)) {
|
|
72
|
+
return (0, utilities_1.pick)(target, args);
|
|
73
|
+
}
|
|
74
|
+
return (0, utilities_1.get)(target, args);
|
|
75
|
+
}
|
|
76
|
+
if (propertyName && (0, utilities_1.has)(target, propertyName)) {
|
|
77
|
+
return (0, utilities_1.get)(target, propertyName);
|
|
78
|
+
}
|
|
79
|
+
return target;
|
|
80
|
+
}
|
|
81
|
+
function generateRoute(route, verb, ...settings) {
|
|
82
|
+
return (target, propertyKey, descriptor) => {
|
|
83
|
+
router(verb, route, target, descriptor.value, propertyKey, settings);
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function router(verb, route, target, handler, propertyKey, settings) {
|
|
87
|
+
const introspectedArgs = (0, commons_3.getOwnArgumentNames)(handler);
|
|
88
|
+
const existedRoutesHandlers = commons_1.Metadata.get(commons_2.ROUTER_HANDLERS_KEY, target) || [];
|
|
89
|
+
existedRoutesHandlers.unshift({ verb, route, handler, target, propertyKey, settings, introspectedArgs });
|
|
90
|
+
commons_1.Metadata.set(commons_2.ROUTER_HANDLERS_KEY, existedRoutesHandlers, target);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Load configuration from .expressive-tea files.
|
|
94
|
+
*
|
|
95
|
+
* Supports YAML (.yaml, .yml) and JSON formats with priority order:
|
|
96
|
+
* 1. .expressive-tea.yaml (highest)
|
|
97
|
+
* 2. .expressive-tea.yml
|
|
98
|
+
* 3. .expressive-tea (JSON, lowest)
|
|
99
|
+
*
|
|
100
|
+
* @returns Configuration object and source file path
|
|
101
|
+
* @throws {Error} If config file is invalid (JSON/YAML parse error)
|
|
102
|
+
* @since 2.0.1
|
|
103
|
+
*/
|
|
104
|
+
function fileSettings() {
|
|
105
|
+
const cwd = process.cwd();
|
|
106
|
+
// Priority order: YAML > YML > JSON
|
|
107
|
+
const configFiles = [
|
|
108
|
+
{ path: '.expressive-tea.yaml', type: 'yaml' },
|
|
109
|
+
{ path: '.expressive-tea.yml', type: 'yaml' },
|
|
110
|
+
{ path: '.expressive-tea', type: 'json' }
|
|
111
|
+
];
|
|
112
|
+
for (const file of configFiles) {
|
|
113
|
+
const filePath = path.join(cwd, file.path);
|
|
114
|
+
if (fs.existsSync(filePath)) {
|
|
115
|
+
try {
|
|
116
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
117
|
+
let config;
|
|
118
|
+
if (file.type === 'yaml') {
|
|
119
|
+
const parsed = yaml.load(content);
|
|
120
|
+
// yaml.load returns undefined for empty strings and null for whitespace/comments
|
|
121
|
+
// Treat empty YAML files as empty configuration objects
|
|
122
|
+
config = (parsed !== null && parsed !== void 0 ? parsed : {});
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
config = JSON.parse(content);
|
|
126
|
+
}
|
|
127
|
+
// Debug log which file was loaded
|
|
128
|
+
console.debug(`[Expressive Tea] Loaded configuration from: ${file.path}`);
|
|
129
|
+
return { config, source: file.path };
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
const errorMsg = file.type === 'yaml'
|
|
133
|
+
? `Invalid YAML in ${file.path}: ${error.message}`
|
|
134
|
+
: `Invalid JSON in ${file.path}: ${error.message}`;
|
|
135
|
+
throw new Error(errorMsg);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// No config file found
|
|
140
|
+
return { config: {}, source: null };
|
|
141
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type KeyPairSyncResult } from 'node:crypto';
|
|
2
|
+
import { type NextFunction, type Request, type Response } from 'express';
|
|
3
|
+
import type ProxyRoute from '../classes/ProxyRoute';
|
|
4
|
+
export interface EncryptedMessage {
|
|
5
|
+
iv: string;
|
|
6
|
+
message: string;
|
|
7
|
+
authTag: string;
|
|
8
|
+
}
|
|
9
|
+
export type TeaGatewayMessage = Record<string, any>;
|
|
10
|
+
export default class TeaGatewayHelper {
|
|
11
|
+
static encrypt(data: TeaGatewayMessage, signature: Buffer): EncryptedMessage;
|
|
12
|
+
static decrypt(data: EncryptedMessage, signature: Buffer): TeaGatewayMessage;
|
|
13
|
+
static sign(data: string, privateKey: string, passphrase: string): Buffer;
|
|
14
|
+
static verify(data: string, publicKey: string, signature: Buffer): boolean;
|
|
15
|
+
static generateKeys(passphrase: string): KeyPairSyncResult<any, any>;
|
|
16
|
+
static proxyResponse(proxyRoute: ProxyRoute, req: Request, res: Response, next: NextFunction): void;
|
|
17
|
+
static httpSchema(schema: string): "http:" | "https:";
|
|
18
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const crypto = require("node:crypto");
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
class TeaGatewayHelper {
|
|
7
|
+
static encrypt(data, signature) {
|
|
8
|
+
const iv = crypto.randomBytes(16);
|
|
9
|
+
const packet = JSON.stringify(data);
|
|
10
|
+
// HKDF key derivation - separate encryption key from signature
|
|
11
|
+
const derivedKey = Buffer.from(crypto.hkdfSync('sha256', signature, Buffer.alloc(32), 'expressive-tea-encryption', 32));
|
|
12
|
+
// AES-256-GCM - authenticated encryption
|
|
13
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', derivedKey, iv);
|
|
14
|
+
const encrypted = Buffer.concat([cipher.update(packet), cipher.final()]);
|
|
15
|
+
const authTag = cipher.getAuthTag();
|
|
16
|
+
return {
|
|
17
|
+
iv: iv.toString('hex'),
|
|
18
|
+
message: encrypted.toString('base64'),
|
|
19
|
+
authTag: authTag.toString('hex')
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
static decrypt(data, signature) {
|
|
23
|
+
// Input validation
|
|
24
|
+
if (!data || !data.iv || !data.message || !data.authTag) {
|
|
25
|
+
throw new Error('Invalid encrypted message format');
|
|
26
|
+
}
|
|
27
|
+
if (!signature || signature.length !== 64) {
|
|
28
|
+
throw new Error('Invalid signature');
|
|
29
|
+
}
|
|
30
|
+
const iv = Buffer.from(data.iv, 'hex');
|
|
31
|
+
const message = Buffer.from(data.message, 'base64');
|
|
32
|
+
const authTag = Buffer.from(data.authTag, 'hex');
|
|
33
|
+
// HKDF key derivation - same as encrypt
|
|
34
|
+
const derivedKey = Buffer.from(crypto.hkdfSync('sha256', signature, Buffer.alloc(32), 'expressive-tea-encryption', 32));
|
|
35
|
+
// AES-256-GCM with auth tag verification
|
|
36
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', derivedKey, iv);
|
|
37
|
+
decipher.setAuthTag(authTag);
|
|
38
|
+
try {
|
|
39
|
+
const decrypted = Buffer.concat([decipher.update(message), decipher.final()]);
|
|
40
|
+
return JSON.parse(decrypted.toString());
|
|
41
|
+
}
|
|
42
|
+
catch (_a) {
|
|
43
|
+
throw new Error('Decryption failed: message tampered or wrong key');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
static sign(data, privateKey, passphrase) {
|
|
47
|
+
return crypto.sign(null, Buffer.from(data), {
|
|
48
|
+
key: privateKey,
|
|
49
|
+
passphrase
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
static verify(data, publicKey, signature) {
|
|
53
|
+
return crypto.verify(null, Buffer.from(data), {
|
|
54
|
+
key: publicKey
|
|
55
|
+
}, signature);
|
|
56
|
+
}
|
|
57
|
+
static generateKeys(passphrase) {
|
|
58
|
+
return (0, node_crypto_1.generateKeyPairSync)('ed25519', {
|
|
59
|
+
modulusLength: 2048,
|
|
60
|
+
publicKeyEncoding: {
|
|
61
|
+
type: 'spki',
|
|
62
|
+
format: 'pem'
|
|
63
|
+
},
|
|
64
|
+
privateKeyEncoding: {
|
|
65
|
+
type: 'pkcs8',
|
|
66
|
+
format: 'pem',
|
|
67
|
+
cipher: 'aes-256-cbc',
|
|
68
|
+
passphrase
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
static proxyResponse(proxyRoute, req, res, next) {
|
|
73
|
+
const router = proxyRoute.registerRoute();
|
|
74
|
+
if (!proxyRoute.hasClients()) {
|
|
75
|
+
next();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
router(req, res, next);
|
|
79
|
+
}
|
|
80
|
+
static httpSchema(schema) {
|
|
81
|
+
if (schema.includes('teapot'))
|
|
82
|
+
return 'http:';
|
|
83
|
+
if (schema.includes('teapots'))
|
|
84
|
+
return 'https:';
|
|
85
|
+
throw new Error(`Invalid Schema: ${schema}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.default = TeaGatewayHelper;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initWebsocket = initWebsocket;
|
|
4
|
+
const WebsocketService_1 = require("../services/WebsocketService");
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
6
|
+
const WebSocket = require("ws");
|
|
7
|
+
const Settings_1 = require("../classes/Settings");
|
|
8
|
+
function initWebsocket(server, secureServer) {
|
|
9
|
+
const settings = Settings_1.default.getInstance();
|
|
10
|
+
const isDetached = settings.get('detachWebsocket');
|
|
11
|
+
if (settings.get('startWebsocket')) {
|
|
12
|
+
WebsocketService_1.default.init();
|
|
13
|
+
WebsocketService_1.default.getInstance().setWebSocket(new WebSocket.Server(isDetached ? { noServer: true } : { server }));
|
|
14
|
+
if (secureServer) {
|
|
15
|
+
WebsocketService_1.default.getInstance().setSecureWebsocket(new WebSocket.Server(isDetached ? { noServer: true } : { server: secureServer }));
|
|
16
|
+
}
|
|
17
|
+
WebsocketService_1.default.getInstance().setHttpServer(server);
|
|
18
|
+
WebsocketService_1.default.getInstance().setHttpServer(secureServer);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/images/logo.png
ADDED
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Container } from 'inversify';
|
|
2
|
+
declare const container: Container;
|
|
3
|
+
export declare const decorators: {
|
|
4
|
+
lazyInject: (serviceIdentifier: string | symbol | interfaces.Newable<any> | interfaces.Abstract<any>) => (proto: any, key: string) => void;
|
|
5
|
+
lazyInjectNamed: (serviceIdentifier: string | symbol | interfaces.Newable<any> | interfaces.Abstract<any>, named: string) => (proto: any, key: string) => void;
|
|
6
|
+
lazyInjectTagged: (serviceIdentifier: string | symbol | interfaces.Newable<any> | interfaces.Abstract<any>, key: string, value: any) => (proto: any, propertyName: string) => void;
|
|
7
|
+
lazyMultiInject: (serviceIdentifier: string | symbol | interfaces.Newable<any> | interfaces.Abstract<any>) => (proto: any, key: string) => void;
|
|
8
|
+
};
|
|
9
|
+
export default container;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decorators = void 0;
|
|
4
|
+
const inversify_1 = require("inversify");
|
|
5
|
+
const inversify_inject_decorators_1 = require("inversify-inject-decorators");
|
|
6
|
+
const container = new inversify_1.Container({ autobind: true });
|
|
7
|
+
exports.decorators = (0, inversify_inject_decorators_1.default)(container);
|
|
8
|
+
exports.default = container;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const EXPRESSIVE_TEA_PROXY_CLASS = "ExpressiveTeaProxy";
|