configurapi-runner-ws 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # configurapi-runner-ws
2
+
3
+ - Use `Event` for an incoming data.
4
+ - User `Response` for a response data.
5
+ - connectionId is available at `event.request.headers.connectionId`
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "configurapi-runner-ws",
3
+ "version": "1.0.0",
4
+ "description": "Websocket runner for configurapi.",
5
+ "bin": {
6
+ "configurapi-runner-ws": "src/app.mjs"
7
+ },
8
+ "main": "src/index.js",
9
+ "scripts": {
10
+ "test": "cd test && mocha ./"
11
+ },
12
+ "files": [
13
+ "src/*"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://gitlab.com/mappies/configurapi-runner-ws.git"
18
+ },
19
+ "keywords": [
20
+ "configurapi",
21
+ "websocket"
22
+ ],
23
+ "author": "Nithiwat Kampanya",
24
+ "license": "MIT",
25
+ "bugs": {
26
+ "url": "https://gitlab.com/mappies/configurapi-runner-ws/issues"
27
+ },
28
+ "homepage": "https://gitlab.com/mappies/configurapi-runner-ws#readme",
29
+ "dependencies": {
30
+ "commander": "^14.0.1",
31
+ "configurapi": "^1.9.0",
32
+ "dotenv": "^17.2.3",
33
+ "one-liner": "^1.3.0",
34
+ "ws": "^8.18.3"
35
+ }
36
+ }
package/src/app.mjs ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import App from './index.js'
5
+ import oneliner from 'one-liner'
6
+ import * as fs from 'fs';
7
+
8
+ ///////////////////////////////////
9
+ // Utility functions
10
+ ///////////////////////////////////
11
+ function format(prefix, str)
12
+ {
13
+ let time = new Date().toISOString();
14
+ return `${time} ${prefix}: ${oneliner(str)}`;
15
+ }
16
+ function logTrace(s)
17
+ {
18
+ console.log(format('Trace', s));
19
+ }
20
+ function logError(s)
21
+ {
22
+ console.error(format('Error', s));
23
+ }
24
+ function logDebug(s)
25
+ {
26
+ console.log(format('Debug', s));
27
+ }
28
+
29
+ ///////////////////////////////////
30
+ // Process arguments
31
+ ///////////////////////////////////
32
+ const commander = new Command();
33
+ commander.option('-p, --port [number]', 'Port number')
34
+ .option('--s-port [number]', 'Secure port number')
35
+ .option('-f, --file [path]', 'Path to the config file')
36
+ .option('--key [path]', 'Path to the private key file (.key)')
37
+ .option('--cert [path]', 'Path to the certificate file (.pem)')
38
+ .option('-k, --keep-alive [number]', 'The number of milliseconds of inactivity a server needs to wait. Default to 0 which disable the keep alive behavior')
39
+ .parse(process.argv);
40
+
41
+ let port = commander.port ? commander.port : 9090;
42
+ let sPort = commander.sPort ? commander.sPort : 9443;
43
+ let configPath = commander.file ? commander.file : 'config.yaml';
44
+ let keepAliveTimeout = commander.keepAlive ? commander.keepAlive : (process.env.CONFIGURAPI_RUNNER_SELF_KEEP_ALIVE || 0);
45
+ let key = commander.key ? fs.readFileSync(commander.key) : undefined;
46
+ let cert = commander.cert ? fs.readFileSync(commander.cert) : undefined;
47
+
48
+ ///////////////////////////////////
49
+ // Start the API
50
+ ///////////////////////////////////
51
+ let app = new App();
52
+ app.on('error', (s) => {logError(s);});
53
+ app.on('debug', (s) => {logDebug(s);});
54
+ app.on('trace', (s) => {logTrace(s);});
55
+ await app.run({port: port, configPath:configPath, keepAliveTimeout:keepAliveTimeout, key: key, cert: cert, sPort: sPort});
package/src/index.js ADDED
@@ -0,0 +1,134 @@
1
+ require('dotenv').config();
2
+ const http = require('http');
3
+ const https = require('https');
4
+ const WsAdapter = require('./wsAdapter');
5
+ const events = require('events');
6
+ const Configurapi = require('configurapi');
7
+ const { WebSocketServer } = require('ws');
8
+ const { randomUUID } = require("node:crypto");
9
+
10
+ module.exports = class HttpRunner extends events.EventEmitter
11
+ {
12
+ constructor()
13
+ {
14
+ super();
15
+
16
+ this.port = 8000;
17
+ this.sPort = 8443;
18
+ this.configPath = "config.yaml";
19
+ this.service = undefined;
20
+ this.keepAliveTimeout = 0;
21
+ this.key = undefined;
22
+ this.cert = undefined;
23
+ }
24
+
25
+ async run(options)
26
+ {
27
+ this._handleOptions(options);
28
+
29
+ let config = Configurapi.Config.load(this.configPath);
30
+
31
+ this.service = new Configurapi.Service(config);
32
+ this.service.on("trace", (s) => this.emit("trace", s));
33
+ this.service.on("debug", (s) => this.emit("debug", s));
34
+ this.service.on("error", (s) => this.emit("error", s));
35
+ await this.service.init();
36
+
37
+ let httpServer;
38
+
39
+ if(this.cert && this.key && this.sPort)
40
+ {
41
+ httpServer = https.createServer({key: this.key, cert: this.cert});
42
+
43
+ httpServer.keepAliveTimeout = this.keepAliveTimeout;
44
+ httpServer.listen(this.sPort);
45
+
46
+ this.emit("trace", "Secure Server is listening...");
47
+ }
48
+ else
49
+ {
50
+ httpServer = http.createServer();
51
+
52
+ httpServer.keepAliveTimeout = this.keepAliveTimeout;
53
+ httpServer.listen(this.port);
54
+
55
+ this.emit("trace", "Server is listening..."+this.port);
56
+ }
57
+
58
+ const wss = new WebSocketServer({ server: httpServer, path: "/ws" })
59
+
60
+ wss.on("connection", async (ws, req) =>
61
+ {
62
+ const connectionId = randomUUID();
63
+ ws.connectionId = connectionId;
64
+
65
+ this.emit("trace", `[connect] ${connectionId}`);
66
+
67
+ ws.on("message", async (data) =>
68
+ {
69
+ const incomingMessage = typeof data === "string" ? data : data.toString("utf8");
70
+
71
+ this._requestListener(ws, incomingMessage);
72
+ });
73
+
74
+ ws.on("close", (code, reason) =>
75
+ {
76
+ const reasonMessage = reason && reason.length ? reason.toString("utf8") : "";
77
+
78
+ this.emit("trace", `[disconnect] ${connectionId} (${code}${reasonMessage ? ` - ${reasonMessage}` : ""})`);
79
+ });
80
+
81
+ ws.on("error", (err) =>
82
+ {
83
+ // Note: 'error' will often be followed by 'close'
84
+ const message = err instanceof Error ? err.message : err;
85
+ this.emit("error", `${connectionId} - ${message}`);
86
+ });
87
+ });
88
+ }
89
+
90
+ _handleOptions(options)
91
+ {
92
+ if (!options) return;
93
+
94
+ if ('port' in options) this.port = options.port;
95
+ if ('configPath' in options) this.configPath = options.configPath;
96
+ if ('keepAliveTimeout' in options) this.keepAliveTimeout = options.keepAliveTimeout;
97
+ if ('key' in options) this.key = options.key;
98
+ if ('cert' in options) this.cert = options.cert;
99
+ if ('sPort' in options) this.sPort = options.sPort;
100
+ }
101
+
102
+ async _requestListener(ws, incomingMessage)
103
+ {
104
+ try
105
+ {
106
+ let event = new Configurapi.Event(await WsAdapter.toRequest(ws, incomingMessage));
107
+
108
+ await this.service.process(event);
109
+
110
+ await WsAdapter.write(ws, event.response);
111
+ }
112
+ catch (error)
113
+ {
114
+ let response = new Configurapi.ErrorResponse(error, error instanceof SyntaxError ? 400 : 500);
115
+
116
+ try
117
+ {
118
+ await WsAdapter.write(ws, response);
119
+ }
120
+ catch(err)
121
+ {
122
+ try
123
+ {
124
+ this.emit("error", JSON.stringify(err));
125
+ }
126
+ catch(errorFromListner)
127
+ {
128
+ console.log(errorFromListner);
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ };
@@ -0,0 +1,48 @@
1
+ const Configurapi = require('configurapi');
2
+ const { WebSocket } = require('ws');
3
+
4
+ module.exports = {
5
+ write: async function(ws, message)
6
+ {
7
+ // If the connection is disconnected, ignore.
8
+ if (ws.readyState !== WebSocket.OPEN) return;
9
+
10
+ await new Promise((resolve, reject)=>
11
+ {
12
+ try
13
+ {
14
+ ws.send(JSON.stringify(message), (err) => {
15
+ if(err)
16
+ {
17
+ return reject(err);
18
+ }
19
+ else
20
+ {
21
+ return resolve();
22
+ }
23
+ });
24
+ }
25
+ catch(e)
26
+ {
27
+ reject(e)
28
+ }
29
+ });
30
+ },
31
+
32
+ toRequest: async function(ws, incomingMessage)
33
+ {
34
+ let data = JSON.parse(incomingMessage);
35
+
36
+ let request = new Configurapi.Request();
37
+
38
+ request.method = '';
39
+ request.headers = data.headers || {};
40
+ request.name = data.name || undefined;
41
+ request.query = data.query || {};
42
+ request.payload = data.payload || undefined;
43
+
44
+ request.headers['connectionId'] = ws.connectionId;
45
+
46
+ return request;
47
+ }
48
+ };