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 +5 -0
- package/package.json +36 -0
- package/src/app.mjs +55 -0
- package/src/index.js +134 -0
- package/src/wsAdapter.js +48 -0
package/README.md
ADDED
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
|
+
};
|
package/src/wsAdapter.js
ADDED
|
@@ -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
|
+
};
|