@zerooneit/expressive-tea 1.3.0-beta.5 → 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/README.md +564 -174
- package/classes/Boot.d.ts +94 -3
- package/classes/Boot.js +171 -51
- package/classes/Engine.d.ts +59 -10
- package/classes/Engine.js +72 -11
- package/classes/EngineRegistry.d.ts +154 -0
- package/classes/EngineRegistry.js +247 -0
- package/classes/LoadBalancer.js +2 -5
- package/classes/ProxyRoute.d.ts +3 -3
- package/classes/ProxyRoute.js +5 -5
- package/classes/Settings.d.ts +31 -2
- package/classes/Settings.js +64 -11
- package/decorators/annotations.d.ts +1 -1
- package/decorators/annotations.js +17 -17
- 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 +15 -15
- package/decorators/module.js +14 -23
- package/decorators/proxy.d.ts +26 -11
- package/decorators/proxy.js +35 -45
- package/decorators/router.d.ts +17 -16
- package/decorators/router.js +32 -52
- package/decorators/server.d.ts +8 -8
- package/decorators/server.js +48 -50
- package/engines/health/index.d.ts +120 -0
- package/engines/health/index.js +179 -0
- package/engines/http/index.d.ts +6 -7
- package/engines/http/index.js +22 -17
- package/engines/index.d.ts +32 -0
- package/engines/index.js +112 -0
- package/engines/socketio/index.d.ts +2 -1
- package/engines/socketio/index.js +16 -6
- package/engines/teacup/index.d.ts +13 -0
- package/engines/teacup/index.js +61 -11
- package/engines/teapot/index.d.ts +15 -2
- package/engines/teapot/index.js +61 -13
- package/engines/websocket/index.d.ts +4 -1
- package/engines/websocket/index.js +10 -2
- package/eslint.config.mjs +138 -0
- package/exceptions/RequestExceptions.d.ts +3 -3
- package/helpers/boot-helper.d.ts +6 -6
- package/helpers/boot-helper.js +30 -24
- package/helpers/decorators.js +7 -6
- package/helpers/promise-helper.d.ts +1 -1
- package/helpers/promise-helper.js +1 -2
- package/helpers/server.d.ts +32 -6
- package/helpers/server.js +101 -61
- package/helpers/teapot-helper.d.ts +5 -8
- package/helpers/teapot-helper.js +39 -11
- package/helpers/websocket-helper.d.ts +3 -5
- package/helpers/websocket-helper.js +3 -3
- package/interfaces/index.d.ts +1 -1
- package/inversify.config.d.ts +4 -4
- package/inversify.config.js +1 -1
- 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 +91 -69
- package/services/DependencyInjection.d.ts +95 -7
- package/services/DependencyInjection.js +123 -5
- package/services/WebsocketService.d.ts +4 -6
- package/services/WebsocketService.js +5 -3
- 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
package/engines/index.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Core Engine Registration
|
|
4
|
+
*
|
|
5
|
+
* Automatically registers all core Expressive Tea engines with the EngineRegistry.
|
|
6
|
+
* Import this file to enable all core engines with proper dependency order.
|
|
7
|
+
*
|
|
8
|
+
* Engine Priority Order:
|
|
9
|
+
* - 0: HTTPEngine (base engine, no dependencies)
|
|
10
|
+
* - 5: HealthCheckEngine (health endpoints, depends on http)
|
|
11
|
+
* - 10: SocketIOEngine, WebsocketEngine (depend on http)
|
|
12
|
+
* - 20: TeapotEngine, TeacupEngine (depend on http, socketio)
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import '@expressive-tea/core/engines'; // Registers all core engines
|
|
17
|
+
* import Boot from '@expressive-tea/core';
|
|
18
|
+
*
|
|
19
|
+
* class MyApp extends Boot {}
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @module engines
|
|
23
|
+
* @since 2.0.0
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.TeacupEngine = exports.TeapotEngine = exports.WebsocketEngine = exports.SocketIOEngine = exports.HealthCheckEngine = exports.HTTPEngine = void 0;
|
|
27
|
+
const EngineRegistry_1 = require("../classes/EngineRegistry");
|
|
28
|
+
const http_1 = require("./http");
|
|
29
|
+
exports.HTTPEngine = http_1.default;
|
|
30
|
+
const health_1 = require("./health");
|
|
31
|
+
exports.HealthCheckEngine = health_1.default;
|
|
32
|
+
const socketio_1 = require("./socketio");
|
|
33
|
+
exports.SocketIOEngine = socketio_1.default;
|
|
34
|
+
const websocket_1 = require("./websocket");
|
|
35
|
+
exports.WebsocketEngine = websocket_1.default;
|
|
36
|
+
const teapot_1 = require("./teapot");
|
|
37
|
+
exports.TeapotEngine = teapot_1.default;
|
|
38
|
+
const teacup_1 = require("./teacup");
|
|
39
|
+
exports.TeacupEngine = teacup_1.default;
|
|
40
|
+
/**
|
|
41
|
+
* Register HTTP Engine
|
|
42
|
+
* Priority: 0 (runs first)
|
|
43
|
+
* Dependencies: none
|
|
44
|
+
*/
|
|
45
|
+
EngineRegistry_1.default.register({
|
|
46
|
+
engine: http_1.default,
|
|
47
|
+
name: 'http',
|
|
48
|
+
version: '2.0.0',
|
|
49
|
+
priority: 0,
|
|
50
|
+
dependencies: []
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* Register Health Check Engine
|
|
54
|
+
* Priority: 5
|
|
55
|
+
* Dependencies: http (needs HTTP server for endpoints)
|
|
56
|
+
*/
|
|
57
|
+
EngineRegistry_1.default.register({
|
|
58
|
+
engine: health_1.default,
|
|
59
|
+
name: 'health',
|
|
60
|
+
version: '2.0.0',
|
|
61
|
+
priority: 5,
|
|
62
|
+
dependencies: ['http']
|
|
63
|
+
});
|
|
64
|
+
/**
|
|
65
|
+
* Register SocketIO Engine
|
|
66
|
+
* Priority: 10
|
|
67
|
+
* Dependencies: http (needs HTTP server to attach to)
|
|
68
|
+
*/
|
|
69
|
+
EngineRegistry_1.default.register({
|
|
70
|
+
engine: socketio_1.default,
|
|
71
|
+
name: 'socketio',
|
|
72
|
+
version: '2.0.0',
|
|
73
|
+
priority: 10,
|
|
74
|
+
dependencies: ['http']
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* Register Websocket Engine
|
|
78
|
+
* Priority: 10
|
|
79
|
+
* Dependencies: http (needs HTTP server to attach to)
|
|
80
|
+
*/
|
|
81
|
+
EngineRegistry_1.default.register({
|
|
82
|
+
engine: websocket_1.default,
|
|
83
|
+
name: 'websocket',
|
|
84
|
+
version: '2.0.0',
|
|
85
|
+
priority: 10,
|
|
86
|
+
dependencies: ['http']
|
|
87
|
+
});
|
|
88
|
+
/**
|
|
89
|
+
* Register Teapot Engine (Microservice Gateway Server)
|
|
90
|
+
* Priority: 20
|
|
91
|
+
* Dependencies: http, socketio
|
|
92
|
+
*/
|
|
93
|
+
EngineRegistry_1.default.register({
|
|
94
|
+
engine: teapot_1.default,
|
|
95
|
+
name: 'teapot',
|
|
96
|
+
version: '2.0.0',
|
|
97
|
+
priority: 20,
|
|
98
|
+
dependencies: ['http', 'socketio']
|
|
99
|
+
});
|
|
100
|
+
/**
|
|
101
|
+
* Register Teacup Engine (Microservice Gateway Client)
|
|
102
|
+
* Priority: 20
|
|
103
|
+
* Dependencies: http, socketio
|
|
104
|
+
*/
|
|
105
|
+
EngineRegistry_1.default.register({
|
|
106
|
+
engine: teacup_1.default,
|
|
107
|
+
name: 'teacup',
|
|
108
|
+
version: '2.0.0',
|
|
109
|
+
priority: 20,
|
|
110
|
+
dependencies: ['http', 'socketio']
|
|
111
|
+
});
|
|
112
|
+
exports.default = EngineRegistry_1.default;
|
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
3
4
|
const socket_io_1 = require("socket.io");
|
|
5
|
+
const inversify_1 = require("inversify");
|
|
4
6
|
const Engine_1 = require("../../classes/Engine");
|
|
5
|
-
const
|
|
7
|
+
const commons_1 = require("@expressive-tea/commons");
|
|
6
8
|
const constants_1 = require("../constants/constants");
|
|
7
|
-
class SocketIOEngine extends Engine_1.default {
|
|
8
|
-
|
|
9
|
+
let SocketIOEngine = class SocketIOEngine extends Engine_1.default {
|
|
10
|
+
init() {
|
|
9
11
|
const commonConfig = {
|
|
10
12
|
path: '/exp-tea/',
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
14
|
transports: ['websocket', 'polling']
|
|
12
15
|
};
|
|
13
16
|
this.io = this.server && new socket_io_1.Server(this.server, Object.assign({}, commonConfig));
|
|
14
17
|
this.ioSecure = this.serverSecure && new socket_io_1.Server(this.serverSecure, Object.assign({}, commonConfig));
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
commons_1.Metadata.set(constants_1.SOCKET_IO_INSTANCE_KEY, this.io, this.context);
|
|
19
|
+
commons_1.Metadata.set(constants_1.SOCKET_IO_SECURE_INSTANCE_KEY, this.ioSecure, this.context);
|
|
17
20
|
}
|
|
18
|
-
|
|
21
|
+
static canRegister() {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
SocketIOEngine = tslib_1.__decorate([
|
|
26
|
+
(0, inversify_1.injectable)(),
|
|
27
|
+
(0, inversify_1.injectFromBase)({ extendConstructorArguments: true })
|
|
28
|
+
], SocketIOEngine);
|
|
19
29
|
exports.default = SocketIOEngine;
|
|
20
30
|
;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ExpressiveTeaEngine from '../../classes/Engine';
|
|
2
|
+
import Boot from '../../classes/Boot';
|
|
2
3
|
export default class TeacupEngine extends ExpressiveTeaEngine {
|
|
3
4
|
private teacupSettings;
|
|
4
5
|
private publicKey;
|
|
@@ -7,8 +8,20 @@ export default class TeacupEngine extends ExpressiveTeaEngine {
|
|
|
7
8
|
private serverSignature;
|
|
8
9
|
private clientSignature;
|
|
9
10
|
private client;
|
|
11
|
+
private isStopping;
|
|
10
12
|
private header;
|
|
11
13
|
private handshaked;
|
|
12
14
|
private accepted;
|
|
13
15
|
start(): Promise<void>;
|
|
16
|
+
static canRegister(ctx?: Boot): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Graceful shutdown for TeacupEngine
|
|
19
|
+
*
|
|
20
|
+
* Disconnects the socket.io-client connection and prevents automatic reconnection.
|
|
21
|
+
* This method is idempotent and safe to call multiple times.
|
|
22
|
+
*
|
|
23
|
+
* @returns {Promise<void>} Promise that resolves when cleanup is complete
|
|
24
|
+
* @since 2.0.0
|
|
25
|
+
*/
|
|
26
|
+
stop(): Promise<void>;
|
|
14
27
|
}
|
package/engines/teacup/index.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return */
|
|
4
5
|
const chalk = require("chalk");
|
|
5
|
-
const
|
|
6
|
-
// tslint:disable-next-line:no-duplicate-imports
|
|
6
|
+
const url_1 = require("url");
|
|
7
7
|
const socket_io_client_1 = require("socket.io-client");
|
|
8
8
|
const inversify_1 = require("inversify");
|
|
9
|
-
const
|
|
10
|
-
const
|
|
9
|
+
const commons_1 = require("@expressive-tea/commons");
|
|
10
|
+
const commons_2 = require("@expressive-tea/commons");
|
|
11
11
|
const teapot_helper_1 = require("../../helpers/teapot-helper");
|
|
12
|
-
const
|
|
12
|
+
const commons_3 = require("@expressive-tea/commons");
|
|
13
13
|
const Engine_1 = require("../../classes/Engine");
|
|
14
14
|
let TeacupEngine = class TeacupEngine extends Engine_1.default {
|
|
15
|
+
constructor() {
|
|
16
|
+
super(...arguments);
|
|
17
|
+
this.isStopping = false;
|
|
18
|
+
}
|
|
15
19
|
header() {
|
|
16
20
|
console.log(chalk.white.bold('Teacup Engine is initializing...'));
|
|
17
21
|
console.log(chalk `
|
|
@@ -26,6 +30,7 @@ let TeacupEngine = class TeacupEngine extends Engine_1.default {
|
|
|
26
30
|
All Communication are encrypted to ensure intruder can not connected, however, please does not share any sensitive data like keys or passwords to avoid security issues.
|
|
27
31
|
`);
|
|
28
32
|
}
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
34
|
handshaked(key, signature, isSecure, cb) {
|
|
30
35
|
try {
|
|
31
36
|
console.log(chalk `{cyan.bold [TEACUP]} - [{magenta.bold ${this.client.id}}]: {yellow.bold Server Verification Started}`);
|
|
@@ -38,31 +43,34 @@ All Communication are encrypted to ensure intruder can not connected, however, p
|
|
|
38
43
|
cb(Buffer.from(this.publicKey), this.clientSignature);
|
|
39
44
|
}
|
|
40
45
|
catch (e) {
|
|
41
|
-
|
|
46
|
+
const error = e;
|
|
47
|
+
console.error(chalk `{cyan.bold [TEACUP]} - {red.bold TEAPOD} {magenta.bold ${this.client.id}}: Failed with next message: ${error.message}`);
|
|
42
48
|
this.client.disconnect();
|
|
43
49
|
}
|
|
44
50
|
}
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
52
|
accepted(cb) {
|
|
46
53
|
var _a;
|
|
47
54
|
console.log(chalk `{cyan.bold [TEACUP]} - [{magenta.bold ${this.client.id}}]: {green.bold Registered} - {blue.bold <${this.teacupSettings.serverUrl}>} <-> {white.bold ${this.teacupSettings.mountTo}}`);
|
|
48
55
|
const encryptedMessage = teapot_helper_1.default.encrypt({
|
|
49
56
|
mountTo: this.teacupSettings.mountTo,
|
|
50
57
|
address: this.teacupSettings.address
|
|
51
|
-
}, this.serverSignature
|
|
58
|
+
}, this.serverSignature);
|
|
52
59
|
cb(encryptedMessage);
|
|
53
60
|
const onClose = () => {
|
|
54
61
|
try {
|
|
55
62
|
this.client.close();
|
|
56
63
|
}
|
|
57
|
-
catch (
|
|
64
|
+
catch (_a) {
|
|
65
|
+
// Intentionally empty - ignore close errors
|
|
58
66
|
}
|
|
59
67
|
};
|
|
60
68
|
this.server.on('close', onClose);
|
|
61
69
|
(_a = this.serverSecure) === null || _a === void 0 ? void 0 : _a.on('close', onClose);
|
|
62
70
|
}
|
|
63
71
|
async start() {
|
|
64
|
-
this.teacupSettings =
|
|
65
|
-
const scheme =
|
|
72
|
+
this.teacupSettings = commons_1.Metadata.get(commons_2.ASSIGN_TEACUP_KEY, (0, commons_3.getClass)(this.context));
|
|
73
|
+
const scheme = new url_1.URL(this.teacupSettings.serverUrl);
|
|
66
74
|
const { publicKey, privateKey } = teapot_helper_1.default.generateKeys(this.teacupSettings.clientKey);
|
|
67
75
|
const protocol = teapot_helper_1.default.httpSchema(scheme.protocol);
|
|
68
76
|
this.publicKey = publicKey;
|
|
@@ -74,13 +82,55 @@ All Communication are encrypted to ensure intruder can not connected, however, p
|
|
|
74
82
|
autoConnect: false
|
|
75
83
|
});
|
|
76
84
|
this.header();
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
77
86
|
this.client.on('handshake', this.handshaked.bind(this));
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
78
88
|
this.client.on('accepted', this.accepted.bind(this));
|
|
79
89
|
this.client.on('error', console.log);
|
|
80
90
|
this.client.connect();
|
|
81
91
|
}
|
|
92
|
+
static canRegister(ctx) {
|
|
93
|
+
return commons_1.Metadata.get(commons_2.ASSIGN_TEACUP_KEY, (0, commons_3.getClass)(ctx), 'isTeacupActive');
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Graceful shutdown for TeacupEngine
|
|
97
|
+
*
|
|
98
|
+
* Disconnects the socket.io-client connection and prevents automatic reconnection.
|
|
99
|
+
* This method is idempotent and safe to call multiple times.
|
|
100
|
+
*
|
|
101
|
+
* @returns {Promise<void>} Promise that resolves when cleanup is complete
|
|
102
|
+
* @since 2.0.0
|
|
103
|
+
*/
|
|
104
|
+
async stop() {
|
|
105
|
+
// Prevent multiple stop calls from racing
|
|
106
|
+
if (this.isStopping) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
this.isStopping = true;
|
|
110
|
+
if (this.client) {
|
|
111
|
+
try {
|
|
112
|
+
// Disable reconnection before disconnecting to prevent automatic reconnect attempts
|
|
113
|
+
this.client.io.opts.reconnection = false;
|
|
114
|
+
// Remove all listeners to prevent any callbacks during shutdown
|
|
115
|
+
this.client.removeAllListeners();
|
|
116
|
+
// Disconnect the socket
|
|
117
|
+
if (this.client.connected) {
|
|
118
|
+
this.client.disconnect();
|
|
119
|
+
}
|
|
120
|
+
// Close the underlying manager to release all resources
|
|
121
|
+
this.client.close();
|
|
122
|
+
console.log('[TEACUP] - Socket connection closed gracefully');
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
// Log but don't throw - we want shutdown to complete even if cleanup has issues
|
|
126
|
+
const error = e;
|
|
127
|
+
console.error(`[TEACUP] - Error during shutdown: ${error.message}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
82
131
|
};
|
|
83
132
|
TeacupEngine = tslib_1.__decorate([
|
|
84
|
-
(0, inversify_1.injectable)()
|
|
133
|
+
(0, inversify_1.injectable)(),
|
|
134
|
+
(0, inversify_1.injectFromBase)({ extendConstructorArguments: true })
|
|
85
135
|
], TeacupEngine);
|
|
86
136
|
exports.default = TeacupEngine;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import ExpressiveTeaEngine from '../../classes/Engine';
|
|
2
|
+
import Boot from '../../classes/Boot';
|
|
2
3
|
export default class TeapotEngine extends ExpressiveTeaEngine {
|
|
3
|
-
private clients;
|
|
4
|
-
private registeredRoute;
|
|
4
|
+
private readonly clients;
|
|
5
|
+
private readonly registeredRoute;
|
|
5
6
|
private teapotSettings;
|
|
6
7
|
private publicKey;
|
|
7
8
|
private privateKey;
|
|
8
9
|
private serverSignature;
|
|
9
10
|
private socketServer;
|
|
11
|
+
private isStopped;
|
|
10
12
|
private static header;
|
|
11
13
|
private registerTeacup;
|
|
12
14
|
private clientVerification;
|
|
@@ -16,4 +18,15 @@ export default class TeapotEngine extends ExpressiveTeaEngine {
|
|
|
16
18
|
private disconnected;
|
|
17
19
|
init(): Promise<void>;
|
|
18
20
|
start(): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Graceful shutdown for the Teapot engine.
|
|
23
|
+
*
|
|
24
|
+
* Disconnects all connected Teacup clients, closes the Socket.IO namespace,
|
|
25
|
+
* and clears internal state. This method is idempotent and safe to call multiple times.
|
|
26
|
+
*
|
|
27
|
+
* @returns {Promise<void>} Promise that resolves when cleanup is complete
|
|
28
|
+
* @since 2.0.0
|
|
29
|
+
*/
|
|
30
|
+
stop(): Promise<void>;
|
|
31
|
+
static canRegister(ctx?: Boot): boolean;
|
|
19
32
|
}
|
package/engines/teapot/index.js
CHANGED
|
@@ -4,17 +4,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const chalk = require("chalk");
|
|
6
6
|
const inversify_1 = require("inversify");
|
|
7
|
-
const
|
|
8
|
-
const
|
|
7
|
+
const commons_1 = require("@expressive-tea/commons");
|
|
8
|
+
const commons_2 = require("@expressive-tea/commons");
|
|
9
9
|
const ProxyRoute_1 = require("../../classes/ProxyRoute");
|
|
10
10
|
const Engine_1 = require("../../classes/Engine");
|
|
11
11
|
const teapot_helper_1 = require("../../helpers/teapot-helper");
|
|
12
|
-
const
|
|
12
|
+
const constants_1 = require("../constants/constants");
|
|
13
|
+
const commons_3 = require("@expressive-tea/commons");
|
|
13
14
|
let TeapotEngine = TeapotEngine_1 = class TeapotEngine extends Engine_1.default {
|
|
14
15
|
constructor() {
|
|
15
16
|
super(...arguments);
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
18
|
this.clients = new Map();
|
|
17
19
|
this.registeredRoute = new Map();
|
|
20
|
+
this.isStopped = false;
|
|
18
21
|
}
|
|
19
22
|
static header(teapotSettings) {
|
|
20
23
|
console.log(chalk.white.bold('Teapot Engine is initializing...'));
|
|
@@ -36,12 +39,11 @@ All Communication are encrypted to ensure intruder can not connected, however, p
|
|
|
36
39
|
registerTeacup(teacup) {
|
|
37
40
|
console.log(chalk `{cyan.bold [TEAPOT]} - {blue TEACUP} [{magenta.bold ${teacup.id}}]: {grey.bold Connected}`);
|
|
38
41
|
teacup.emit('handshake', Buffer.from(this.publicKey), this.serverSignature, Boolean(this.serverSecure), this.clientVerification.bind(this, teacup));
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
39
43
|
teacup.on('disconnect', this.disconnected.bind(this, teacup));
|
|
40
44
|
}
|
|
41
45
|
clientVerification(teacup, userPublicKey, userSignature) {
|
|
42
46
|
console.log(chalk `{cyan.bold [TEAPOT]} - {blue TEACUP} [{magenta.bold ${teacup.id}}]: {yellow.bold Client Verification Started}`);
|
|
43
|
-
console.log('userKey', userPublicKey.toString('base64'));
|
|
44
|
-
console.log('userSignature', userSignature.toString('base64'));
|
|
45
47
|
try {
|
|
46
48
|
if (!teapot_helper_1.default.verify(this.teapotSettings.clientKey, userPublicKey.toString('ascii'), userSignature)) {
|
|
47
49
|
console.log(chalk `{cyan.bold [TEAPOT]} - {red.bold TEACUP} [{magenta.bold ${teacup.id}}]: Failed to verify and will be disconnected...`);
|
|
@@ -55,15 +57,19 @@ All Communication are encrypted to ensure intruder can not connected, however, p
|
|
|
55
57
|
console.log(chalk `{cyan.bold [TEAPOT]} - {blue TEACUP} [{magenta.bold ${teacup.id}}]: {green.bold Client Verified}`);
|
|
56
58
|
}
|
|
57
59
|
catch (e) {
|
|
58
|
-
|
|
60
|
+
const error = e;
|
|
61
|
+
console.log(chalk `{cyan.bold [TEAPOT]} - {red.bold TEACUP} [{magenta.bold ${teacup.id}}]: Failed wiht next message: ${error.message}`);
|
|
59
62
|
teacup.disconnect();
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
registered(teacup, encryptedMessage) {
|
|
63
66
|
try {
|
|
64
|
-
const message = teapot_helper_1.default.decrypt(encryptedMessage, this.serverSignature
|
|
67
|
+
const message = teapot_helper_1.default.decrypt(encryptedMessage, this.serverSignature);
|
|
65
68
|
const isRegistered = this.registeredRoute.has(message.mountTo);
|
|
66
69
|
const proxyRoute = isRegistered ? this.registeredRoute.get(message.mountTo) : new ProxyRoute_1.default(message.mountTo);
|
|
70
|
+
if (!proxyRoute) {
|
|
71
|
+
throw new Error('Failed to create or retrieve proxy route');
|
|
72
|
+
}
|
|
67
73
|
proxyRoute.registerServer(message.address, teacup.id);
|
|
68
74
|
if (!isRegistered) {
|
|
69
75
|
this.registeredRoute.set(message.mountTo, proxyRoute);
|
|
@@ -72,13 +78,16 @@ All Communication are encrypted to ensure intruder can not connected, however, p
|
|
|
72
78
|
console.log(chalk `{cyan.bold [TEAPOT]} - {blue TEACUP} [{magenta.bold ${teacup.id}}] {blue.bold <${message.address}>} <--> {white.bold ${message.mountTo}}`);
|
|
73
79
|
}
|
|
74
80
|
catch (e) {
|
|
75
|
-
|
|
81
|
+
const error = e;
|
|
82
|
+
console.log(chalk `{cyan.bold [TEAPOT]} - {red.bold TEACUP} {magenta.bold ${teacup.id}}: Failed wiht next message: ${error.message}`);
|
|
76
83
|
}
|
|
77
84
|
}
|
|
78
85
|
removeFromRoutes(routes = [], id) {
|
|
79
86
|
routes.forEach(route => {
|
|
80
87
|
const proxyRoute = this.registeredRoute.get(route);
|
|
81
|
-
proxyRoute
|
|
88
|
+
if (proxyRoute) {
|
|
89
|
+
proxyRoute.unregisterServer(id);
|
|
90
|
+
}
|
|
82
91
|
});
|
|
83
92
|
}
|
|
84
93
|
findClientInRoutes(teacupId) {
|
|
@@ -97,23 +106,62 @@ All Communication are encrypted to ensure intruder can not connected, however, p
|
|
|
97
106
|
console.log(chalk `{cyan.bold [TEAPOT]} - {blue TEACUP} [{magenta.bold ${teacup.id}}]: Got disconnected by ${reason}`);
|
|
98
107
|
}
|
|
99
108
|
catch (e) {
|
|
100
|
-
|
|
109
|
+
const error = e;
|
|
110
|
+
console.log(chalk `{cyan.bold [TEAPOT]} - {red.bold TEACUP} {magenta.bold ${teacup.id}}: Failed wiht next message: ${error.message}`);
|
|
101
111
|
}
|
|
102
112
|
}
|
|
103
113
|
async init() {
|
|
104
|
-
|
|
114
|
+
// Metadata is stored on the class by decorators, not on instances
|
|
115
|
+
this.teapotSettings = commons_1.Metadata.get(commons_2.ASSIGN_TEAPOT_KEY, (0, commons_3.getClass)(this.context));
|
|
105
116
|
const { publicKey, privateKey } = teapot_helper_1.default.generateKeys(this.teapotSettings.serverKey);
|
|
106
117
|
this.publicKey = publicKey;
|
|
107
118
|
this.privateKey = privateKey;
|
|
108
119
|
this.serverSignature = teapot_helper_1.default.sign(this.teapotSettings.clientKey, privateKey, this.teapotSettings.serverKey);
|
|
109
120
|
}
|
|
110
121
|
async start() {
|
|
111
|
-
|
|
122
|
+
// Socket.IO instance is stored on the Boot instance at runtime (not on the class)
|
|
123
|
+
this.socketServer = commons_1.Metadata.get(constants_1.SOCKET_IO_INSTANCE_KEY, this.context).of('/teapot');
|
|
112
124
|
TeapotEngine_1.header(this.teapotSettings);
|
|
125
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
113
126
|
this.socketServer.on('connection', this.registerTeacup.bind(this));
|
|
114
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Graceful shutdown for the Teapot engine.
|
|
130
|
+
*
|
|
131
|
+
* Disconnects all connected Teacup clients, closes the Socket.IO namespace,
|
|
132
|
+
* and clears internal state. This method is idempotent and safe to call multiple times.
|
|
133
|
+
*
|
|
134
|
+
* @returns {Promise<void>} Promise that resolves when cleanup is complete
|
|
135
|
+
* @since 2.0.0
|
|
136
|
+
*/
|
|
137
|
+
async stop() {
|
|
138
|
+
// Idempotent: skip if already stopped or never started
|
|
139
|
+
if (this.isStopped || !this.socketServer) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.isStopped = true;
|
|
143
|
+
console.log(chalk.cyan.bold('[TEAPOT]') + ' - Initiating graceful shutdown...');
|
|
144
|
+
// Disconnect all connected clients gracefully
|
|
145
|
+
const connectedSockets = await this.socketServer.fetchSockets();
|
|
146
|
+
for (const socket of connectedSockets) {
|
|
147
|
+
const routes = this.findClientInRoutes(socket.id);
|
|
148
|
+
this.removeFromRoutes(routes, socket.id);
|
|
149
|
+
socket.disconnect(true);
|
|
150
|
+
console.log(chalk `{cyan.bold [TEAPOT]} - {blue TEACUP} [{magenta.bold ${socket.id}}]: {yellow.bold Disconnected for shutdown}`);
|
|
151
|
+
}
|
|
152
|
+
// Remove all listeners from the namespace
|
|
153
|
+
this.socketServer.removeAllListeners();
|
|
154
|
+
// Clear internal state
|
|
155
|
+
this.clients.clear();
|
|
156
|
+
this.registeredRoute.clear();
|
|
157
|
+
console.log(chalk.cyan.bold('[TEAPOT]') + ' - Graceful shutdown complete.');
|
|
158
|
+
}
|
|
159
|
+
static canRegister(ctx) {
|
|
160
|
+
return commons_1.Metadata.get(commons_2.ASSIGN_TEAPOT_KEY, (0, commons_3.getClass)(ctx), 'isTeapotActive');
|
|
161
|
+
}
|
|
115
162
|
};
|
|
116
163
|
TeapotEngine = TeapotEngine_1 = tslib_1.__decorate([
|
|
117
|
-
(0, inversify_1.injectable)()
|
|
164
|
+
(0, inversify_1.injectable)(),
|
|
165
|
+
(0, inversify_1.injectFromBase)({ extendConstructorArguments: true })
|
|
118
166
|
], TeapotEngine);
|
|
119
167
|
exports.default = TeapotEngine;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import ExpressiveTeaEngine from '../../classes/Engine';
|
|
2
|
+
import Boot from '../../classes/Boot';
|
|
3
|
+
import Settings from '../../classes/Settings';
|
|
2
4
|
export default class WebsocketEngine extends ExpressiveTeaEngine {
|
|
3
5
|
canStart: boolean;
|
|
4
6
|
isDetached: boolean;
|
|
5
|
-
init():
|
|
7
|
+
init(): void;
|
|
8
|
+
static canRegister(ctx?: Boot, settings?: Settings): boolean;
|
|
6
9
|
}
|
|
@@ -11,8 +11,10 @@ let WebsocketEngine = class WebsocketEngine extends Engine_1.default {
|
|
|
11
11
|
this.canStart = false;
|
|
12
12
|
this.isDetached = false;
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
init() {
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
15
16
|
this.canStart = this.settings.get('startWebsocket');
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
16
18
|
this.isDetached = this.settings.get('detachWebsocket');
|
|
17
19
|
if (this.canStart) {
|
|
18
20
|
WebsocketService_1.default.init();
|
|
@@ -24,8 +26,14 @@ let WebsocketEngine = class WebsocketEngine extends Engine_1.default {
|
|
|
24
26
|
WebsocketService_1.default.getInstance().setHttpServer(this.serverSecure);
|
|
25
27
|
}
|
|
26
28
|
}
|
|
29
|
+
static canRegister(ctx, settings) {
|
|
30
|
+
var _a;
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
32
|
+
return (_a = settings === null || settings === void 0 ? void 0 : settings.get('startWebsocket')) !== null && _a !== void 0 ? _a : false;
|
|
33
|
+
}
|
|
27
34
|
};
|
|
28
35
|
WebsocketEngine = tslib_1.__decorate([
|
|
29
|
-
(0, inversify_1.injectable)()
|
|
36
|
+
(0, inversify_1.injectable)(),
|
|
37
|
+
(0, inversify_1.injectFromBase)({ extendConstructorArguments: true })
|
|
30
38
|
], WebsocketEngine);
|
|
31
39
|
exports.default = WebsocketEngine;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import eslint from '@eslint/js';
|
|
3
|
+
import tseslintPlugin from '@typescript-eslint/eslint-plugin';
|
|
4
|
+
import tsparser from '@typescript-eslint/parser';
|
|
5
|
+
import jsdoc from 'eslint-plugin-jsdoc';
|
|
6
|
+
import globals from 'globals';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { dirname } from 'node:path';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
|
|
13
|
+
export default [
|
|
14
|
+
// Global ignores (applies to all configs)
|
|
15
|
+
{
|
|
16
|
+
ignores: [
|
|
17
|
+
'benchmark/**',
|
|
18
|
+
'__test__/mock/*',
|
|
19
|
+
'__test__/**/helper/*',
|
|
20
|
+
'node_modules/**',
|
|
21
|
+
'coverage/**',
|
|
22
|
+
'dist/**',
|
|
23
|
+
'**/*.js',
|
|
24
|
+
'**/*.d.ts',
|
|
25
|
+
'**/*.js.map',
|
|
26
|
+
'**/*.d.ts.map',
|
|
27
|
+
'!eslint.config.js',
|
|
28
|
+
'!jest.config.js',
|
|
29
|
+
'!gulpfile.js',
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
// Base ESLint recommended rules
|
|
34
|
+
eslint.configs.recommended,
|
|
35
|
+
|
|
36
|
+
// Main configuration for TypeScript files
|
|
37
|
+
{
|
|
38
|
+
files: ['**/*.ts'],
|
|
39
|
+
languageOptions: {
|
|
40
|
+
parser: tsparser,
|
|
41
|
+
parserOptions: {
|
|
42
|
+
project: './tsconfig.linter.json',
|
|
43
|
+
tsconfigRootDir: __dirname,
|
|
44
|
+
},
|
|
45
|
+
globals: {
|
|
46
|
+
...globals.browser,
|
|
47
|
+
...globals.node,
|
|
48
|
+
...globals.es2021,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
plugins: {
|
|
52
|
+
'@typescript-eslint': tseslintPlugin,
|
|
53
|
+
jsdoc,
|
|
54
|
+
},
|
|
55
|
+
rules: {
|
|
56
|
+
// Apply TypeScript recommended rules
|
|
57
|
+
...tseslintPlugin.configs.recommended.rules,
|
|
58
|
+
|
|
59
|
+
// Disable semicolon requirements
|
|
60
|
+
'semi': 'off',
|
|
61
|
+
'@typescript-eslint/semi': 'off',
|
|
62
|
+
|
|
63
|
+
// Array type preferences
|
|
64
|
+
'@typescript-eslint/array-type': ['error', { default: 'array' }],
|
|
65
|
+
|
|
66
|
+
// Return await handling
|
|
67
|
+
'@typescript-eslint/return-await': 'off',
|
|
68
|
+
|
|
69
|
+
// Boolean expression strictness
|
|
70
|
+
'@typescript-eslint/strict-boolean-expressions': 'off',
|
|
71
|
+
|
|
72
|
+
// Function return type requirements
|
|
73
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
74
|
+
|
|
75
|
+
// Allow classes with only static members
|
|
76
|
+
'@typescript-eslint/no-extraneous-class': 'off',
|
|
77
|
+
|
|
78
|
+
// Unsafe argument warnings
|
|
79
|
+
'@typescript-eslint/no-unsafe-argument': 'warn',
|
|
80
|
+
'@typescript-eslint/no-unsafe-assignment': 'warn',
|
|
81
|
+
'@typescript-eslint/no-unsafe-member-access': 'warn',
|
|
82
|
+
'@typescript-eslint/no-unsafe-call': 'warn',
|
|
83
|
+
'@typescript-eslint/no-unsafe-return': 'warn',
|
|
84
|
+
|
|
85
|
+
// Unused variables with ignore patterns
|
|
86
|
+
'@typescript-eslint/no-unused-vars': ['error', {
|
|
87
|
+
argsIgnorePattern: '^_',
|
|
88
|
+
varsIgnorePattern: '^_',
|
|
89
|
+
}],
|
|
90
|
+
|
|
91
|
+
// Allow explicit any where needed (warn instead of error)
|
|
92
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
93
|
+
|
|
94
|
+
// Allow non-null assertions (used in tests)
|
|
95
|
+
'@typescript-eslint/no-non-null-assertion': 'off',
|
|
96
|
+
|
|
97
|
+
// Allow require imports (used in some configs)
|
|
98
|
+
'@typescript-eslint/no-require-imports': 'off',
|
|
99
|
+
|
|
100
|
+
// General rules
|
|
101
|
+
'no-duplicate-imports': 'off',
|
|
102
|
+
'no-unused-vars': 'off', // Use TypeScript version instead
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
// Configuration for test files
|
|
107
|
+
{
|
|
108
|
+
files: ['__test__/**/*.ts', '**/*.spec.ts', '**/*.test.ts'],
|
|
109
|
+
languageOptions: {
|
|
110
|
+
globals: {
|
|
111
|
+
...globals.jest,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
rules: {
|
|
115
|
+
// Relax TypeScript strict rules for test files
|
|
116
|
+
'@typescript-eslint/no-unsafe-argument': 'off',
|
|
117
|
+
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
118
|
+
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
119
|
+
'@typescript-eslint/no-unsafe-call': 'off',
|
|
120
|
+
'@typescript-eslint/no-unsafe-return': 'off',
|
|
121
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// Configuration for JavaScript config files
|
|
126
|
+
{
|
|
127
|
+
files: ['*.js', '*.mjs', '*.cjs'],
|
|
128
|
+
languageOptions: {
|
|
129
|
+
globals: {
|
|
130
|
+
...globals.node,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
rules: {
|
|
134
|
+
'@typescript-eslint/no-require-imports': 'off',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
|