@nsshunt/stsappframework 3.0.94 → 3.0.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/package.json +1 -1
  2. package/src_new/authDefs.ts +37 -0
  3. package/src_new/authutilsnode.ts +375 -0
  4. package/src_new/commonTypes.ts +239 -0
  5. package/src_new/index.ts +22 -0
  6. package/src_new/influxdb/influxDBManager.ts +972 -0
  7. package/src_new/influxdb/influxDBManagerAgent.ts +316 -0
  8. package/src_new/influxdb/influxDBManagerBase.ts +111 -0
  9. package/src_new/influxdb/influxDBManagerService.ts +375 -0
  10. package/src_new/instrumentationsubscriber.ts +286 -0
  11. package/src_new/kafka/IMKafkaManager.ts +154 -0
  12. package/src_new/kafka/kafkaconsumer.ts +82 -0
  13. package/src_new/kafka/kafkamanager.ts +186 -0
  14. package/src_new/kafka/kafkaproducer.ts +58 -0
  15. package/src_new/kafkatesting/config.ts +10 -0
  16. package/src_new/kafkatesting/consume.ts +116 -0
  17. package/src_new/kafkatesting/produce.ts +155 -0
  18. package/src_new/masterprocessbase.ts +590 -0
  19. package/src_new/middleware/serverNetworkMiddleware.ts +240 -0
  20. package/src_new/network.ts +36 -0
  21. package/src_new/processbase.ts +413 -0
  22. package/src_new/processoptions.ts +164 -0
  23. package/src_new/publishertransports/publishTransportDirect.ts +45 -0
  24. package/src_new/publishertransports/publishTransportUtils.ts +53 -0
  25. package/src_new/server.ts +141 -0
  26. package/src_new/serverprocessbase.ts +393 -0
  27. package/src_new/singleprocessbase.ts +123 -0
  28. package/src_new/socketIoServerHelper.ts +177 -0
  29. package/src_new/stscontrollerbase.ts +15 -0
  30. package/src_new/stslatencycontroller.ts +27 -0
  31. package/src_new/stslatencyroute.ts +16 -0
  32. package/src_new/stsrouterbase.ts +22 -0
  33. package/src_new/tcpclient/app.ts +19 -0
  34. package/src_new/tcpclient/app2.ts +56 -0
  35. package/src_new/tcpserver/app.ts +11 -0
  36. package/src_new/tcpserver/appConfig.ts +65 -0
  37. package/src_new/tcpserver/appmaster.ts +522 -0
  38. package/src_new/validation/errors.ts +6 -0
  39. package/src_new/webworkertesting/app.ts +49 -0
  40. package/src_new/webworkertesting/worker.ts +24 -0
  41. package/src_new/workerprocessbase.test.ts +47 -0
  42. package/src_new/workerprocessbase.ts +187 -0
