configurapi-runner-ws 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "configurapi-runner-ws",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "Websocket runner for configurapi.",
5
5
  "bin": {
6
6
  "configurapi-runner-ws": "src/app.mjs"
package/src/app.mjs CHANGED
@@ -32,6 +32,7 @@ function logDebug(s)
32
32
  const commander = new Command();
33
33
  commander.option('-p, --port [number]', 'Port number')
34
34
  .option('--s-port [number]', 'Secure port number')
35
+ .option('-m, --mport [number]', 'Management port number')
35
36
  .option('-f, --file [path]', 'Path to the config file')
36
37
  .option('--key [path]', 'Path to the private key file (.key)')
37
38
  .option('--cert [path]', 'Path to the certificate file (.pem)')
@@ -40,6 +41,7 @@ commander.option('-p, --port [number]', 'Port number')
40
41
 
41
42
  let port = commander.port ? commander.port : 9090;
42
43
  let sPort = commander.sPort ? commander.sPort : 9443;
44
+ let mPort = commander.mPort ? commander.mPort : 9100;
43
45
  let configPath = commander.file ? commander.file : 'config.yaml';
44
46
  let keepAliveTimeout = commander.keepAlive ? commander.keepAlive : (process.env.CONFIGURAPI_RUNNER_SELF_KEEP_ALIVE || 0);
45
47
  let key = commander.key ? fs.readFileSync(commander.key) : undefined;
@@ -52,4 +54,4 @@ let app = new App();
52
54
  app.on('error', (s) => {logError(s);});
53
55
  app.on('debug', (s) => {logDebug(s);});
54
56
  app.on('trace', (s) => {logTrace(s);});
55
- await app.run({port: port, configPath:configPath, keepAliveTimeout:keepAliveTimeout, key: key, cert: cert, sPort: sPort});
57
+ await app.run({port: port, configPath:configPath, keepAliveTimeout:keepAliveTimeout, key: key, cert: cert, sPort: sPort, mPort});
package/src/index.js CHANGED
@@ -6,6 +6,28 @@ const events = require('events');
6
6
  const Configurapi = require('configurapi');
7
7
  const { WebSocketServer } = require('ws');
8
8
  const { randomUUID } = require("node:crypto");
9
+ const { Response, Route, Policy, PolicyHandlerLoader, LogLevel } = require('configurapi');
10
+ const oneliner = require('one-liner');
11
+ const wsAdapter = require('./wsAdapter');
12
+ const URL = require('url');
13
+
14
+ function addInternalRoute(self, config, routeName,)
15
+ {
16
+ let route = new Route();
17
+ route.name = routeName;
18
+
19
+ route.on(LogLevel.Trace, (s)=>self.emit(LogLevel.Trace, oneliner(s)));
20
+ route.on(LogLevel.Debug, (s)=>self.emit(LogLevel.Debug, oneliner(s)));
21
+ route.on(LogLevel.Error, (s)=>self.emit(LogLevel.Error, oneliner(s)));
22
+
23
+ let policy = new Policy()
24
+ policy.name = `${routeName}`;
25
+ route.policies = [policy]
26
+
27
+ config.events.set(route.name, route);
28
+ }
29
+
30
+ const connections = []
9
31
 
10
32
  module.exports = class HttpRunner extends events.EventEmitter
11
33
  {
@@ -15,6 +37,7 @@ module.exports = class HttpRunner extends events.EventEmitter
15
37
 
16
38
  this.port = 8000;
17
39
  this.sPort = 8443;
40
+ this.mPort = 9100;
18
41
  this.configPath = "config.yaml";
19
42
  this.service = undefined;
20
43
  this.keepAliveTimeout = 0;
@@ -28,7 +51,35 @@ module.exports = class HttpRunner extends events.EventEmitter
28
51
 
29
52
  let config = Configurapi.Config.load(this.configPath);
30
53
 
31
- this.service = new Configurapi.Service(config);
54
+ let loader = new PolicyHandlerLoader()
55
+ loader.handlers.set('list_@connections', (e)=>
56
+ {
57
+ e.response.headers['Content-Type'] = 'application/json';
58
+ e.response.body = Object.keys(connections);
59
+ });
60
+ loader.handlers.set('post_@connection', async (e)=>
61
+ {
62
+ e.response.headers['Content-Type'] = 'application/json';
63
+
64
+ const connectionId = e.params?.['@connection'];
65
+
66
+ if(connectionId && connections[connectionId])
67
+ {
68
+ let wsResponse = new Response(e.payload, 200, {...e.request.headers, 'connection-id': connectionId})
69
+ await wsAdapter.write(connections[connectionId].ws, wsResponse)
70
+ e.response.statusCode = 204
71
+ }
72
+ else
73
+ {
74
+ e.response.statusCode = 404;
75
+ e.response.body = JSON.stringify({ message: `The connection '${connectionId}' does not exist.`, details: ''})
76
+ }
77
+ });
78
+
79
+ addInternalRoute(this, config, 'list_@connections')
80
+ addInternalRoute(this, config, 'post_@connection')
81
+
82
+ this.service = new Configurapi.Service(config, loader);
32
83
  this.service.on("trace", (s) => this.emit("trace", s));
33
84
  this.service.on("debug", (s) => this.emit("debug", s));
34
85
  this.service.on("error", (s) => this.emit("error", s));
@@ -55,6 +106,14 @@ module.exports = class HttpRunner extends events.EventEmitter
55
106
  this.emit("trace", "Server is listening..."+this.port);
56
107
  }
57
108
 
109
+ let managementServer = http.createServer({key: this.key, cert: this.cert}, (req, resp) => {
110
+ this._handleManagementRequest(req, resp);
111
+ });
112
+ managementServer.keepAliveTimeout = this.keepAliveTimeout;
113
+ managementServer.listen(this.mPort);
114
+
115
+ this.emit('trace', 'Management server is listening...'+this.mPort);
116
+
58
117
  const wss = new WebSocketServer({ server: httpServer, path: "/ws", handleProtocols: (protocols, req) => {
59
118
  if(protocols === undefined)
60
119
  {
@@ -77,9 +136,13 @@ module.exports = class HttpRunner extends events.EventEmitter
77
136
  const connectionId = randomUUID();
78
137
  ws.connectionId = connectionId;
79
138
 
139
+ connections[connectionId] = {
140
+ connectionId,
141
+ ws
142
+ }
80
143
  this.emit("trace", `[connect] ${connectionId}`);
81
144
 
82
- let onConnectPromise = this._requestListener(ws, undefined, 'on_connect');
145
+ let onConnectPromise = config.events.has('on_connect') ? this._requestListener(ws, undefined, 'on_connect') : undefined;
83
146
 
84
147
  ws.on("message", async (data) =>
85
148
  {
@@ -92,10 +155,12 @@ module.exports = class HttpRunner extends events.EventEmitter
92
155
 
93
156
  ws.on("close", async(code, reason) =>
94
157
  {
95
- await this._requestListener(ws, undefined, 'on_disconnect');
158
+ await (config.events.has('on_disconnect') ? this._requestListener(ws, undefined, 'on_disconnect') : undefined);
96
159
 
97
160
  const reasonMessage = reason && reason.length ? reason.toString("utf8") : "";
98
161
 
162
+ delete connections[connectionId];
163
+
99
164
  this.emit("trace", `[disconnect] ${connectionId} (${code}${reasonMessage ? ` - ${reasonMessage}` : ""})`);
100
165
  });
101
166
 
@@ -118,6 +183,7 @@ module.exports = class HttpRunner extends events.EventEmitter
118
183
  if ('key' in options) this.key = options.key;
119
184
  if ('cert' in options) this.cert = options.cert;
120
185
  if ('sPort' in options) this.sPort = options.sPort;
186
+ if ('mPort' in options) this.mPort = options.mPort;
121
187
  }
122
188
 
123
189
  async _requestListener(ws, incomingMessage, customEventName)
@@ -188,4 +254,81 @@ module.exports = class HttpRunner extends events.EventEmitter
188
254
  }
189
255
  }
190
256
 
257
+ async _handleManagementRequest(incomingMessage, serverResponse)
258
+ {
259
+ try
260
+ {
261
+ let event = new Configurapi.Event(await this._toRequest(incomingMessage));
262
+
263
+ await this.service.process(event);
264
+
265
+ serverResponse.writeHead(200, event.response.headers);
266
+ serverResponse.end(JSON.stringify(event.response));
267
+ }
268
+ catch (error)
269
+ {
270
+ let response = new Configurapi.ErrorResponse(error, error instanceof SyntaxError ? 400 : 500);
271
+
272
+ serverResponse.writeHead(response.statusCode, response.headers);
273
+ serverResponse.end(JSON.stringify(response.body));
274
+ }
275
+ }
276
+
277
+ async _toRequest(incomingMessage)
278
+ {
279
+ let request = new Configurapi.Request();
280
+
281
+ request.method = incomingMessage.method.toLowerCase();
282
+ request.headers = incomingMessage.headers;
283
+
284
+ let url = URL.parse(incomingMessage.url, true);
285
+ let data = [];
286
+
287
+ request.payload = undefined;
288
+ request.query = url.query;
289
+ request.path = url.pathname;
290
+ request.pathAndQuery = url.href;
291
+
292
+ return new Promise((resolve, reject) =>
293
+ {
294
+ incomingMessage.on('data', function(chunk) {
295
+ data.push(chunk);
296
+ }).on('end', function() {
297
+ if(data.length <= 0) {
298
+ request.payload = undefined;
299
+ return resolve(request);
300
+ }
301
+
302
+ request.payload = Buffer.concat(data);
303
+
304
+ if(request.payload && 'content-type' in request.headers)
305
+ {
306
+ let contentType = request.headers['content-type'];
307
+
308
+ if(contentType.startsWith('text/'))
309
+ {
310
+ request.payload = request.payload.toString();
311
+ }
312
+ else if(contentType.startsWith('application/json'))
313
+ {
314
+ try
315
+ {
316
+ if(request.payload)
317
+ {
318
+ request.payload = JSON.parse(request.payload);
319
+ }
320
+ }
321
+ catch(err)
322
+ {
323
+ reject(err);
324
+ }
325
+ }
326
+ }
327
+
328
+ resolve(request);
329
+ }).on('error', function(err) {
330
+ reject(err);
331
+ });
332
+ });
333
+ }
191
334
  };