@scpxl/nodejs-framework 1.0.17 → 1.0.20
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/README.md +29 -0
- package/dist/api-requester/api-requester.d.ts +27 -6
- package/dist/api-requester/api-requester.d.ts.map +1 -1
- package/dist/api-requester/api-requester.js +188 -26
- package/dist/api-requester/api-requester.js.map +2 -2
- package/dist/api-requester/index.d.ts +1 -0
- package/dist/api-requester/index.d.ts.map +1 -1
- package/dist/api-requester/index.js.map +1 -1
- package/dist/application/base-application.d.ts +15 -22
- package/dist/application/base-application.d.ts.map +1 -1
- package/dist/application/base-application.js +145 -111
- package/dist/application/base-application.js.map +2 -2
- package/dist/application/command-application.d.ts.map +1 -1
- package/dist/application/command-application.js +3 -4
- package/dist/application/command-application.js.map +2 -2
- package/dist/application/web-application.d.ts.map +1 -1
- package/dist/application/web-application.js +9 -2
- package/dist/application/web-application.js.map +2 -2
- package/dist/cache/manager.d.ts +87 -6
- package/dist/cache/manager.d.ts.map +1 -1
- package/dist/cache/manager.js +77 -30
- package/dist/cache/manager.js.map +2 -2
- package/dist/cluster/cluster-manager.d.ts.map +1 -1
- package/dist/cluster/cluster-manager.js +5 -8
- package/dist/cluster/cluster-manager.js.map +2 -2
- package/dist/config/env.d.ts +11 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +103 -0
- package/dist/config/env.js.map +7 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +7 -0
- package/dist/config/schema.d.ts +408 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +218 -0
- package/dist/config/schema.js.map +7 -0
- package/dist/database/dynamic-entity.d.ts.map +1 -1
- package/dist/database/dynamic-entity.js +6 -2
- package/dist/database/dynamic-entity.js.map +2 -2
- package/dist/database/instance.d.ts.map +1 -1
- package/dist/database/instance.js +0 -8
- package/dist/database/instance.js.map +2 -2
- package/dist/database/manager.d.ts.map +1 -1
- package/dist/database/manager.js +71 -9
- package/dist/database/manager.js.map +2 -2
- package/dist/error/error-reporter.d.ts +96 -0
- package/dist/error/error-reporter.d.ts.map +1 -0
- package/dist/error/error-reporter.js +228 -0
- package/dist/error/error-reporter.js.map +7 -0
- package/dist/error/error.interface.d.ts +126 -0
- package/dist/error/error.interface.d.ts.map +1 -0
- package/dist/error/error.interface.js +45 -0
- package/dist/error/error.interface.js.map +7 -0
- package/dist/error/framework-errors.d.ts +113 -0
- package/dist/error/framework-errors.d.ts.map +1 -0
- package/dist/error/framework-errors.js +176 -0
- package/dist/error/framework-errors.js.map +7 -0
- package/dist/error/index.d.ts +6 -0
- package/dist/error/index.d.ts.map +1 -0
- package/dist/error/index.js +34 -0
- package/dist/error/index.js.map +7 -0
- package/dist/event/manager.d.ts.map +1 -1
- package/dist/event/manager.js +2 -9
- package/dist/event/manager.js.map +2 -2
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +2 -2
- package/dist/lifecycle/exit.d.ts +2 -1
- package/dist/lifecycle/exit.d.ts.map +1 -1
- package/dist/lifecycle/exit.js +6 -0
- package/dist/lifecycle/exit.js.map +2 -2
- package/dist/lifecycle/index.d.ts +7 -0
- package/dist/lifecycle/index.d.ts.map +1 -0
- package/dist/lifecycle/index.js +12 -0
- package/dist/lifecycle/index.js.map +7 -0
- package/dist/lifecycle/lifecycle-manager.d.ts +55 -2
- package/dist/lifecycle/lifecycle-manager.d.ts.map +1 -1
- package/dist/lifecycle/lifecycle-manager.js +233 -7
- package/dist/lifecycle/lifecycle-manager.js.map +3 -3
- package/dist/lifecycle/shutdown-controller.d.ts +15 -0
- package/dist/lifecycle/shutdown-controller.d.ts.map +1 -0
- package/dist/lifecycle/shutdown-controller.js +38 -0
- package/dist/lifecycle/shutdown-controller.js.map +7 -0
- package/dist/lifecycle/types.d.ts +28 -0
- package/dist/lifecycle/types.d.ts.map +1 -0
- package/dist/lifecycle/types.js +13 -0
- package/dist/lifecycle/types.js.map +7 -0
- package/dist/logger/logger.d.ts +2 -1
- package/dist/logger/logger.d.ts.map +1 -1
- package/dist/logger/logger.js +35 -11
- package/dist/logger/logger.js.map +2 -2
- package/dist/performance/cache-performance.d.ts +6 -0
- package/dist/performance/cache-performance.d.ts.map +1 -1
- package/dist/performance/cache-performance.js +16 -0
- package/dist/performance/cache-performance.js.map +2 -2
- package/dist/performance/index.d.ts +1 -0
- package/dist/performance/index.d.ts.map +1 -1
- package/dist/performance/index.js +1 -0
- package/dist/performance/index.js.map +2 -2
- package/dist/performance/performance-monitor.d.ts.map +1 -1
- package/dist/performance/performance-monitor.js +47 -18
- package/dist/performance/performance-monitor.js.map +2 -2
- package/dist/performance/performance-monitor.plugin.d.ts +24 -0
- package/dist/performance/performance-monitor.plugin.d.ts.map +1 -0
- package/dist/performance/performance-monitor.plugin.js +89 -0
- package/dist/performance/performance-monitor.plugin.js.map +7 -0
- package/dist/performance/webserver-performance.js +1 -1
- package/dist/performance/webserver-performance.js.map +2 -2
- package/dist/queue/manager.d.ts.map +1 -1
- package/dist/queue/manager.js +3 -10
- package/dist/queue/manager.js.map +2 -2
- package/dist/queue/worker.d.ts.map +1 -1
- package/dist/queue/worker.js +2 -2
- package/dist/queue/worker.js.map +2 -2
- package/dist/redis/manager.d.ts.map +1 -1
- package/dist/redis/manager.js +228 -33
- package/dist/redis/manager.js.map +2 -2
- package/dist/request-context/index.d.ts +3 -0
- package/dist/request-context/index.d.ts.map +1 -0
- package/dist/request-context/index.js +25 -0
- package/dist/request-context/index.js.map +7 -0
- package/dist/request-context/request-context.d.ts +108 -0
- package/dist/request-context/request-context.d.ts.map +1 -0
- package/dist/request-context/request-context.interface.d.ts +46 -0
- package/dist/request-context/request-context.interface.d.ts.map +1 -0
- package/dist/request-context/request-context.interface.js +1 -0
- package/dist/request-context/request-context.interface.js.map +7 -0
- package/dist/request-context/request-context.js +79 -0
- package/dist/request-context/request-context.js.map +7 -0
- package/dist/services/aws/s3.js +4 -6
- package/dist/services/aws/s3.js.map +2 -2
- package/dist/util/file.d.ts +13 -0
- package/dist/util/file.d.ts.map +1 -1
- package/dist/util/file.js +46 -9
- package/dist/util/file.js.map +2 -2
- package/dist/util/helper.d.ts +16 -1
- package/dist/util/helper.d.ts.map +1 -1
- package/dist/util/helper.js +19 -43
- package/dist/util/helper.js.map +2 -2
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.d.ts.map +1 -1
- package/dist/util/index.js +18 -16
- package/dist/util/index.js.map +2 -2
- package/dist/util/loader.d.ts.map +1 -1
- package/dist/util/loader.js +13 -2
- package/dist/util/loader.js.map +2 -2
- package/dist/util/os.d.ts.map +1 -1
- package/dist/util/os.js +8 -14
- package/dist/util/os.js.map +2 -2
- package/dist/util/time.d.ts +8 -2
- package/dist/util/time.d.ts.map +1 -1
- package/dist/util/time.js +12 -7
- package/dist/util/time.js.map +2 -2
- package/dist/util/timing.d.ts +36 -0
- package/dist/util/timing.d.ts.map +1 -0
- package/dist/util/timing.interface.d.ts +47 -0
- package/dist/util/timing.interface.d.ts.map +1 -0
- package/dist/util/timing.interface.js +1 -0
- package/dist/util/timing.interface.js.map +7 -0
- package/dist/util/timing.js +98 -0
- package/dist/util/timing.js.map +7 -0
- package/dist/util/url.js +1 -1
- package/dist/util/url.js.map +2 -2
- package/dist/webserver/controller/base.d.ts +3 -1
- package/dist/webserver/controller/base.d.ts.map +1 -1
- package/dist/webserver/controller/base.interface.d.ts +2 -0
- package/dist/webserver/controller/base.interface.d.ts.map +1 -1
- package/dist/webserver/controller/base.js +4 -1
- package/dist/webserver/controller/base.js.map +2 -2
- package/dist/webserver/controller/health.d.ts +8 -1
- package/dist/webserver/controller/health.d.ts.map +1 -1
- package/dist/webserver/controller/health.js +36 -22
- package/dist/webserver/controller/health.js.map +2 -2
- package/dist/webserver/webserver.d.ts +16 -2
- package/dist/webserver/webserver.d.ts.map +1 -1
- package/dist/webserver/webserver.interface.d.ts +37 -0
- package/dist/webserver/webserver.interface.d.ts.map +1 -1
- package/dist/webserver/webserver.interface.js.map +2 -2
- package/dist/webserver/webserver.js +117 -20
- package/dist/webserver/webserver.js.map +2 -2
- package/dist/websocket/controllers/server/system.d.ts.map +1 -1
- package/dist/websocket/controllers/server/system.js.map +2 -2
- package/dist/websocket/websocket-base.d.ts.map +1 -1
- package/dist/websocket/websocket-base.js +2 -3
- package/dist/websocket/websocket-base.js.map +2 -2
- package/dist/websocket/websocket-server.d.ts +1 -1
- package/dist/websocket/websocket-server.d.ts.map +1 -1
- package/dist/websocket/websocket-server.js +7 -31
- package/dist/websocket/websocket-server.js.map +2 -2
- package/package.json +51 -10
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
import crypto from "node:crypto";
|
|
3
4
|
import Fastify from "fastify";
|
|
4
5
|
import cors from "@fastify/cors";
|
|
6
|
+
import helmet from "@fastify/helmet";
|
|
7
|
+
import rateLimit from "@fastify/rate-limit";
|
|
5
8
|
import multipart from "@fastify/multipart";
|
|
6
9
|
import {
|
|
7
10
|
WebServerRouteType
|
|
8
11
|
} from "./webserver.interface.js";
|
|
9
12
|
import { Logger } from "../logger/index.js";
|
|
10
|
-
import { Helper, Loader, Time } from "../util/index.js";
|
|
13
|
+
import { File, Helper, Loader, Time } from "../util/index.js";
|
|
11
14
|
import WebServerUtil from "./util.js";
|
|
12
15
|
import { WebServerHealthController } from "../index.js";
|
|
13
|
-
import {
|
|
16
|
+
import { enterRequestContext } from "../request-context/index.js";
|
|
14
17
|
class WebServer {
|
|
15
18
|
static {
|
|
16
19
|
__name(this, "WebServer");
|
|
@@ -24,6 +27,8 @@ class WebServer {
|
|
|
24
27
|
eventManager;
|
|
25
28
|
databaseInstance;
|
|
26
29
|
fastifyServer;
|
|
30
|
+
lifecycleManager;
|
|
31
|
+
_isReady = false;
|
|
27
32
|
constructor(params) {
|
|
28
33
|
const defaultOptions = {
|
|
29
34
|
host: "0.0.0.0",
|
|
@@ -53,23 +58,70 @@ class WebServer {
|
|
|
53
58
|
this.queueManager = params.queueManager;
|
|
54
59
|
this.eventManager = params.eventManager;
|
|
55
60
|
this.databaseInstance = params.databaseInstance;
|
|
61
|
+
this.lifecycleManager = params.lifecycleManager;
|
|
62
|
+
const defaultBodyLimit = 25 * 1024 * 1024;
|
|
63
|
+
const defaultConnectionTimeout = 10 * 1e3;
|
|
56
64
|
this.fastifyServer = Fastify({
|
|
57
65
|
logger: false,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// 30 minutes
|
|
61
|
-
connectionTimeout: 30 * 60 * 1e3
|
|
66
|
+
bodyLimit: this.options.bodyLimit ?? defaultBodyLimit,
|
|
67
|
+
connectionTimeout: this.options.connectionTimeout ?? defaultConnectionTimeout
|
|
62
68
|
});
|
|
63
69
|
}
|
|
64
70
|
/**
|
|
65
71
|
* Load web server.
|
|
66
72
|
*/
|
|
67
73
|
async load() {
|
|
74
|
+
await this.configureSecurity();
|
|
68
75
|
this.configureHooks();
|
|
69
76
|
this.configureCORS();
|
|
70
77
|
this.configureMultipartUploads();
|
|
71
78
|
await this.configureRoutes();
|
|
72
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Configure security features (Helmet, Rate Limiting)
|
|
82
|
+
*/
|
|
83
|
+
async configureSecurity() {
|
|
84
|
+
const security = this.options.security ?? {};
|
|
85
|
+
const helmetConfig = security.helmet ?? { enabled: true };
|
|
86
|
+
if (helmetConfig.enabled !== false) {
|
|
87
|
+
await this.fastifyServer.register(helmet, {
|
|
88
|
+
contentSecurityPolicy: helmetConfig.contentSecurityPolicy !== false,
|
|
89
|
+
crossOriginEmbedderPolicy: helmetConfig.crossOriginEmbedderPolicy !== false,
|
|
90
|
+
crossOriginOpenerPolicy: helmetConfig.crossOriginOpenerPolicy !== false,
|
|
91
|
+
crossOriginResourcePolicy: helmetConfig.crossOriginResourcePolicy !== false,
|
|
92
|
+
dnsPrefetchControl: helmetConfig.dnsPrefetchControl !== false,
|
|
93
|
+
frameguard: helmetConfig.frameguard !== false,
|
|
94
|
+
hidePoweredBy: helmetConfig.hidePoweredBy !== false,
|
|
95
|
+
hsts: helmetConfig.hsts !== false,
|
|
96
|
+
ieNoOpen: helmetConfig.ieNoOpen !== false,
|
|
97
|
+
noSniff: helmetConfig.noSniff !== false,
|
|
98
|
+
originAgentCluster: helmetConfig.originAgentCluster !== false,
|
|
99
|
+
permittedCrossDomainPolicies: helmetConfig.permittedCrossDomainPolicies !== false,
|
|
100
|
+
referrerPolicy: helmetConfig.referrerPolicy !== false,
|
|
101
|
+
xssFilter: helmetConfig.xssFilter !== false
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const rateLimitConfig = security.rateLimit ?? { enabled: true };
|
|
105
|
+
if (rateLimitConfig.enabled !== false) {
|
|
106
|
+
await this.fastifyServer.register(rateLimit, {
|
|
107
|
+
max: rateLimitConfig.max ?? 1e3,
|
|
108
|
+
timeWindow: rateLimitConfig.timeWindow ?? "1 minute",
|
|
109
|
+
ban: rateLimitConfig.ban,
|
|
110
|
+
cache: rateLimitConfig.cache ?? 5e3
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (process.env.NODE_ENV === "production" && this.options.cors?.enabled) {
|
|
114
|
+
const corsConfig = this.options.cors;
|
|
115
|
+
if (corsConfig.urls?.includes("*")) {
|
|
116
|
+
this.logger.warn({
|
|
117
|
+
message: "Wildcard CORS (*) is enabled in production - this is a security risk",
|
|
118
|
+
meta: {
|
|
119
|
+
recommendation: "Specify allowed origins explicitly"
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
73
125
|
/**
|
|
74
126
|
* Configure hooks.
|
|
75
127
|
*/
|
|
@@ -97,17 +149,24 @@ class WebServer {
|
|
|
97
149
|
if (this.options.debug?.simulateSlowConnection?.enabled && this.options.debug?.simulateSlowConnection?.delay && this.options.debug?.simulateSlowConnection?.delay > 0) {
|
|
98
150
|
await new Promise((resolve) => setTimeout(resolve, this.options.debug?.simulateSlowConnection?.delay));
|
|
99
151
|
}
|
|
100
|
-
const
|
|
152
|
+
const requestId = request.headers["x-request-id"] ?? crypto.randomUUID();
|
|
153
|
+
request.requestId = requestId;
|
|
154
|
+
const pathsToIgnore = ["/health/live", "/health/ready"];
|
|
101
155
|
if (pathsToIgnore.includes(request.url) || request.method === "OPTIONS") {
|
|
102
156
|
} else {
|
|
103
|
-
|
|
157
|
+
const startTime = Time.now();
|
|
158
|
+
request.startTime = startTime;
|
|
159
|
+
enterRequestContext({ requestId, startTime });
|
|
104
160
|
}
|
|
105
161
|
}
|
|
106
162
|
async onResponse(request, reply) {
|
|
163
|
+
if (request.requestId) {
|
|
164
|
+
reply.header("X-Request-ID", request.requestId);
|
|
165
|
+
}
|
|
107
166
|
if (!request.startTime) {
|
|
108
167
|
return;
|
|
109
168
|
}
|
|
110
|
-
const executionTime = Time.
|
|
169
|
+
const executionTime = Time.calculateElapsedTimeMs({
|
|
111
170
|
startTime: request.startTime
|
|
112
171
|
});
|
|
113
172
|
const formattedExecutionTime = Time.formatTime({
|
|
@@ -120,7 +179,7 @@ class WebServer {
|
|
|
120
179
|
Path: request.url,
|
|
121
180
|
Status: reply.statusCode
|
|
122
181
|
};
|
|
123
|
-
if (process.env.NODE_ENV !== "
|
|
182
|
+
if (process.env.NODE_ENV !== "development") {
|
|
124
183
|
logParams.IP = ip.toString();
|
|
125
184
|
}
|
|
126
185
|
logParams.Time = formattedExecutionTime;
|
|
@@ -164,7 +223,7 @@ class WebServer {
|
|
|
164
223
|
* Configure routes.
|
|
165
224
|
*/
|
|
166
225
|
async configureRoutes() {
|
|
167
|
-
const controllersDirectoryExists = await
|
|
226
|
+
const controllersDirectoryExists = await File.pathExists(this.options.controllersDirectory);
|
|
168
227
|
if (!controllersDirectoryExists) {
|
|
169
228
|
Logger.warn({
|
|
170
229
|
message: "Web server controllers directory not found",
|
|
@@ -178,13 +237,22 @@ class WebServer {
|
|
|
178
237
|
directory: this.options.controllersDirectory,
|
|
179
238
|
extensions: [".ts", ".js"]
|
|
180
239
|
});
|
|
181
|
-
this.routes.push(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
240
|
+
this.routes.push(
|
|
241
|
+
{
|
|
242
|
+
type: WebServerRouteType.Default,
|
|
243
|
+
method: "GET",
|
|
244
|
+
path: "/health/live",
|
|
245
|
+
controller: WebServerHealthController,
|
|
246
|
+
action: "live"
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
type: WebServerRouteType.Default,
|
|
250
|
+
method: "GET",
|
|
251
|
+
path: "/health/ready",
|
|
252
|
+
controller: WebServerHealthController,
|
|
253
|
+
action: "ready"
|
|
254
|
+
}
|
|
255
|
+
);
|
|
188
256
|
for (const route of this.routes) {
|
|
189
257
|
let ControllerClass;
|
|
190
258
|
let controllerName;
|
|
@@ -215,7 +283,8 @@ class WebServer {
|
|
|
215
283
|
redisInstance: this.redisInstance,
|
|
216
284
|
queueManager: this.queueManager,
|
|
217
285
|
eventManager: this.eventManager,
|
|
218
|
-
databaseInstance: this.databaseInstance
|
|
286
|
+
databaseInstance: this.databaseInstance,
|
|
287
|
+
lifecycleManager: this.lifecycleManager
|
|
219
288
|
});
|
|
220
289
|
let routeMethod;
|
|
221
290
|
let routeAction;
|
|
@@ -251,6 +320,8 @@ class WebServer {
|
|
|
251
320
|
])
|
|
252
321
|
),
|
|
253
322
|
required: Object.keys(entityValidationSchema.keys).filter(
|
|
323
|
+
// Dynamic schema inspection of joi describe output; keys are from trusted entity definitions
|
|
324
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
254
325
|
(key) => entityValidationSchema.keys[key].flags?.presence === "required"
|
|
255
326
|
)
|
|
256
327
|
} : {};
|
|
@@ -286,6 +357,9 @@ class WebServer {
|
|
|
286
357
|
routeAction,
|
|
287
358
|
routeValidation
|
|
288
359
|
}) {
|
|
360
|
+
if (!/^[A-Za-z0-9_]+$/.test(routeAction) || ["__proto__", "prototype", "constructor"].includes(routeAction)) {
|
|
361
|
+
throw new Error("Invalid controller action name");
|
|
362
|
+
}
|
|
289
363
|
const controllerHandler = controllerInstance[routeAction];
|
|
290
364
|
if (!controllerHandler) {
|
|
291
365
|
Logger.warn({
|
|
@@ -306,7 +380,21 @@ class WebServer {
|
|
|
306
380
|
return;
|
|
307
381
|
}
|
|
308
382
|
const validate = request.compileValidationSchema(routeValidation.schema);
|
|
309
|
-
|
|
383
|
+
let dataToValidate;
|
|
384
|
+
switch (routeValidation.type) {
|
|
385
|
+
case "body":
|
|
386
|
+
dataToValidate = request.body;
|
|
387
|
+
break;
|
|
388
|
+
case "query":
|
|
389
|
+
dataToValidate = request.query;
|
|
390
|
+
break;
|
|
391
|
+
case "params":
|
|
392
|
+
dataToValidate = request.params;
|
|
393
|
+
break;
|
|
394
|
+
default:
|
|
395
|
+
dataToValidate = void 0;
|
|
396
|
+
}
|
|
397
|
+
if (!validate(dataToValidate)) {
|
|
310
398
|
return reply.code(400).send({
|
|
311
399
|
error: validate.errors
|
|
312
400
|
});
|
|
@@ -323,16 +411,25 @@ class WebServer {
|
|
|
323
411
|
host: this.options.host,
|
|
324
412
|
port: this.options.port
|
|
325
413
|
});
|
|
414
|
+
this._isReady = true;
|
|
326
415
|
} catch (error) {
|
|
327
416
|
Logger.error({ error });
|
|
417
|
+
throw error;
|
|
328
418
|
}
|
|
329
419
|
}
|
|
330
420
|
/**
|
|
331
421
|
* Stop web server.
|
|
332
422
|
*/
|
|
333
423
|
async stop() {
|
|
424
|
+
this._isReady = false;
|
|
334
425
|
await this.fastifyServer.close();
|
|
335
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* Check if web server is ready to accept traffic.
|
|
429
|
+
*/
|
|
430
|
+
isReady() {
|
|
431
|
+
return this._isReady && this.fastifyServer.server?.listening === true;
|
|
432
|
+
}
|
|
336
433
|
/**
|
|
337
434
|
* Log web server message
|
|
338
435
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/webserver/webserver.ts"],
|
|
4
|
-
"sourcesContent": ["import Fastify, { type FastifyInstance, type FastifyReply, type FastifyRequest, type HTTPMethods } from 'fastify';\nimport cors from '@fastify/cors';\nimport multipart from '@fastify/multipart';\nimport {\n type WebServerConstructorParams,\n type WebServerOptions,\n type WebServerRoute,\n WebServerRouteType,\n} from './webserver.interface.js';\nimport { Logger } from '../logger/index.js';\nimport { Helper, Loader, Time } from '../util/index.js';\nimport WebServerUtil from './util.js';\nimport type { RedisInstance } from '../redis/index.js';\nimport type { DatabaseInstance } from '../database/index.js';\nimport type { WebServerBaseControllerType } from './controller/base.interface.js';\nimport type { QueueManager } from '../queue/index.js';\nimport { WebServerHealthController } from '../index.js';\nimport type { ApplicationConfig } from '../application/base-application.interface.js';\nimport { existsSync } from 'fs';\nimport type EventManager from '../event/manager.js';\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n startTime?: [number, number];\n }\n}\n\nclass WebServer {\n private logger: typeof Logger = Logger;\n\n private applicationConfig: ApplicationConfig;\n\n private options: WebServerOptions;\n private routes: WebServerRoute[];\n\n private redisInstance: RedisInstance;\n private queueManager: QueueManager;\n private eventManager: EventManager;\n private databaseInstance: DatabaseInstance;\n\n public fastifyServer: FastifyInstance;\n\n constructor(params: WebServerConstructorParams) {\n // Define default options\n const defaultOptions: Partial<WebServerOptions> = {\n host: '0.0.0.0',\n port: 3001,\n cors: {\n enabled: false,\n },\n errors: {\n verbose: false,\n },\n debug: {\n printRoutes: false,\n simulateSlowConnection: {\n enabled: false,\n delay: 250,\n },\n },\n log: {\n startUp: true,\n },\n };\n\n // Merge default options\n const mergedOptions = Helper.defaultsDeep(params.options, defaultOptions);\n\n this.applicationConfig = params.applicationConfig;\n\n this.options = mergedOptions;\n this.routes = params.routes;\n\n this.redisInstance = params.redisInstance;\n this.queueManager = params.queueManager;\n this.eventManager = params.eventManager;\n this.databaseInstance = params.databaseInstance;\n\n // Create Fastify server\n this.fastifyServer = Fastify({\n logger: false,\n // body limit = 5gb\n bodyLimit: 5 * 1024 * 1024 * 1024,\n // 30 minutes\n connectionTimeout: 30 * 60 * 1000,\n });\n }\n\n /**\n * Load web server.\n */\n public async load(): Promise<void> {\n // Configure hooks\n this.configureHooks();\n\n // Configure CORS\n this.configureCORS();\n\n // Configure multipart uploads\n this.configureMultipartUploads();\n\n // Configure routes\n await this.configureRoutes();\n }\n\n /**\n * Configure hooks.\n */\n private configureHooks(): void {\n this.fastifyServer.addHook('onListen', async () => this.onListen());\n this.fastifyServer.addHook('onRequest', async request => this.onRequest(request));\n this.fastifyServer.addHook('onResponse', async (request, reply) => this.onResponse(request, reply));\n this.fastifyServer.addHook('onError', async (request, reply, error) => this.onError(request, reply, error));\n this.fastifyServer.addHook('onClose', async () => this.onClose());\n\n // if (process.env.NODE_ENV === 'local') {\n // this.fastifyServer.addHook('onSend', (request, reply, payload, done) => {\n // reply.header('Cache-Control', 'no-store');\n // done();\n // });\n // }\n }\n\n private async onListen(): Promise<void> {\n const address = this.fastifyServer.server.address();\n const port = typeof address === 'string' ? address : address?.port;\n\n if (this.options.log?.startUp) {\n this.log('Started', {\n Host: this.options.host,\n Port: port,\n // CORS: this.options.cors?.enabled && this.options.cors?..length > 0 ? this.options.corsUrls.join(', ') : 'Disabled',\n CORS: this.options.cors?.enabled ? this.options.cors.urls.join(', ') : 'Disabled',\n 'Fastify Version': this.fastifyServer.version,\n });\n }\n }\n\n private async onRequest(request: FastifyRequest): Promise<void> {\n if (\n this.options.debug?.simulateSlowConnection?.enabled &&\n this.options.debug?.simulateSlowConnection?.delay &&\n this.options.debug?.simulateSlowConnection?.delay > 0\n ) {\n await new Promise(resolve => setTimeout(resolve, this.options.debug?.simulateSlowConnection?.delay));\n }\n\n const pathsToIgnore = ['/health'];\n\n if (pathsToIgnore.includes(request.url) || request.method === 'OPTIONS') {\n // ...\n } else {\n request.startTime = process.hrtime();\n }\n }\n\n private async onResponse(request: FastifyRequest, reply: FastifyReply): Promise<void> {\n if (!request.startTime) {\n return;\n }\n\n const executionTime = Time.calculateElapsedTime({\n startTime: request.startTime,\n });\n const formattedExecutionTime = Time.formatTime({\n time: executionTime,\n numDecimals: 3,\n });\n\n const ip = request.headers['x-forwarded-for'] ?? request.ip;\n\n const logParams: Record<string, unknown> = {\n Method: request.method,\n Path: request.url,\n Status: reply.statusCode,\n };\n\n if (process.env.NODE_ENV !== 'local') {\n logParams.IP = ip.toString();\n }\n\n logParams.Time = formattedExecutionTime;\n\n // if (cluster.isWorker && cluster.worker) {\n // logParams.Worker = cluster.worker.id;\n // }\n\n this.log('API Request', logParams);\n }\n\n private async onError(request: FastifyRequest, reply: FastifyReply, error: Error): Promise<void> {\n // Adjusted for Fastify types\n Logger.error({ error });\n // Implement any additional logic here\n }\n\n private async onClose(): Promise<void> {\n this.log('Stopped');\n }\n\n private configureCORS(): void {\n if (!this.options.cors?.enabled) {\n return;\n }\n\n // Handle wildcard origin for development\n const origin = this.options.cors.urls.includes('*') ? true : this.options.cors.urls;\n\n this.fastifyServer.register(cors, {\n origin,\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n preflightContinue: false,\n optionsSuccessStatus: 204,\n // credentials: true,\n });\n }\n\n private configureMultipartUploads(): void {\n this.fastifyServer.register(multipart, {\n // attachFieldsToBody: true,\n limits: {\n fieldNameSize: 100,\n fieldSize: 1024 * 1024 * 10,\n fields: 10,\n fileSize: 1024 * 1024 * 1024 * 10, // 10GB file size limit\n files: 1,\n headerPairs: 2000,\n },\n });\n }\n\n /**\n * Configure routes.\n */\n private async configureRoutes(): Promise<void> {\n // Check if controllers directory exists\n const controllersDirectoryExists = await existsSync(this.options.controllersDirectory);\n\n if (!controllersDirectoryExists) {\n Logger.warn({\n message: 'Web server controllers directory not found',\n meta: {\n Directory: this.options.controllersDirectory,\n },\n });\n\n return;\n }\n\n // Load controllers\n const controllers = await Loader.loadModulesInDirectory({\n directory: this.options.controllersDirectory,\n extensions: ['.ts', '.js'],\n });\n\n // Add health check route\n this.routes.push({\n type: WebServerRouteType.Default,\n method: 'GET',\n path: '/health',\n controller: WebServerHealthController,\n action: 'health',\n });\n\n // Go through each route\n for (const route of this.routes) {\n let ControllerClass: WebServerBaseControllerType;\n\n let controllerName;\n\n if (route.controller) {\n ControllerClass = route.controller;\n\n controllerName = ControllerClass.name;\n } else if (route.controllerName) {\n ControllerClass = controllers[route.controllerName];\n\n controllerName = route.controllerName;\n } else {\n throw new Error('Web server controller config not found');\n }\n\n if (typeof ControllerClass !== 'function') {\n const controllerPath = `${this.options.controllersDirectory}/${route.controllerName}.ts`;\n\n Logger.warn({\n message: 'Web server controller not found',\n meta: {\n Controller: route.controllerName,\n Path: controllerPath,\n Route: `${route.path}`,\n },\n });\n\n continue;\n }\n\n // Initialize controller instance\n const controllerInstance = new ControllerClass({\n applicationConfig: this.applicationConfig,\n webServerOptions: this.options,\n redisInstance: this.redisInstance,\n queueManager: this.queueManager,\n eventManager: this.eventManager,\n databaseInstance: this.databaseInstance,\n });\n\n let routeMethod;\n let routeAction;\n let routePath;\n\n switch (route.type) {\n case WebServerRouteType.Default: {\n routeMethod = route.method;\n routeAction = route.action;\n routePath = route.path;\n\n this.defineRoute({\n controllerInstance,\n controllerName,\n routeMethod,\n routePath,\n routeAction,\n routeValidation: route.validation,\n });\n\n break;\n }\n case WebServerRouteType.Entity: {\n if (this.applicationConfig.database && this.applicationConfig.database.enabled === true) {\n const entityModel = await Loader.loadEntityModule({\n entitiesDirectory: this.applicationConfig.database.entitiesDirectory,\n entityName: route.entityName,\n });\n\n const entityValidationSchema = entityModel.schema?.describe();\n\n const formattedEntityValidationSchema = entityValidationSchema\n ? {\n type: 'object',\n properties: Object.fromEntries(\n Object.entries(entityValidationSchema.keys).map(([key, value]) => [\n key,\n { type: (value as any).type },\n ]),\n ),\n required: Object.keys(entityValidationSchema.keys).filter(\n key => entityValidationSchema.keys[key].flags?.presence === 'required',\n ),\n }\n : {};\n\n const entityRouteDefinitions = WebServerUtil.getEntityRouteDefinitions({\n basePath: route.path,\n entityValidationSchema: formattedEntityValidationSchema,\n });\n\n for (const entityRouteDefinition of entityRouteDefinitions) {\n this.defineRoute({\n controllerInstance,\n controllerName,\n routeMethod: entityRouteDefinition.method,\n routePath: entityRouteDefinition.path,\n routeAction: entityRouteDefinition.action,\n routeValidation: entityRouteDefinition.validationSchema,\n });\n }\n }\n\n break;\n }\n }\n }\n\n if (this.options.debug?.printRoutes) {\n this.log('Routes:');\n\n console.log(this.fastifyServer.printRoutes());\n }\n }\n\n public async defineRoute({\n controllerInstance,\n controllerName,\n routeMethod,\n routePath,\n routeAction,\n routeValidation,\n }: {\n controllerInstance: any;\n controllerName: string;\n routeMethod: HTTPMethods | HTTPMethods[];\n routePath: string;\n routeAction: string;\n routeValidation?: {\n type: 'body' | 'query' | 'params';\n schema: { [key: string]: any };\n };\n }): Promise<void> {\n // Get controller action handler\n const controllerHandler = controllerInstance[routeAction as keyof typeof controllerInstance];\n\n if (!controllerHandler) {\n Logger.warn({\n message: 'Web server controller action not found',\n meta: {\n Controller: controllerName,\n Action: routeAction,\n },\n });\n\n throw new Error('Web server controller action not found');\n }\n\n // Add route\n this.fastifyServer.route({\n method: routeMethod,\n url: routePath,\n handler: controllerHandler,\n preValidation: async (request, reply) => {\n if (!routeValidation?.schema) {\n // Logger.warn('Web server route validation schema not found', {\n // Controller: controllerName,\n // Action: routeAction,\n // });\n\n return;\n }\n\n const validate = request.compileValidationSchema(routeValidation.schema);\n\n if (!validate(request[routeValidation.type])) {\n return reply.code(400).send({\n error: validate.errors,\n });\n }\n },\n });\n }\n\n /**\n * Start web server.\n */\n public async start(): Promise<void> {\n try {\n await this.fastifyServer.listen({\n host: this.options.host,\n port: this.options.port,\n });\n } catch (error) {\n Logger.error({ error });\n }\n }\n\n /**\n * Stop web server.\n */\n public async stop(): Promise<void> {\n // Close Fastify server\n await this.fastifyServer.close();\n }\n\n /**\n * Log web server message\n */\n public log(message: string, meta?: Record<string, unknown>): void {\n this.logger.custom({ level: 'webServer', message, meta });\n }\n}\n\nexport default WebServer;\n"],
|
|
5
|
-
"mappings": ";;AAAA,OAAO,aAAiG;AACxG,OAAO,UAAU;AACjB,OAAO,eAAe;AACtB;AAAA,EAIE;AAAA,OACK;AACP,SAAS,cAAc;AACvB,SAAS,QAAQ,QAAQ,YAAY;
|
|
4
|
+
"sourcesContent": ["import crypto from 'node:crypto';\nimport Fastify, { type FastifyInstance, type FastifyReply, type FastifyRequest, type HTTPMethods } from 'fastify';\nimport cors from '@fastify/cors';\nimport helmet from '@fastify/helmet';\nimport rateLimit from '@fastify/rate-limit';\nimport multipart from '@fastify/multipart';\nimport {\n type WebServerConstructorParams,\n type WebServerOptions,\n type WebServerRoute,\n WebServerRouteType,\n} from './webserver.interface.js';\nimport { Logger } from '../logger/index.js';\nimport { File, Helper, Loader, Time } from '../util/index.js';\nimport WebServerUtil from './util.js';\nimport type { RedisInstance } from '../redis/index.js';\nimport type { DatabaseInstance } from '../database/index.js';\nimport type { WebServerBaseControllerType } from './controller/base.interface.js';\nimport type { QueueManager } from '../queue/index.js';\nimport { WebServerHealthController } from '../index.js';\nimport type { LifecycleManager } from '../lifecycle/lifecycle-manager.js';\nimport type { ApplicationConfig } from '../application/base-application.interface.js';\nimport type EventManager from '../event/manager.js';\nimport { enterRequestContext } from '../request-context/index.js';\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n startTime?: number;\n requestId?: string;\n }\n}\n\nclass WebServer {\n private logger: typeof Logger = Logger;\n\n private applicationConfig: ApplicationConfig;\n\n private options: WebServerOptions;\n private routes: WebServerRoute[];\n\n private redisInstance: RedisInstance;\n private queueManager: QueueManager;\n private eventManager: EventManager;\n private databaseInstance: DatabaseInstance;\n\n public fastifyServer: FastifyInstance;\n\n private lifecycleManager: LifecycleManager;\n private _isReady = false;\n\n constructor(params: WebServerConstructorParams & { lifecycleManager: LifecycleManager }) {\n // Define default options\n const defaultOptions: Partial<WebServerOptions> = {\n host: '0.0.0.0',\n port: 3001,\n cors: {\n enabled: false,\n },\n errors: {\n verbose: false,\n },\n debug: {\n printRoutes: false,\n simulateSlowConnection: {\n enabled: false,\n delay: 250,\n },\n },\n log: {\n startUp: true,\n },\n };\n\n // Merge default options\n const mergedOptions = Helper.defaultsDeep(params.options, defaultOptions);\n\n this.applicationConfig = params.applicationConfig;\n\n this.options = mergedOptions;\n this.routes = params.routes;\n\n this.redisInstance = params.redisInstance;\n this.queueManager = params.queueManager;\n this.eventManager = params.eventManager;\n this.databaseInstance = params.databaseInstance;\n this.lifecycleManager = params.lifecycleManager;\n\n // Create Fastify server\n const defaultBodyLimit = 25 * 1024 * 1024; // 25MB (safer default)\n const defaultConnectionTimeout = 10 * 1000; // 10 seconds (safer default)\n\n this.fastifyServer = Fastify({\n logger: false,\n bodyLimit: this.options.bodyLimit ?? defaultBodyLimit,\n connectionTimeout: this.options.connectionTimeout ?? defaultConnectionTimeout,\n });\n }\n\n /**\n * Load web server.\n */\n public async load(): Promise<void> {\n // Configure security (helmet, rate limiting)\n await this.configureSecurity();\n\n // Configure hooks\n this.configureHooks();\n\n // Configure CORS\n this.configureCORS();\n\n // Configure multipart uploads\n this.configureMultipartUploads();\n\n // Configure routes\n await this.configureRoutes();\n }\n\n /**\n * Configure security features (Helmet, Rate Limiting)\n */\n private async configureSecurity(): Promise<void> {\n const security = this.options.security ?? {};\n\n // Configure Helmet for security headers\n const helmetConfig = security.helmet ?? { enabled: true };\n if (helmetConfig.enabled !== false) {\n await this.fastifyServer.register(helmet, {\n contentSecurityPolicy: helmetConfig.contentSecurityPolicy !== false,\n crossOriginEmbedderPolicy: helmetConfig.crossOriginEmbedderPolicy !== false,\n crossOriginOpenerPolicy: helmetConfig.crossOriginOpenerPolicy !== false,\n crossOriginResourcePolicy: helmetConfig.crossOriginResourcePolicy !== false,\n dnsPrefetchControl: helmetConfig.dnsPrefetchControl !== false,\n frameguard: helmetConfig.frameguard !== false,\n hidePoweredBy: helmetConfig.hidePoweredBy !== false,\n hsts: helmetConfig.hsts !== false,\n ieNoOpen: helmetConfig.ieNoOpen !== false,\n noSniff: helmetConfig.noSniff !== false,\n originAgentCluster: helmetConfig.originAgentCluster !== false,\n permittedCrossDomainPolicies: helmetConfig.permittedCrossDomainPolicies !== false,\n referrerPolicy: helmetConfig.referrerPolicy !== false,\n xssFilter: helmetConfig.xssFilter !== false,\n });\n }\n\n // Configure rate limiting\n const rateLimitConfig = security.rateLimit ?? { enabled: true };\n if (rateLimitConfig.enabled !== false) {\n await this.fastifyServer.register(rateLimit, {\n max: rateLimitConfig.max ?? 1000,\n timeWindow: rateLimitConfig.timeWindow ?? '1 minute',\n ban: rateLimitConfig.ban,\n cache: rateLimitConfig.cache ?? 5000,\n });\n }\n\n // Warn about wildcard CORS in production\n if (process.env.NODE_ENV === 'production' && this.options.cors?.enabled) {\n const corsConfig = this.options.cors as { enabled: true; urls: string[] };\n if (corsConfig.urls?.includes('*')) {\n this.logger.warn({\n message: 'Wildcard CORS (*) is enabled in production - this is a security risk',\n meta: {\n recommendation: 'Specify allowed origins explicitly',\n },\n });\n }\n }\n }\n\n /**\n * Configure hooks.\n */\n private configureHooks(): void {\n this.fastifyServer.addHook('onListen', async () => this.onListen());\n this.fastifyServer.addHook('onRequest', async request => this.onRequest(request));\n this.fastifyServer.addHook('onResponse', async (request, reply) => this.onResponse(request, reply));\n this.fastifyServer.addHook('onError', async (request, reply, error) => this.onError(request, reply, error));\n this.fastifyServer.addHook('onClose', async () => this.onClose());\n\n // if (process.env.NODE_ENV === 'local') {\n // this.fastifyServer.addHook('onSend', (request, reply, payload, done) => {\n // reply.header('Cache-Control', 'no-store');\n // done();\n // });\n // }\n }\n\n private async onListen(): Promise<void> {\n const address = this.fastifyServer.server.address();\n const port = typeof address === 'string' ? address : address?.port;\n\n if (this.options.log?.startUp) {\n this.log('Started', {\n Host: this.options.host,\n Port: port,\n // CORS: this.options.cors?.enabled && this.options.cors?..length > 0 ? this.options.corsUrls.join(', ') : 'Disabled',\n CORS: this.options.cors?.enabled ? this.options.cors.urls.join(', ') : 'Disabled',\n 'Fastify Version': this.fastifyServer.version,\n });\n }\n }\n\n private async onRequest(request: FastifyRequest): Promise<void> {\n if (\n this.options.debug?.simulateSlowConnection?.enabled &&\n this.options.debug?.simulateSlowConnection?.delay &&\n this.options.debug?.simulateSlowConnection?.delay > 0\n ) {\n await new Promise(resolve => setTimeout(resolve, this.options.debug?.simulateSlowConnection?.delay));\n }\n\n // Generate or use existing request ID for correlation\n const requestId = (request.headers['x-request-id'] as string | undefined) ?? crypto.randomUUID();\n request.requestId = requestId;\n\n const pathsToIgnore = ['/health/live', '/health/ready'];\n\n if (pathsToIgnore.includes(request.url) || request.method === 'OPTIONS') {\n // ...\n } else {\n const startTime = Time.now();\n request.startTime = startTime;\n\n // Initialize AsyncLocalStorage context for this request\n // Using enterWith() to set context for the current async execution\n enterRequestContext({ requestId, startTime });\n }\n }\n\n private async onResponse(request: FastifyRequest, reply: FastifyReply): Promise<void> {\n // Add request ID to response headers for client-side correlation\n if (request.requestId) {\n reply.header('X-Request-ID', request.requestId);\n }\n\n if (!request.startTime) {\n return;\n }\n\n const executionTime = Time.calculateElapsedTimeMs({\n startTime: request.startTime,\n });\n const formattedExecutionTime = Time.formatTime({\n time: executionTime,\n numDecimals: 3,\n });\n\n const ip = request.headers['x-forwarded-for'] ?? request.ip;\n\n const logParams: Record<string, unknown> = {\n Method: request.method,\n Path: request.url,\n Status: reply.statusCode,\n };\n\n if (process.env.NODE_ENV !== 'development') {\n logParams.IP = ip.toString();\n }\n\n logParams.Time = formattedExecutionTime;\n\n // if (cluster.isWorker && cluster.worker) {\n // logParams.Worker = cluster.worker.id;\n // }\n\n this.log('API Request', logParams);\n }\n\n private async onError(request: FastifyRequest, reply: FastifyReply, error: Error): Promise<void> {\n // Adjusted for Fastify types\n Logger.error({ error });\n // Implement any additional logic here\n }\n\n private async onClose(): Promise<void> {\n this.log('Stopped');\n }\n\n private configureCORS(): void {\n if (!this.options.cors?.enabled) {\n return;\n }\n\n // Handle wildcard origin for development\n const origin = this.options.cors.urls.includes('*') ? true : this.options.cors.urls;\n\n this.fastifyServer.register(cors, {\n origin,\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n preflightContinue: false,\n optionsSuccessStatus: 204,\n // credentials: true,\n });\n }\n\n private configureMultipartUploads(): void {\n this.fastifyServer.register(multipart, {\n // attachFieldsToBody: true,\n limits: {\n fieldNameSize: 100,\n fieldSize: 1024 * 1024 * 10,\n fields: 10,\n fileSize: 1024 * 1024 * 1024 * 10, // 10GB file size limit\n files: 1,\n headerPairs: 2000,\n },\n });\n }\n\n /**\n * Configure routes.\n */\n private async configureRoutes(): Promise<void> {\n // Check if controllers directory exists\n const controllersDirectoryExists = await File.pathExists(this.options.controllersDirectory);\n\n if (!controllersDirectoryExists) {\n Logger.warn({\n message: 'Web server controllers directory not found',\n meta: {\n Directory: this.options.controllersDirectory,\n },\n });\n\n return;\n }\n\n // Load controllers\n const controllers = await Loader.loadModulesInDirectory({\n directory: this.options.controllersDirectory,\n extensions: ['.ts', '.js'],\n });\n\n // Add health check routes\n this.routes.push(\n {\n type: WebServerRouteType.Default,\n method: 'GET',\n path: '/health/live',\n controller: WebServerHealthController,\n action: 'live',\n },\n {\n type: WebServerRouteType.Default,\n method: 'GET',\n path: '/health/ready',\n controller: WebServerHealthController,\n action: 'ready',\n },\n );\n\n // Go through each route\n for (const route of this.routes) {\n let ControllerClass: WebServerBaseControllerType;\n\n let controllerName;\n\n if (route.controller) {\n ControllerClass = route.controller;\n\n controllerName = ControllerClass.name;\n } else if (route.controllerName) {\n ControllerClass = controllers[route.controllerName];\n\n controllerName = route.controllerName;\n } else {\n throw new Error('Web server controller config not found');\n }\n\n if (typeof ControllerClass !== 'function') {\n const controllerPath = `${this.options.controllersDirectory}/${route.controllerName}.ts`;\n\n Logger.warn({\n message: 'Web server controller not found',\n meta: {\n Controller: route.controllerName,\n Path: controllerPath,\n Route: `${route.path}`,\n },\n });\n\n continue;\n }\n\n // Initialize controller instance\n const controllerInstance = new ControllerClass({\n applicationConfig: this.applicationConfig,\n webServerOptions: this.options,\n redisInstance: this.redisInstance,\n queueManager: this.queueManager,\n eventManager: this.eventManager,\n databaseInstance: this.databaseInstance,\n lifecycleManager: this.lifecycleManager,\n });\n\n let routeMethod;\n let routeAction;\n let routePath;\n\n switch (route.type) {\n case WebServerRouteType.Default: {\n routeMethod = route.method;\n routeAction = route.action;\n routePath = route.path;\n\n this.defineRoute({\n controllerInstance,\n controllerName,\n routeMethod,\n routePath,\n routeAction,\n routeValidation: route.validation,\n });\n\n break;\n }\n case WebServerRouteType.Entity: {\n if (this.applicationConfig.database && this.applicationConfig.database.enabled === true) {\n const entityModel = await Loader.loadEntityModule({\n entitiesDirectory: this.applicationConfig.database.entitiesDirectory,\n entityName: route.entityName,\n });\n\n const entityValidationSchema = entityModel.schema?.describe();\n\n const formattedEntityValidationSchema = entityValidationSchema\n ? {\n type: 'object',\n properties: Object.fromEntries(\n Object.entries(entityValidationSchema.keys).map(([key, value]) => [\n key,\n { type: (value as any).type },\n ]),\n ),\n required: Object.keys(entityValidationSchema.keys).filter(\n // Dynamic schema inspection of joi describe output; keys are from trusted entity definitions\n // eslint-disable-next-line security/detect-object-injection\n key => entityValidationSchema.keys[key].flags?.presence === 'required',\n ),\n }\n : {};\n\n const entityRouteDefinitions = WebServerUtil.getEntityRouteDefinitions({\n basePath: route.path,\n entityValidationSchema: formattedEntityValidationSchema,\n });\n\n for (const entityRouteDefinition of entityRouteDefinitions) {\n this.defineRoute({\n controllerInstance,\n controllerName,\n routeMethod: entityRouteDefinition.method,\n routePath: entityRouteDefinition.path,\n routeAction: entityRouteDefinition.action,\n routeValidation: entityRouteDefinition.validationSchema,\n });\n }\n }\n\n break;\n }\n }\n }\n\n if (this.options.debug?.printRoutes) {\n this.log('Routes:');\n\n console.log(this.fastifyServer.printRoutes());\n }\n }\n\n public async defineRoute({\n controllerInstance,\n controllerName,\n routeMethod,\n routePath,\n routeAction,\n routeValidation,\n }: {\n controllerInstance: any;\n controllerName: string;\n routeMethod: HTTPMethods | HTTPMethods[];\n routePath: string;\n routeAction: string;\n routeValidation?: {\n type: 'body' | 'query' | 'params';\n schema: { [key: string]: any };\n };\n }): Promise<void> {\n // Get controller action handler\n // Validate action name to avoid prototype access\n if (!/^[A-Za-z0-9_]+$/.test(routeAction) || ['__proto__', 'prototype', 'constructor'].includes(routeAction)) {\n throw new Error('Invalid controller action name');\n }\n // Dynamic access guarded by regex + deny list (keys validated above)\n const controllerHandler = controllerInstance[routeAction as keyof typeof controllerInstance];\n\n if (!controllerHandler) {\n Logger.warn({\n message: 'Web server controller action not found',\n meta: {\n Controller: controllerName,\n Action: routeAction,\n },\n });\n\n throw new Error('Web server controller action not found');\n }\n\n // Add route\n this.fastifyServer.route({\n method: routeMethod,\n url: routePath,\n handler: controllerHandler,\n preValidation: async (request, reply) => {\n if (!routeValidation?.schema) {\n // Logger.warn('Web server route validation schema not found', {\n // Controller: controllerName,\n // Action: routeAction,\n // });\n\n return;\n }\n\n const validate = request.compileValidationSchema(routeValidation.schema);\n\n // Avoid dynamic request[...] access for security lint; map explicitly\n let dataToValidate: any;\n switch (routeValidation.type) {\n case 'body':\n dataToValidate = request.body;\n break;\n case 'query':\n dataToValidate = request.query;\n break;\n case 'params':\n dataToValidate = request.params;\n break;\n default:\n dataToValidate = undefined;\n }\n\n if (!validate(dataToValidate)) {\n return reply.code(400).send({\n error: validate.errors,\n });\n }\n },\n });\n }\n\n /**\n * Start web server.\n */\n public async start(): Promise<void> {\n try {\n await this.fastifyServer.listen({\n host: this.options.host,\n port: this.options.port,\n });\n this._isReady = true;\n } catch (error) {\n Logger.error({ error });\n throw error;\n }\n }\n\n /**\n * Stop web server.\n */\n public async stop(): Promise<void> {\n this._isReady = false;\n // Close Fastify server\n await this.fastifyServer.close();\n }\n\n /**\n * Check if web server is ready to accept traffic.\n */\n public isReady(): boolean {\n return this._isReady && this.fastifyServer.server?.listening === true;\n }\n\n /**\n * Log web server message\n */\n public log(message: string, meta?: Record<string, unknown>): void {\n this.logger.custom({ level: 'webServer', message, meta });\n }\n}\n\nexport default WebServer;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../../../../src/websocket/controllers/server/system.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,6BAA6B,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../../../../src/websocket/controllers/server/system.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,6BAA6B,MAAM,iCAAiC,CAAC;AAG5E,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,6BAA6B;IAClE,QAAQ,GAAI,iBAAiB,SAAS,EAAE,mBAAmB,MAAM,EAAE,MAAM,GAAG,KAAG,GAAG,CA6CvF;IAEK,SAAS,GAAI,iBAAiB,SAAS,EAAE,mBAAmB,MAAM,EAAE,MAAM,GAAG,KAAG,GAAG,CAqCxF;CACH"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/websocket/controllers/server/system.ts"],
|
|
4
|
-
"sourcesContent": ["import type { WebSocket } from 'ws';\nimport WebSocketServerBaseController from '../../controller/server/base.js';\nimport
|
|
5
|
-
"mappings": ";;AACA,OAAO,mCAAmC;
|
|
4
|
+
"sourcesContent": ["import type { WebSocket } from 'ws';\nimport WebSocketServerBaseController from '../../controller/server/base.js';\nimport { Logger } from '../../../logger/index.js';\n\nexport default class SystemController extends WebSocketServerBaseController {\n public joinRoom = (clientWebSocket: WebSocket, webSocketClientId: string, data: any): any => {\n const userId = data.userId ?? webSocketClientId;\n const userType = data.userType ?? 'user';\n const username = data.username ?? `user_${webSocketClientId.substring(0, 8)}`;\n const roomName = data.roomName;\n\n if (!roomName) {\n return {\n success: false,\n error: 'Room name is required',\n clientId: webSocketClientId,\n };\n }\n\n try {\n // Join room\n this.webSocketServer.joinRoom({\n ws: clientWebSocket,\n userId,\n userType,\n username,\n roomName,\n });\n\n return {\n success: true,\n message: `Successfully joined room: ${roomName}`,\n data: {\n userId,\n userType,\n username,\n roomName,\n joinedAt: new Date().toISOString(),\n },\n };\n } catch (error) {\n Logger.error({ error, message: 'Failed to join room via system controller' });\n\n return {\n success: false,\n error: 'Failed to join room',\n message: error instanceof Error ? error.message : 'Unknown error',\n clientId: webSocketClientId,\n };\n }\n };\n\n public leaveRoom = (clientWebSocket: WebSocket, webSocketClientId: string, data: any): any => {\n const roomName = data.roomName;\n\n if (!roomName) {\n return {\n success: false,\n error: 'Room name is required',\n clientId: webSocketClientId,\n };\n }\n\n try {\n // Leave room\n this.webSocketServer.leaveRoom({\n ws: clientWebSocket,\n roomName,\n });\n\n return {\n success: true,\n message: `Successfully left room: ${roomName}`,\n data: {\n roomName,\n leftAt: new Date().toISOString(),\n },\n clientId: webSocketClientId,\n };\n } catch (error) {\n Logger.error({ error, message: 'Failed to leave room via system controller' });\n\n return {\n success: false,\n error: 'Failed to leave room',\n message: error instanceof Error ? error.message : 'Unknown error',\n clientId: webSocketClientId,\n };\n }\n };\n}\n"],
|
|
5
|
+
"mappings": ";;AACA,OAAO,mCAAmC;AAC1C,SAAS,cAAc;AAEvB,MAAO,yBAAuC,8BAA8B;AAAA,EAJ5E,OAI4E;AAAA;AAAA;AAAA,EACnE,WAAW,wBAAC,iBAA4B,mBAA2B,SAAmB;AAC3F,UAAM,SAAS,KAAK,UAAU;AAC9B,UAAM,WAAW,KAAK,YAAY;AAClC,UAAM,WAAW,KAAK,YAAY,QAAQ,kBAAkB,UAAU,GAAG,CAAC,CAAC;AAC3E,UAAM,WAAW,KAAK;AAEtB,QAAI,CAAC,UAAU;AACb,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,QACP,UAAU;AAAA,MACZ;AAAA,IACF;AAEA,QAAI;AAEF,WAAK,gBAAgB,SAAS;AAAA,QAC5B,IAAI;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,aAAO;AAAA,QACL,SAAS;AAAA,QACT,SAAS,6BAA6B,QAAQ;AAAA,QAC9C,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,WAAU,oBAAI,KAAK,GAAE,YAAY;AAAA,QACnC;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,aAAO,MAAM,EAAE,OAAO,SAAS,4CAA4C,CAAC;AAE5E,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,QACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU;AAAA,QAClD,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF,GA7CkB;AAAA,EA+CX,YAAY,wBAAC,iBAA4B,mBAA2B,SAAmB;AAC5F,UAAM,WAAW,KAAK;AAEtB,QAAI,CAAC,UAAU;AACb,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,QACP,UAAU;AAAA,MACZ;AAAA,IACF;AAEA,QAAI;AAEF,WAAK,gBAAgB,UAAU;AAAA,QAC7B,IAAI;AAAA,QACJ;AAAA,MACF,CAAC;AAED,aAAO;AAAA,QACL,SAAS;AAAA,QACT,SAAS,2BAA2B,QAAQ;AAAA,QAC5C,MAAM;AAAA,UACJ;AAAA,UACA,SAAQ,oBAAI,KAAK,GAAE,YAAY;AAAA,QACjC;AAAA,QACA,UAAU;AAAA,MACZ;AAAA,IACF,SAAS,OAAO;AACd,aAAO,MAAM,EAAE,OAAO,SAAS,6CAA6C,CAAC;AAE7E,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,QACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU;AAAA,QAClD,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF,GArCmB;AAsCrB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-base.d.ts","sourceRoot":"","sources":["../../src/websocket/websocket-base.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"websocket-base.d.ts","sourceRoot":"","sources":["../../src/websocket/websocket-base.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAIvG,OAAO,KAAK,SAAS,MAAM,IAAI,CAAC;AAGhC,MAAM,CAAC,OAAO,CAAC,QAAQ,OAAO,aAAa;IACzC,SAAS,CAAC,MAAM,EAAE,cAAc,EAAE,CAAM;IACxC,SAAS,CAAC,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAa;IAE1E,SAAS,CAAC,aAAa,EAAE,cAAc,EAAE,CAAM;IAE/C,aAAoB,IAAI,IAAI,aAAa,CAAC;IAE1C,SAAS,CAAC,QAAQ,CAAC,yBAAyB,IAAI,GAAG;IACnD,SAAS,CAAC,QAAQ,CAAC,iBAAiB,IAAI,OAAO;IAC/C,SAAS,CAAC,QAAQ,CAAC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;cAE5D,eAAe,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,oBAAoB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;cAsEtF,mBAAmB,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,GAAG,CAAC;IAuDlH,SAAS,CAAC,WAAW,IAAI,IAAI;CAiB9B"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
-
import { existsSync } from "fs";
|
|
4
3
|
import { getRouteKey, log, parseServerMessage } from "./utils.js";
|
|
5
|
-
import { Helper, Loader } from "../util/index.js";
|
|
4
|
+
import { File, Helper, Loader } from "../util/index.js";
|
|
6
5
|
class WebSocketBase {
|
|
7
6
|
static {
|
|
8
7
|
__name(this, "WebSocketBase");
|
|
@@ -11,7 +10,7 @@ class WebSocketBase {
|
|
|
11
10
|
routeHandlers = /* @__PURE__ */ new Map();
|
|
12
11
|
defaultRoutes = [];
|
|
13
12
|
async configureRoutes(routes, controllersDirectory) {
|
|
14
|
-
const controllersDirectoryExists = await
|
|
13
|
+
const controllersDirectoryExists = await File.pathExists(controllersDirectory);
|
|
15
14
|
if (!controllersDirectoryExists) {
|
|
16
15
|
log("Controllers directory not found", {
|
|
17
16
|
Directory: controllersDirectory
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/websocket/websocket-base.ts"],
|
|
4
|
-
"sourcesContent": ["import
|
|
5
|
-
"mappings": ";;
|
|
4
|
+
"sourcesContent": ["import type { WebSocketMessageHandler, WebSocketRoute, WebSocketType } from './websocket.interface.js';\nimport { getRouteKey, log, parseServerMessage } from './utils.js';\nimport type { WebSocketServerBaseControllerType } from './controller/server/base.interface.js';\nimport type { WebSocketClientBaseControllerType } from './controller/client/base.interface.js';\nimport type WebSocket from 'ws';\nimport { File, Helper, Loader } from '../util/index.js';\n\nexport default abstract class WebSocketBase {\n protected routes: WebSocketRoute[] = [];\n protected routeHandlers: Map<string, WebSocketMessageHandler> = new Map();\n\n protected defaultRoutes: WebSocketRoute[] = [];\n\n public abstract get type(): WebSocketType;\n\n protected abstract getControllerDependencies(): any;\n protected abstract shouldPrintRoutes(): boolean;\n protected abstract handleMessageError(clientId: string, error: string): void;\n\n protected async configureRoutes(routes: WebSocketRoute[], controllersDirectory: string): Promise<void> {\n // log ('Configuring routes', { Type: this.type, 'Controllers Directory': controllersDirectory });\n\n const controllersDirectoryExists = await File.pathExists(controllersDirectory);\n\n if (!controllersDirectoryExists) {\n log('Controllers directory not found', {\n Directory: controllersDirectory,\n });\n\n return;\n }\n\n const scriptFileExtension = Helper.getScriptFileExtension();\n\n // Load controllers\n const controllers = await Loader.loadModulesInDirectory({\n directory: controllersDirectory,\n // NOTE:\n // When getting \"system\", it gets /app/node_modules/@scpxl/nodejs-framework/dist/websocket/controllers/server\n // Therefor .js is needed also\n // Fix so only .ts vs .js is needed\n extensions: ['.ts', '.js'],\n });\n\n for (const route of routes) {\n let ControllerClass: WebSocketServerBaseControllerType | WebSocketClientBaseControllerType;\n\n // log('Registering route', {\n // Type: route.type,\n // Controller: route.controller ? route.controller.toString() : route.controllerName,\n // Action: route.action,\n // });\n\n if (route.controller) {\n ControllerClass = route.controller;\n } else if (route.controllerName) {\n ControllerClass = controllers[route.controllerName];\n } else {\n throw new Error('WebSocket controller config not found');\n }\n\n if (typeof ControllerClass !== 'function') {\n log('Controller not found', {\n Controller: route.controllerName,\n Path: `${controllersDirectory}/${route.controllerName}.${scriptFileExtension}`,\n });\n\n continue;\n }\n\n const controllerDependencies = this.getControllerDependencies();\n\n const controllerInstance = new ControllerClass(controllerDependencies);\n\n const controllerHandler = controllerInstance[\n route.action as keyof typeof controllerInstance\n ] as WebSocketMessageHandler;\n const routeKey = getRouteKey(route.type, route.action);\n\n this.routeHandlers.set(routeKey, controllerHandler);\n }\n\n if (this.shouldPrintRoutes()) {\n log('Routes:', { Type: this.type });\n\n this.printRoutes();\n }\n }\n\n protected async handleServerMessage(ws: WebSocket, message: WebSocket.Data, clientId: string): Promise<void | any> {\n try {\n const parsedMessage = parseServerMessage(message);\n const type = parsedMessage.type;\n const action = parsedMessage.action;\n\n log('Incoming message', {\n 'Client ID': clientId,\n Type: type ?? '-',\n Action: action ?? '-',\n });\n\n const routeKey = getRouteKey(parsedMessage.type as string, parsedMessage.action as string);\n\n const messageHandler = this.routeHandlers.get(routeKey);\n\n if (messageHandler) {\n const messageResponse = await messageHandler(ws, clientId, parsedMessage.data);\n\n return {\n type,\n action,\n response: messageResponse,\n };\n }\n // throw new Error(`Route handler not found (Route Key: ${routeKey} | Type: ${type} | Action: ${action})`);\n\n log('Route handler not found', {\n RouteKey: routeKey,\n Type: type,\n Action: action,\n });\n\n // if (\n // typeof this.applicationConfig.webSocket\n // ?.serverMessageHandler === 'function'\n // ) {\n // // Execute custom application subscriber event handler\n // this.applicationConfig.webSocket.serverMessageHandler(\n // {\n // ws,\n // clientId,\n // parsedMessage,\n // },\n // );\n // }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n\n log(errorMessage);\n\n this.handleMessageError(clientId, errorMessage);\n }\n }\n\n protected printRoutes(): void {\n let routesString = '';\n\n const routeKeys = Array.from(this.routeHandlers.keys());\n\n routeKeys.forEach((routeKey, index) => {\n const [type, action] = routeKey.split(':');\n\n routesString += `Type: ${type} -> Action: ${action}`;\n\n if (index !== routeKeys.length - 1) {\n routesString += '\\n';\n }\n });\n\n log(routesString);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -10,7 +10,7 @@ import type { FastifyInstance } from 'fastify';
|
|
|
10
10
|
export default class WebSocketServer extends WebSocketBase {
|
|
11
11
|
protected defaultRoutes: WebSocketRoute[];
|
|
12
12
|
private server?;
|
|
13
|
-
private
|
|
13
|
+
private abortController;
|
|
14
14
|
private workerId;
|
|
15
15
|
private uniqueInstanceId;
|
|
16
16
|
private applicationConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-server.d.ts","sourceRoot":"","sources":["../../src/websocket/websocket-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,eAAe,IAAI,EAAE,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpE,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,aAAa,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,YAAY,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,gBAAgB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,sBAAsB,MAAM,+BAA+B,CAAC;AAEnE,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAMhD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI/C,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,aAAa;IACxD,SAAS,CAAC,aAAa,EAAE,cAAc,EAAE,CAWvC;IAEF,OAAO,CAAC,MAAM,CAAC,CAAK;IAEpB,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"websocket-server.d.ts","sourceRoot":"","sources":["../../src/websocket/websocket-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -31,7 +31,7 @@ class WebSocketServer extends WebSocketBase {
|
|
|
31
31
|
}
|
|
32
32
|
];
|
|
33
33
|
server;
|
|
34
|
-
|
|
34
|
+
abortController = new AbortController();
|
|
35
35
|
workerId;
|
|
36
36
|
uniqueInstanceId;
|
|
37
37
|
applicationConfig;
|
|
@@ -138,10 +138,7 @@ class WebSocketServer extends WebSocketBase {
|
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
140
|
async stop() {
|
|
141
|
-
|
|
142
|
-
clearInterval(this.checkConnectedClientsInterval);
|
|
143
|
-
this.checkConnectedClientsInterval = void 0;
|
|
144
|
-
}
|
|
141
|
+
this.abortController.abort();
|
|
145
142
|
this.redisInstance.subscriberClient?.removeListener("message", this.handleSubscriberMessage);
|
|
146
143
|
this.redisSubscriberEvents.forEach((subscriberEventName) => {
|
|
147
144
|
this.redisInstance.subscriberClient?.unsubscribe(subscriberEventName);
|
|
@@ -160,6 +157,7 @@ class WebSocketServer extends WebSocketBase {
|
|
|
160
157
|
this.roomManager = new WebSocketRoomManager({
|
|
161
158
|
clientManager: this.clientManager
|
|
162
159
|
});
|
|
160
|
+
this.abortController = new AbortController();
|
|
163
161
|
log("Server stopped");
|
|
164
162
|
}
|
|
165
163
|
getControllerDependencies() {
|
|
@@ -177,10 +175,11 @@ class WebSocketServer extends WebSocketBase {
|
|
|
177
175
|
if (!this.server) {
|
|
178
176
|
throw new Error("WebSocket server not started");
|
|
179
177
|
}
|
|
180
|
-
if (this.options.disconnectInactiveClients?.enabled) {
|
|
181
|
-
|
|
178
|
+
if (this.options.disconnectInactiveClients?.enabled && this.options.disconnectInactiveClients.intervalCheckTime) {
|
|
179
|
+
setInterval(
|
|
182
180
|
() => this.checkInactiveClients(),
|
|
183
|
-
this.options.disconnectInactiveClients.intervalCheckTime
|
|
181
|
+
this.options.disconnectInactiveClients.intervalCheckTime,
|
|
182
|
+
{ signal: this.abortController.signal }
|
|
184
183
|
);
|
|
185
184
|
}
|
|
186
185
|
this.redisSubscriberEvents.forEach((subscriberEventName) => {
|
|
@@ -467,29 +466,6 @@ class WebSocketServer extends WebSocketBase {
|
|
|
467
466
|
log("Checking inactive clients...");
|
|
468
467
|
}
|
|
469
468
|
}
|
|
470
|
-
// private disconnectClient({
|
|
471
|
-
// clientId,
|
|
472
|
-
// }: {
|
|
473
|
-
// clientId: string;
|
|
474
|
-
// }) {
|
|
475
|
-
// const clientInfo = this.connectedClients.get(clientId);
|
|
476
|
-
// if (clientInfo?.ws) {
|
|
477
|
-
// const connectedTime =
|
|
478
|
-
// Date.now() - clientInfo.lastActivity;
|
|
479
|
-
// clientInfo.ws.close();
|
|
480
|
-
// Logger.info(
|
|
481
|
-
// 'WebSocket client was disconnected due to inactivity',
|
|
482
|
-
// {
|
|
483
|
-
// ID: clientId,
|
|
484
|
-
// Worker: this.workerId,
|
|
485
|
-
// 'Time Connected': Time.formatTime({
|
|
486
|
-
// time: connectedTime,
|
|
487
|
-
// format: 's',
|
|
488
|
-
// }),
|
|
489
|
-
// },
|
|
490
|
-
// );
|
|
491
|
-
// }
|
|
492
|
-
// }
|
|
493
469
|
broadcastToAllClients({
|
|
494
470
|
data,
|
|
495
471
|
excludeClientId
|