@@ -0,0 +1,393 @@
1
+ /* eslint @typescript-eslint/no-explicit-any: 0, @typescript-eslint/no-unused-vars: 0 */ // --> OFF
2
+ import debugModule from 'debug'
3
+ const debug = debugModule(`proc:${process.pid}`);
4
+
5
+ import { $Options } from '@nsshunt/stsconfig'
6
+ const goptions = $Options()
7
+
8
+ import fs from "fs"
9
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
10
+ import colors from 'colors'
11
+
12
+ import { createAdapter as clusterCreateAdapter } from '@socket.io/cluster-adapter'
13
+ import { createAdapter } from "@socket.io/redis-streams-adapter";
14
+
15
+ import { JSONObject, Sleep } from '@nsshunt/stsutils'
16
+
17
+ import { ProcessOptions, STSServerType } from './processoptions'
18
+ import { ProcessBase } from './processbase';
19
+
20
+ import { register, Counter, collectDefaultMetrics, AggregatorRegistry } from 'prom-client'
21
+
22
+ import { createServer as createServerHttps } from 'https'
23
+ import { createServer } from 'http'
24
+ import tls from 'node:tls'
25
+ import net from 'node:net'
26
+
27
+ import { Server, ServerOptions } from "socket.io";
28
+ import { STSExpressServer } from './server';
29
+ import { Express } from 'express'
30
+
31
+ import { createClient, RedisClientType } from 'redis';
32
+
33
+ import jayson from 'jayson'
34
+
35
+ /**
36
+ * todo
37
+ * @typedef {Object} options - todo
38
+ * @property {boolean} [wssServer=false] - Create a web socket server on this worker instance
39
+ */
40
+ export class ServerProcessBase extends ProcessBase
41
+ {
42
+ #masterProcessExitTime = goptions.masterProcessExitTime;
43
+ #io: Server | null = null;
44
+ #redisClient: RedisClientType | null = null;
45
+ #httpServer: any = null;
46
+ #expressServer: STSExpressServer | null = null;
47
+ #sockets: net.Socket[] = [ ];
48
+ #shuttingDown = false;
49
+
50
+ constructor(options: ProcessOptions) {
51
+ super(options);
52
+ }
53
+
54
+ get httpServer() {
55
+ return this.#httpServer;
56
+ }
57
+
58
+ get io() {
59
+ return this.#io;
60
+ }
61
+
62
+ get expressServer(): STSExpressServer | null {
63
+ return this.#expressServer;
64
+ }
65
+ set expressServer(val: STSExpressServer | null) {
66
+ this.#expressServer = val;
67
+ }
68
+
69
+ // Setup server to Prometheus scrapes:
70
+ #SetupPrometheusEndPoints = (expressServer: Express) => {
71
+ // AggregatorRegistry is required here in the worker as well as the master in order for prom-client to work correctly.
72
+ new AggregatorRegistry();
73
+
74
+ const prefix = 'sts_';
75
+
76
+ collectDefaultMetrics({
77
+ labels: { NODE_APP_INSTANCE: process.pid },
78
+ prefix: prefix
79
+ });
80
+
81
+ const c = new Counter({
82
+ name: 'sts_test_counter',
83
+ help: 'Example of a counter',
84
+ labelNames: ['code'],
85
+ });
86
+
87
+ setInterval(() => {
88
+ c.inc({ code: 200 });
89
+ }, 1000).unref();
90
+
91
+ setInterval(() => {
92
+ c.inc({ code: 400 });
93
+ c.inc({ code: 'worker_' + process.pid });
94
+ }, 500).unref();
95
+
96
+ expressServer.get('/metrics', async (req: any, res: any) => {
97
+ try {
98
+ res.set('Content-Type', register.contentType);
99
+ res.end(await register.metrics());
100
+ } catch (ex) {
101
+ res.status(500).end(ex);
102
+ }
103
+ });
104
+
105
+ expressServer.get('/metrics/counter', async (req: any, res: any) => {
106
+ try {
107
+ res.set('Content-Type', register.contentType);
108
+ res.end(await register.getSingleMetricAsString('test_counter'));
109
+ } catch (ex) {
110
+ res.status(500).end(ex);
111
+ }
112
+ });
113
+ }
114
+
115
+ #SetupTLSServer = async (socket: net.Socket): Promise<void> => {
116
+ // Add a 'close' event handler to this instance of socket
117
+ console.log('CONNECTED: ' + socket.remoteAddress + ':' + socket.remotePort + ' ' + process.pid);
118
+ this.#sockets.push(socket);
119
+
120
+ //const self = this;
121
+ socket.on('close', (data: any) => {
122
+ const index = this.#sockets.findIndex(function(o) {
123
+ return o.remoteAddress === socket.remoteAddress && o.remotePort === socket.remotePort;
124
+ })
125
+ if (index !== -1) this.#sockets.splice(index, 1);
126
+ console.log('CLOSED: ' + socket.remoteAddress + ' ' + socket.remotePort + ' ' + process.pid);
127
+ });
128
+
129
+ socket.on('data', function(data: any) {
130
+ console.log('DATA ' + socket.remoteAddress + ': ' + socket.remotePort + ': ' + data);
131
+ socket.write(socket.remoteAddress + ':' + socket.remotePort + " said " + data + '\n');
132
+
133
+ // Write the data back to all the connected, the client will receive it as data from the server
134
+ /*
135
+ self.#sockets.forEach(function(socket, index, array) {
136
+ socket.write(socket.remoteAddress + ':' + socket.remotePort + " said " + data + '\n');
137
+ });
138
+
139
+ */
140
+ });
141
+ }
142
+
143
+ #SetupRPCServer = async (socket: net.Socket): Promise<void> => {
144
+ console.log('CONNECTED: ' + socket.remoteAddress + ':' + socket.remotePort + ' ' + process.pid);
145
+
146
+ socket.on('close', function(data: any) {
147
+ console.log('CLOSED: ' + socket.remoteAddress + ' ' + socket.remotePort + ' ' + process.pid);
148
+ });
149
+
150
+ socket.on('data', function(data: any) {
151
+ console.log('DATA ' + socket.remoteAddress + ': ' + data);
152
+ });
153
+ }
154
+
155
+ #SetupWSSServer = async (): Promise<void> => {
156
+ // socket.io
157
+ // WebSocket
158
+ const options: Partial<ServerOptions> = {
159
+ transports: [ "websocket" ] // or [ "websocket", "polling" ] (to use long-poolling. Note that the order matters)
160
+ // The default path is /socket.io
161
+ // This can be changed with the path option as shown below
162
+ //,path: '/zzz'
163
+ };
164
+
165
+ //this.#io = require("socket.io")(this.#httpServer, options);
166
+ this.#io = new Server(this.#httpServer, options);
167
+
168
+ if (this.options.clusterMode) {
169
+ if (this.options.useRedisAdaptor) {
170
+ this.LogEx(`Using Redis for socket.io cluster management (worker)`);
171
+ if (this.options.redisAdaptorUrl) {
172
+ this.LogEx(`Redis url: [${this.options.redisAdaptorUrl}]`);
173
+ this.#redisClient = createClient({url: this.options.redisAdaptorUrl});
174
+ } else {
175
+ this.LogEx(`Redis url: [localhost]`);
176
+ this.#redisClient = createClient();
177
+ }
178
+ await this.#redisClient.connect();
179
+ this.#io.adapter(createAdapter(this.#redisClient) as any);
180
+ this.LogEx(`Redis successfully connected.`);
181
+ } else {
182
+ this.#io.adapter(clusterCreateAdapter() as any);
183
+ this.LogEx(`Using nodejs cluster mode for socket.io cluster management`);
184
+ }
185
+ } else {
186
+ this.LogEx(`Not using any adaptors for socket.io cluster management.}`);
187
+ }
188
+
189
+ // To use a seperate socket server, the code below can be applied.
190
+ // this.#io = require("socket.io")(options);
191
+ // this.#io.adapter(createAdapter());
192
+ // this.#io.listen(3006);
193
+ // LogEx(`socket.io init`);
194
+
195
+ this.#io.engine.on("connection_error", (err) => {
196
+ this.LogEx(err.req); // the request object
197
+ this.LogEx(err.code); // the error code, for example 1
198
+ this.LogEx(err.message); // the error message, for example "Session ID unknown"
199
+ this.LogEx(err.context); // some additional error context
200
+ });
201
+ }
202
+
203
+ #GetTLSOptions = (): JSONObject => {
204
+ return {
205
+ key: fs.readFileSync(this.options.httpsServerKeyPath),
206
+ cert: fs.readFileSync(this.options.httpsServerCertificatePath)
207
+ };
208
+ }
209
+
210
+ #SetupExpressServer = async (useTls: boolean): Promise<void> => {
211
+ if (useTls) {
212
+ this.#httpServer = createServerHttps(this.#GetTLSOptions(), (this.#expressServer as STSExpressServer).App);
213
+ } else {
214
+ this.#httpServer = createServer((this.#expressServer as STSExpressServer).App);
215
+ }
216
+ if (this.options.prometheusSupport === true) {
217
+ this.#SetupPrometheusEndPoints((this.#expressServer as STSExpressServer).App);
218
+ }
219
+ if (this.options.wssServer === true) {
220
+ await this.#SetupWSSServer();
221
+ }
222
+ // https://stackoverflow.com/questions/21342828/node-express-unix-domain-socket-permissions
223
+ //@@httpServer.listen('/tmp/stsrest01.sock').on('listening', () =>
224
+ //@@httpServer.listen('/var/run/sts/stsrest01.sock').on('listening', () =>
225
+ this.#httpServer.listen(this.options.listenPort, () => {
226
+ //@@chmodSync(this.options.port, 511);
227
+ }).on('listening', () =>
228
+ {
229
+ this.LogEx(`live on ${this.options.endpoint}:${this.options.listenPort}${this.options.apiRoot}`);
230
+ });
231
+ }
232
+
233
+ #SetupTCPRawServer = async (): Promise<void> => {
234
+ // The second parameter is the automatic listener for the secureConnection event from the tls.Server class
235
+ this.#httpServer = tls.createServer(this.#GetTLSOptions(), this.#SetupTLSServer);
236
+ this.#httpServer.listen(this.options.listenPort, 'stscore.stsmda.org', () => {
237
+ console.log('TCP Server is running on port ' + this.options.listenPort + '.');
238
+ }).on('listening', () =>
239
+ {
240
+ this.LogEx(`TCP live on ${this.options.endpoint}:${this.options.listenPort}${this.options.apiRoot}`);
241
+ });
242
+ }
243
+
244
+ #SetupJSONRPCServer = async (): Promise<void> => {
245
+ const jaysonServer = new jayson.server();
246
+ // Supported methods here - move somewhere else ...
247
+ jaysonServer.method("add", function(args: any, callback: any) {
248
+ callback(null, args[0] + args[1]);
249
+ });
250
+ this.#httpServer = jaysonServer.tls(this.#GetTLSOptions());
251
+ (this.#httpServer as tls.Server).on('secureConnection', this.#SetupRPCServer);
252
+ this.#httpServer.listen(this.options.listenPort, 'stscore.stsmda.org', () => {
253
+ console.log('JSON RPC 2.0 Server is running on port ' + this.options.listenPort + '.');
254
+ }).on('listening', () =>
255
+ {
256
+ this.LogEx(`JSON RPC 2.0 live on ${this.options.endpoint}:${this.options.listenPort}${this.options.apiRoot}`);
257
+ });
258
+ }
259
+
260
+ ProcessTerminating = async (): Promise<void> => {
261
+ return;
262
+ }
263
+
264
+ override get shuttingDown(): boolean {
265
+ return this.#shuttingDown;
266
+ }
267
+
268
+ // Terminate in order;
269
+ // forked worker threads (send signal)
270
+ // De-Register service
271
+ // systeminformation observers
272
+ // instrument timers (gauge etc.)
273
+ // publisher
274
+ // terminate UI (if loaded)
275
+ Terminate = async (clusterPerformExit: boolean, signal?: any): Promise<void> => {
276
+ if (this.#shuttingDown === false) {
277
+ this.#shuttingDown = true;
278
+
279
+ await this.ProcessTerminate();
280
+
281
+ await this.ProcessTerminating();
282
+
283
+ if (!this.options.clusterMode) {
284
+ if (this.GetUIController() !== null)
285
+ {
286
+ this.LogEx('Destroy the user interface controller.');
287
+ this.GetUIController().DestroyUI();
288
+ }
289
+
290
+ if (signal) {
291
+ this.LogEx(this.GetSignalColour(signal)(`Main Process (singleprocess): ${process.pid} received signal: ${signal}`));
292
+ } else {
293
+ this.LogEx(this.GetSignalColour(null)(`Main Process (singleprocess): ${process.pid} received Terminate without signal.`));
294
+ }
295
+ }
296
+
297
+ if (this.options.wssServer === true && this.#io !== null)
298
+ {
299
+ this.LogEx(`Disconnect Sockets.`);
300
+ if (this.socketIoServerHelper !== null) {
301
+ this.socketIoServerHelper.DisconnectSockets();
302
+ } else {
303
+ this.#io.disconnectSockets();
304
+ }
305
+ this.socketIoServerHelper = null;
306
+ this.#io = null;
307
+ // Note that this.#redisClient.disconnect() is not required becuase DisconnectSockets performs this action.
308
+ }
309
+
310
+ if (this.#httpServer) {
311
+ if (this.options.serverType === STSServerType.TCPRAW_TLS) {
312
+ this.#sockets.forEach((socket: net.Socket, index, array) => {
313
+ this.LogEx(`TCP Socket destroy, remote address: [${socket.remoteAddress}], remote port: [${socket.remotePort}]`.yellow);
314
+ socket.destroy();
315
+ //socket.end();
316
+ });
317
+ }
318
+ this.LogEx(`Closing httpServer.`);
319
+ await this.#httpServer.close();
320
+ }
321
+
322
+ if (this.options.useDatabase) {
323
+ this.LogEx(`Ending database connections and pools.`);
324
+ await this.TerminateDatabase();
325
+ //await this.accessLayer.enddatabase();
326
+ }
327
+
328
+ if (this.options.clusterMode) {
329
+ this.LogEx(`Performing exit value: [${clusterPerformExit}]`);
330
+ if (clusterPerformExit) {
331
+ this.LogEx(`Process will self terminate with process.exit(0).`);
332
+ } else {
333
+ this.LogEx(`Child process will not self terminate. Terminate will be handled by master process.`);
334
+ }
335
+ }
336
+
337
+ //if (this.InstrumentController && this.InstrumentController.Workers.length > 0) {
338
+ if (this.InstrumentController) {
339
+ this.LogEx(`Ending publisher.`);
340
+ setTimeout(() => {
341
+ //if (this.InstrumentController && this.InstrumentController.Workers.length > 0) {
342
+ if (this.InstrumentController) {
343
+ this.InstrumentController.InstrumentTerminate();
344
+ }
345
+ }, 100);
346
+ }
347
+
348
+ //@@ always return here appears to always cleanly exit
349
+ // and cleanly exit from socket.io cluster adaptor
350
+ // without return here, socket.io cluster adaptor terminates in an error state
351
+ // as the implementation relies on cluster.on to send messages to worker threads
352
+ // but these have already been closed from the process.exit(0) below.
353
+
354
+ await Sleep(1000); // Allow socket.io time to clean-up
355
+
356
+ if (this.options.clusterMode) {
357
+ if (clusterPerformExit) {
358
+ setTimeout(() => {
359
+ process.exit(0);
360
+ }, 0);
361
+ }
362
+ } else {
363
+ if (this.options.processExitOnTerminate && this.options.processExitOnTerminate === true) {
364
+ setTimeout(() => {
365
+ this.LogEx(`Performing process.exit(0).`);
366
+ process.exit(0);
367
+ }, this.#masterProcessExitTime); // Give the workers time to terminate gracefully
368
+ } else {
369
+ this.LogEx(`Performing process.exit(0) - Immediate.`);
370
+ }
371
+ }
372
+ } else {
373
+ this.LogEx(`Process already terminating.`);
374
+ }
375
+ }
376
+
377
+ SetupSTSServer = async(): Promise<void> => {
378
+ switch (this.options.serverType) {
379
+ case STSServerType.EXPRESS :
380
+ await this.#SetupExpressServer(false);
381
+ break;
382
+ case STSServerType.EXPRESS_TLS :
383
+ await this.#SetupExpressServer(true);
384
+ break;
385
+ case STSServerType.TCPRAW_TLS :
386
+ await this.#SetupTCPRawServer();
387
+ break;
388
+ case STSServerType.JSONRPC2_TLS :
389
+ await this.#SetupJSONRPCServer();
390
+ break;
391
+ }
392
+ }
393
+ }
@@ -0,0 +1,123 @@
1
+ /* eslint @typescript-eslint/no-explicit-any: 0 */ // --> OFF
2
+ import { $Options } from '@nsshunt/stsconfig'
3
+ const goptions = $Options()
4
+
5
+ import chalk from 'chalk';
6
+
7
+ import { Gauge, GaugeTypes, InstrumentGaugeOptions, InstrumentGaugeTelemetry } from '@nsshunt/stsinstrumentation'
8
+
9
+ import { ProcessOptions } from './processoptions'
10
+ import { ISingleProcessBase } from './commonTypes';
11
+ import { InstrumentDefinitions } from '@nsshunt/stspublisherserver'
12
+ import { STSExpressServer } from './server'
13
+ import { ServerProcessBase } from './serverprocessbase'
14
+
15
+ import si from 'systeminformation' // https://systeminformation.io/
16
+
17
+ export type EventCb = (socket: any, data: any) => void
18
+
19
+ export class SingleProcessBase extends ServerProcessBase implements ISingleProcessBase
20
+ {
21
+ /**
22
+ *
23
+ * @param {SingleProcessBaseOptions} options
24
+ */
25
+ constructor(options: ProcessOptions) {
26
+ super(options)
27
+ }
28
+
29
+ override CollectAdditionalTelemetry(): void {
30
+ const siValueObject = {
31
+ currentLoad: 'currentLoad'
32
+ }
33
+ si.get(siValueObject).then(data => {
34
+ this.UpdateInstrument(Gauge.CPU_SYSTEM_LOAD_GAUGE, {
35
+ val: data.currentLoad.currentLoad
36
+ } as InstrumentGaugeTelemetry);
37
+ });
38
+ }
39
+
40
+ override GetAdditionalInstruments(): InstrumentDefinitions {
41
+ return [
42
+ [ Gauge.CPU_SYSTEM_LOAD_GAUGE, GaugeTypes.INSTRUMENT_GAUGE, {
43
+ interval: goptions.instrumentationObservationInterval,
44
+ sampleSize: goptions.instrumentationTimeWindow
45
+ } as InstrumentGaugeOptions]
46
+ ]
47
+ }
48
+
49
+ /**
50
+ * UIController (instance of UIController) to manage a console based user interface associated for this node application.
51
+ * @returns UIController instance to manage a console based user interface associated for this node application. Null for no capability.
52
+ */
53
+ override GetUIController(): any {
54
+ return null;
55
+ }
56
+
57
+ ProcessStarted() {
58
+ return null;
59
+ }
60
+
61
+ SetupServer(): Promise<boolean>
62
+ {
63
+ return new Promise((resolve, reject) => {
64
+ try {
65
+ this.SetupInstrumentation();
66
+ setTimeout(async () => {
67
+ try {
68
+ await this.SetupServerEx();
69
+ resolve(true);
70
+ } catch (error) {
71
+ reject(error);
72
+ }
73
+ }, 100);
74
+ } catch (error) {
75
+ reject(error);
76
+ }
77
+ })
78
+ }
79
+
80
+ SetupServerEx = async () => {
81
+ this.ProcessStartup();
82
+
83
+ if (this.options.expressServerRouteFactory || this.options.expressServerRouteStaticFactory) {
84
+ this.expressServer = new STSExpressServer(this.options, this);
85
+ }
86
+
87
+ this.LogEx(`Service instance starting. Instance Id: [${this.options.serviceInstanceId}]`);
88
+ this.LogEx(`Master process:${process.pid} started`);
89
+
90
+ this.LogSystemTelemetry();
91
+
92
+ process.on('SIGINT', async () => {
93
+ await this.Terminate(false, 'SIGINT');
94
+ });
95
+
96
+ process.on('SIGTERM', async () => {
97
+ await this.Terminate(false, 'SIGTERM');
98
+ });
99
+
100
+ process.on('exit', (code) => {
101
+ if (code === 0) {
102
+ this.LogEx(chalk.green(`Main Process: ${process.pid} terminated gracefully with code: ${code}`));
103
+ } else {
104
+ this.LogEx(chalk.red(`Main Process: ${process.pid} terminated with code: ${code}`));
105
+ }
106
+ });
107
+
108
+ await this.SetupSTSServer();
109
+
110
+ this.ProcessStarted();
111
+
112
+ this.LogEx(chalk.green(`Main process:${process.pid} started`));
113
+ }
114
+
115
+ async TerminateApplication()
116
+ {
117
+ await this.Terminate(false, 'SIGINT');
118
+ }
119
+
120
+ override ProcessTerminating = async (): Promise<void> => {
121
+ return;
122
+ }
123
+ }
@@ -0,0 +1,177 @@
1
+ /* eslint @typescript-eslint/no-explicit-any: 0, @typescript-eslint/no-unused-vars: 0 */ // --> OFF
2
+ import Debug from "debug";
3
+ const debug = Debug(`proc:${process.pid}:socketiohelper`);
4
+
5
+ import { JSONObject } from '@nsshunt/stsutils'
6
+
7
+ import { Server, Namespace, Socket } from "socket.io";
8
+
9
+ import { STSSocketIONamespace, STSServerSocket, STSNamespace,
10
+ ISocketIoServerHelper, ServerEventCb, InterServerEvents } from './commonTypes'
11
+
12
+ import { STSDefaultClientToServerEvents, STSDefaultServerToClientEvents } from '@nsshunt/stssocketio-client'
13
+
14
+ export interface ISocketIoServerHelperOptions
15
+ {
16
+ logger: (message: string) => void;
17
+ }
18
+
19
+ export class SocketIoServerHelper<ClientToServerEvents extends STSDefaultClientToServerEvents, ServerToClientEvents extends STSDefaultServerToClientEvents> implements ISocketIoServerHelper<ClientToServerEvents, ServerToClientEvents>
20
+ {
21
+ #socketIoServerHelperOptions: ISocketIoServerHelperOptions
22
+
23
+ constructor(options: ISocketIoServerHelperOptions) { // IProcessBase
24
+ this.#socketIoServerHelperOptions = options;
25
+ }
26
+
27
+ #namespace: Record<string, STSSocketIONamespace> = { };
28
+
29
+ LogMessage = (namespace: STSSocketIONamespace, message: string): void => {
30
+
31
+ this.#socketIoServerHelperOptions.logger(`${namespace.namespace}: ${message}`);
32
+ /*
33
+ this.#stsApp.UpdateInstrument(Gauge.LOGGER, {
34
+ LogMessage: message
35
+ } as InstrumentLogTelemetry);
36
+ */
37
+ };
38
+
39
+ LeaveRoom = (namespace: STSSocketIONamespace, socket: Socket<ClientToServerEvents, ServerToClientEvents>, room: string): void => {
40
+ const logMessage = `${namespace.socketionamespace.name}: Leaving room [${room}]`;
41
+ debug(logMessage);
42
+ this.LogMessage(namespace, logMessage);
43
+ socket.leave(room);
44
+ };
45
+
46
+ JoinRoom = (namespace: STSSocketIONamespace, socket: Socket<ClientToServerEvents, ServerToClientEvents>, room: string): void => {
47
+ const logMessage = `${namespace.socketionamespace.name}: Socket joining room [${room}], ID: [${socket.id}]`;
48
+ debug(logMessage);
49
+ this.LogMessage(namespace, logMessage);
50
+ socket.join(room);
51
+ };
52
+
53
+ #SetupStandardEvents = (namespace: STSSocketIONamespace, socket: Socket<STSDefaultClientToServerEvents, STSDefaultServerToClientEvents>): void => {
54
+ socket.on("__STSdisconnect", (reason) => {
55
+ debug(`${namespace.socketionamespace.name}: socket disconnect, ID: [${socket.id}] [${reason}]`);
56
+ });
57
+
58
+ socket.on("__STSdisconnecting", (reason, callBackResult) => {
59
+ debug(`${namespace.socketionamespace.name}: socket disconnecting, ID: [${socket.id}] [${reason}]`);
60
+ callBackResult("__STSdisconnecting accepted by server.");
61
+ });
62
+
63
+ socket.on('__STSjoinRoom', (rooms: string[]): void => { //@@ names
64
+ rooms.forEach((room) => {
65
+ const logMessage = `${namespace.socketionamespace.name}:socket.on:joinRoom: Socket joining room [${room}], ID: [${socket.id}]`;
66
+ debug(logMessage);
67
+ this.JoinRoom(namespace, socket, room)
68
+ });
69
+ });
70
+
71
+ socket.on('__STSleaveRoom', (rooms: string[]): void => { //@@ names
72
+ rooms.forEach((room) => {
73
+ const logMessage = `${namespace.socketionamespace.name}:socket.on:joinRoom: Socket leaving room [${room}], ID: [${socket.id}]`;
74
+ debug(logMessage);
75
+ this.LeaveRoom(namespace, socket, room);
76
+ });
77
+ });
78
+
79
+ socket.on('__STSsendToRoom', (rooms: string[], payload: { command: string, payload: JSONObject }): void => {
80
+ rooms.forEach((room) => {
81
+ const logMessage = `${namespace.socketionamespace.name}:socket.on:sendToRoom: Sending to room [${room}], ID: [${socket.id}]`;
82
+ debug(logMessage);
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ namespace.socketionamespace.to(room).emit(payload.command as any, payload);
85
+ });
86
+ });
87
+ }
88
+
89
+ // Use this middleward to check every incomming connection
90
+ #SetupConnectionMiddleware = (nameSpace: STSSocketIONamespace) => {
91
+ nameSpace.socketionamespace.use((socket, next) => {
92
+ //console.log(JSON.stringify(socket));
93
+ //console.log(JSON.stringify(socket.handshake.auth));
94
+ //console.log(JSON.stringify(socket.handshake.headers));
95
+ //if (isValid(socket.request)) {
96
+ const a=5; // for lint purposes
97
+ if (a === 5) {
98
+ next();
99
+ } else {
100
+ next(new Error("invalid"));
101
+ }
102
+ });
103
+ }
104
+
105
+ // Use this middleware to check very packet being received
106
+ #SetupMessageMiddleware = (socket: STSServerSocket) => {
107
+ socket.use(([event, ...args], next) => {
108
+ //console.log(JSON.stringify(event));
109
+ //console.log(JSON.stringify(args));
110
+ /*
111
+ if (isUnauthorized(event)) {
112
+ return next(new Error("unauthorized event"));
113
+ }
114
+ */
115
+ next();
116
+ });
117
+ }
118
+
119
+ SetupNamespace = (io: Server, namespace: STSNamespace, rooms: string[], autoJoinRooms: boolean, /* serverSocketEvents: ServerSocketEvent[], */
120
+ socketConnectCallBack: ((socket: Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents>) => void) | null,
121
+ socketEventsCallBack: ((socket: Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents>) => void) | null
122
+ ): Namespace<ClientToServerEvents, ServerToClientEvents, InterServerEvents> => {
123
+ // Create STS Command Centre Client namespace
124
+ this.#namespace[namespace] = {
125
+ namespace: namespace,
126
+ pid: process.pid,
127
+ socketionamespace: io.of(`/${namespace}/`)
128
+ }
129
+
130
+ this.#SetupConnectionMiddleware(this.#namespace[namespace]);
131
+
132
+ this.#namespace[namespace].socketionamespace.on("connection", socket => {
133
+ const logMessage = `${namespace}: Socket connected, ID: [${socket.id}]`;
134
+ debug(logMessage);
135
+ this.LogMessage(this.#namespace[namespace], logMessage);
136
+ debug(`${namespace}: Auth: [${JSON.stringify(socket.handshake.auth)}]`);
137
+
138
+ this.#SetupMessageMiddleware(socket);
139
+
140
+ if (autoJoinRooms) {
141
+ rooms.map((room) => {
142
+ this.JoinRoom(this.#namespace[namespace], socket, room);
143
+ });
144
+ }
145
+
146
+ this.#SetupStandardEvents(this.#namespace[namespace], socket);
147
+
148
+ if (socketConnectCallBack) {
149
+ setTimeout(() => {
150
+ socketConnectCallBack(socket);
151
+ }, 0);
152
+ }
153
+
154
+ if (socketEventsCallBack) {
155
+ socketEventsCallBack(socket);
156
+ }
157
+ });
158
+
159
+ return this.#namespace[namespace].socketionamespace as Namespace<ClientToServerEvents, ServerToClientEvents, InterServerEvents>;
160
+ }
161
+
162
+ GetSTSSocketIONamespace = (namespace: string): STSSocketIONamespace => {
163
+ return this.#namespace[namespace];
164
+ }
165
+
166
+ DisconnectSockets = (): void =>
167
+ {
168
+ for (const [, namespace] of Object.entries(this.#namespace)) {
169
+ namespace.socketionamespace.disconnectSockets();
170
+ }
171
+ this.#namespace = { };
172
+ }
173
+
174
+ SetupEvent(event: ClientToServerEvents, eventCb: ServerEventCb): ISocketIoServerHelper<ClientToServerEvents, ServerToClientEvents> {
175
+ return this as any;
176
+ }
177
+ }