exaroton 1.5.1 → 1.7.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 +112 -7
- package/package.json +3 -2
- package/src/Client.js +23 -1
- package/src/Server/Server.js +112 -2
- package/src/Websocket/ConsoleStream.js +33 -0
- package/src/Websocket/HeapStream.js +8 -0
- package/src/Websocket/StatsStream.js +8 -0
- package/src/Websocket/Stream.js +163 -0
- package/src/Websocket/TickStream.js +16 -0
- package/src/Websocket/WebsocketClient.js +269 -0
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Node.js exaroton API client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## About
|
|
4
4
|
The exaroton API allows automated access to some basic functionalities of your game servers, such as starting or stopping
|
|
5
|
-
the server. You can read the API documentation here: https://
|
|
5
|
+
the server. You can read the API documentation here: https://developers.exaroton.com
|
|
6
6
|
|
|
7
7
|
This is the official Node.js implementation of this API.
|
|
8
8
|
|
|
@@ -12,10 +12,11 @@ This is the official Node.js implementation of this API.
|
|
|
12
12
|
npm install exaroton
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Usage
|
|
16
16
|
To use the API and this client you have to get your API key, which you can generate in your exaroton account settings: https://exaroton.com/account
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
### Create a client object
|
|
19
20
|
```js
|
|
20
21
|
const {Client} = require('exaroton');
|
|
21
22
|
|
|
@@ -23,13 +24,15 @@ const client = new Client(token);
|
|
|
23
24
|
```
|
|
24
25
|
*Remember to keep your token secret and don't add it to any private or public code repositories.*
|
|
25
26
|
|
|
27
|
+
### REST API
|
|
28
|
+
|
|
26
29
|
#### Get account info
|
|
27
30
|
````js
|
|
28
31
|
let account = await client.getAccount();
|
|
29
32
|
console.log("My account is " + account.name + " and I have " + account.credits + " credits.");
|
|
30
33
|
````
|
|
31
34
|
|
|
32
|
-
The account object contains the fields and information as listed in the [documentation](https://
|
|
35
|
+
The account object contains the fields and information as listed in the [documentation](https://developers.exaroton.com/#account-get).
|
|
33
36
|
|
|
34
37
|
#### List servers
|
|
35
38
|
```js
|
|
@@ -40,7 +43,7 @@ for(let server of servers) {
|
|
|
40
43
|
}
|
|
41
44
|
```
|
|
42
45
|
|
|
43
|
-
Each server object contains the fields and information as listed in the [documentation](https://
|
|
46
|
+
Each server object contains the fields and information as listed in the [documentation](https://developers.exaroton.com/#servers-get).
|
|
44
47
|
|
|
45
48
|
#### Create a server object by ID
|
|
46
49
|
```js
|
|
@@ -65,7 +68,7 @@ if (server.hasStatus(server.STATUS.ONLINE)) {
|
|
|
65
68
|
console.log("Server is offline.");
|
|
66
69
|
}
|
|
67
70
|
```
|
|
68
|
-
The server status is an `integer` as described in the [documentation](https://
|
|
71
|
+
The server status is an `integer` as described in the [documentation](https://developers.exaroton.com/#header-server-status). You can use
|
|
69
72
|
the [ServerStatus](./src/Server/ServerStatus.js) object, which you can require on its own `const {ServerStatus} = require('exaroton')` or via the
|
|
70
73
|
shorthand `server.STATUS` property.
|
|
71
74
|
|
|
@@ -133,6 +136,25 @@ try {
|
|
|
133
136
|
```
|
|
134
137
|
The RAM is set in full GiB and has to be between 2 and 16.
|
|
135
138
|
|
|
139
|
+
#### Get the server MOTD
|
|
140
|
+
```js
|
|
141
|
+
try {
|
|
142
|
+
let motd = await server.getMOTD();
|
|
143
|
+
console.log(motd);
|
|
144
|
+
} catch (e) {
|
|
145
|
+
console.error(e.message);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### Set the server MOTD
|
|
150
|
+
```js
|
|
151
|
+
try {
|
|
152
|
+
await server.setMOTD("Hello world!");
|
|
153
|
+
} catch (e) {
|
|
154
|
+
console.error(e.message);
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
136
158
|
#### Player lists
|
|
137
159
|
A player list is a list of players such as the whitelist, ops or bans.
|
|
138
160
|
Player list entries are usually usernames, but might be something else, e.g. IPs in the banned-ips list.
|
|
@@ -194,4 +216,87 @@ try {
|
|
|
194
216
|
} catch (e) {
|
|
195
217
|
console.error(e.message);
|
|
196
218
|
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Websocket API
|
|
222
|
+
The websocket API allows a constant connection to our websocket service to receive
|
|
223
|
+
events in real time without polling (e.g. trying to get the server status every few seconds).
|
|
224
|
+
|
|
225
|
+
#### Server status events
|
|
226
|
+
You can simply connect to the websocket API for a server by running the `subscribe()` function.
|
|
227
|
+
```js
|
|
228
|
+
server.subscribe();
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
By default, you are always subscribed to server status update events, you can
|
|
232
|
+
react to server status changes by adding a listener:
|
|
233
|
+
```js
|
|
234
|
+
server.subscribe();
|
|
235
|
+
server.on("status", function(server) {
|
|
236
|
+
console.log(server.status);
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
This event is not only triggered when the status itself changes but also when other
|
|
240
|
+
events happen, e.g. a player joins the server.
|
|
241
|
+
|
|
242
|
+
#### Console
|
|
243
|
+
There are several optional streams that you can subscribe to, e.g.
|
|
244
|
+
the console.
|
|
245
|
+
```js
|
|
246
|
+
server.subscribe("console");
|
|
247
|
+
```
|
|
248
|
+
The console stream emits an event for every new console line.
|
|
249
|
+
```js
|
|
250
|
+
server.subscribe("console");
|
|
251
|
+
server.on("console:line", function(data) {
|
|
252
|
+
console.log(data.line);
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
The `data.line` property is already cleaned up for easier use in this client library, you can use `data.rawLine` if you want the raw
|
|
256
|
+
data with all formatting codes etc.
|
|
257
|
+
|
|
258
|
+
The console stream also allows you to send commands directly over the websocket. This is faster because the connection is already
|
|
259
|
+
established and no further authorization etc. is necessary. This library already checks if you are subscribed to the console stream
|
|
260
|
+
and sends the command through that stream instead, so you can just use it the same way as before:
|
|
261
|
+
```js
|
|
262
|
+
try {
|
|
263
|
+
await server.executeCommand("say Hello world!");
|
|
264
|
+
} catch (e) {
|
|
265
|
+
console.error(e.message);
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### Tick times
|
|
270
|
+
On Minecraft Java edition servers with version 1.16 and higher it is possible to get the tick times, and the TPS (ticks per second) of your server.
|
|
271
|
+
This information is also available as an optional stream.
|
|
272
|
+
```js
|
|
273
|
+
server.subscribe("tick");
|
|
274
|
+
server.on("tick:tick", function(data) {
|
|
275
|
+
console.log("Tick time: " + data.averageTickTime + "ms");
|
|
276
|
+
console.log("TPS: " + data.tps);
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### RAM usage
|
|
281
|
+
There are two different optional streams to get RAM usage, the general `stats` stream and the Java specific `heap` stream.
|
|
282
|
+
It is recommended to use the `heap` stream if you are running a server software that is based on Java. It is not recommended using both.
|
|
283
|
+
|
|
284
|
+
You can subscribe to multiple streams at once by passing an array to the subscribe function.
|
|
285
|
+
```js
|
|
286
|
+
server.subscribe(["stats", "heap"]);
|
|
287
|
+
server.on("stats:stats", function(data) {
|
|
288
|
+
console.log(data.memory.usage);
|
|
289
|
+
});
|
|
290
|
+
server.on("heap:heap", function(data) {
|
|
291
|
+
console.log(data.usage);
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
#### Unsubscribe
|
|
296
|
+
You can unsubscribe from one, multiple or all streams using the `server.unsubscribe()` function.
|
|
297
|
+
|
|
298
|
+
```js
|
|
299
|
+
server.unsubscribe("console");
|
|
300
|
+
server.unsubscribe(["tick", "heap"]);
|
|
301
|
+
server.unsubscribe(); // this disconnects the websocket connection
|
|
197
302
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exaroton",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "exaroton API client",
|
|
5
5
|
"homepage": "https://exaroton.com",
|
|
6
6
|
"main": "index.js",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"form-data": "^3.0.1",
|
|
15
|
-
"got": "^11.8.2"
|
|
15
|
+
"got": "^11.8.2",
|
|
16
|
+
"ws": "^7.4.5"
|
|
16
17
|
}
|
|
17
18
|
}
|
package/src/Client.js
CHANGED
|
@@ -9,12 +9,27 @@ const GetServersRequest = require('./Request/GetServersRequest');
|
|
|
9
9
|
const packageConfig = require('../package.json');
|
|
10
10
|
|
|
11
11
|
class Client {
|
|
12
|
+
/**
|
|
13
|
+
* @type {string}
|
|
14
|
+
*/
|
|
15
|
+
protocol = "https";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @type {string}
|
|
19
|
+
*/
|
|
20
|
+
host = "api.exaroton.com";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @type {string}
|
|
24
|
+
*/
|
|
25
|
+
basePath = "/v1/";
|
|
26
|
+
|
|
12
27
|
/**
|
|
13
28
|
* API base URL used for all requests
|
|
14
29
|
*
|
|
15
30
|
* @type {string}
|
|
16
31
|
*/
|
|
17
|
-
baseURL
|
|
32
|
+
get baseURL() { return this.protocol + "://" + this.host + this.basePath }
|
|
18
33
|
|
|
19
34
|
/**
|
|
20
35
|
* API token used for authentication
|
|
@@ -56,6 +71,13 @@ class Client {
|
|
|
56
71
|
return this;
|
|
57
72
|
}
|
|
58
73
|
|
|
74
|
+
/**
|
|
75
|
+
* @return {string}
|
|
76
|
+
*/
|
|
77
|
+
getAPIToken() {
|
|
78
|
+
return this.#apiToken;
|
|
79
|
+
}
|
|
80
|
+
|
|
59
81
|
/**
|
|
60
82
|
* Set the user agent
|
|
61
83
|
*
|
package/src/Server/Server.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
const EventEmitter = require('events');
|
|
2
|
+
|
|
3
|
+
const WebsocketClient = require("../Websocket/WebsocketClient");
|
|
1
4
|
const Software = require('./Software');
|
|
2
5
|
const Players = require('./Players');
|
|
3
6
|
const ServerStatus = require('./ServerStatus');
|
|
@@ -13,7 +16,7 @@ const GetServerOptionRequest = require('../Request/Server/GetServerOptionRequest
|
|
|
13
16
|
const SetServerOptionRequest = require('../Request/Server/SetServerOptionRequest');
|
|
14
17
|
const GetPlayerListsRequest = require('../Request/Server/PlayerLists/GetPlayerListsRequest');
|
|
15
18
|
|
|
16
|
-
class Server {
|
|
19
|
+
class Server extends EventEmitter {
|
|
17
20
|
/**
|
|
18
21
|
* Shorthand to get server status constants
|
|
19
22
|
*
|
|
@@ -29,6 +32,11 @@ class Server {
|
|
|
29
32
|
*/
|
|
30
33
|
#client;
|
|
31
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @type {WebsocketClient}
|
|
37
|
+
*/
|
|
38
|
+
#websocketClient;
|
|
39
|
+
|
|
32
40
|
/**
|
|
33
41
|
* Unique server ID
|
|
34
42
|
*
|
|
@@ -113,10 +121,18 @@ class Server {
|
|
|
113
121
|
* @param {string} id
|
|
114
122
|
*/
|
|
115
123
|
constructor(client, id) {
|
|
124
|
+
super();
|
|
116
125
|
this.#client = client;
|
|
117
126
|
this.id = id;
|
|
118
127
|
}
|
|
119
128
|
|
|
129
|
+
/**
|
|
130
|
+
* @return {Client}
|
|
131
|
+
*/
|
|
132
|
+
getClient() {
|
|
133
|
+
return this.#client;
|
|
134
|
+
}
|
|
135
|
+
|
|
120
136
|
/**
|
|
121
137
|
* Get/update the server info
|
|
122
138
|
*
|
|
@@ -163,9 +179,17 @@ class Server {
|
|
|
163
179
|
* Execute a command in the server console
|
|
164
180
|
*
|
|
165
181
|
* @param {string} command
|
|
166
|
-
* @return {Promise<Response>}
|
|
182
|
+
* @return {Promise<Response|boolean>}
|
|
167
183
|
*/
|
|
168
184
|
async executeCommand(command) {
|
|
185
|
+
if (this.#websocketClient && this.#websocketClient.hasStream("console")) {
|
|
186
|
+
/** @type {ConsoleStream} stream **/
|
|
187
|
+
let stream = this.#websocketClient.getStream("console");
|
|
188
|
+
if (stream.isStarted()) {
|
|
189
|
+
stream.sendCommand(command);
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
169
193
|
return this.#client.request(new ExecuteServerCommandRequest(this.id, command));
|
|
170
194
|
}
|
|
171
195
|
|
|
@@ -212,6 +236,24 @@ class Server {
|
|
|
212
236
|
return this.setOption("ram", ram);
|
|
213
237
|
}
|
|
214
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Get the server MOTD
|
|
241
|
+
*
|
|
242
|
+
* @returns {Promise<string>}
|
|
243
|
+
*/
|
|
244
|
+
getMOTD() {
|
|
245
|
+
return this.getOption("motd");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Set the server MOTD
|
|
250
|
+
*
|
|
251
|
+
* @param {string} motd
|
|
252
|
+
* @returns {Promise<Response>}
|
|
253
|
+
*/
|
|
254
|
+
setMOTD(motd) {
|
|
255
|
+
return this.setOption("motd", motd);
|
|
256
|
+
}
|
|
215
257
|
|
|
216
258
|
/**
|
|
217
259
|
* Get a server option
|
|
@@ -284,6 +326,74 @@ class Server {
|
|
|
284
326
|
return false;
|
|
285
327
|
}
|
|
286
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Get a websocket client for this server
|
|
331
|
+
*
|
|
332
|
+
* @return {WebsocketClient}
|
|
333
|
+
*/
|
|
334
|
+
getWebsocketClient() {
|
|
335
|
+
if (!this.#websocketClient) {
|
|
336
|
+
this.#websocketClient = new WebsocketClient(this);
|
|
337
|
+
this.#websocketClient.on("status", (server) => this.emit("status", server));
|
|
338
|
+
this.#websocketClient.on("event", (data) => this.emit(data.stream + ":" + data.type, data.data));
|
|
339
|
+
}
|
|
340
|
+
return this.#websocketClient;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Subscribe to one or multiple streams
|
|
345
|
+
*
|
|
346
|
+
* @return {boolean}
|
|
347
|
+
* @param {string[]|string} streams
|
|
348
|
+
*/
|
|
349
|
+
subscribe(streams) {
|
|
350
|
+
let websocketClient = this.getWebsocketClient();
|
|
351
|
+
if (!websocketClient.isConnected()) {
|
|
352
|
+
websocketClient.connect();
|
|
353
|
+
}
|
|
354
|
+
if (!streams) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (typeof streams === "string") {
|
|
359
|
+
streams = [streams];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for (let stream of streams) {
|
|
363
|
+
let websocketStream = websocketClient.getStream(stream)
|
|
364
|
+
if (!websocketStream) {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
websocketStream.start();
|
|
368
|
+
}
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Unsubscribe from one, multiple or all streams
|
|
374
|
+
*
|
|
375
|
+
* @param {string[]|string} streams
|
|
376
|
+
*/
|
|
377
|
+
unsubscribe(streams) {
|
|
378
|
+
let websocketClient = this.getWebsocketClient();
|
|
379
|
+
if (!streams) {
|
|
380
|
+
websocketClient.disconnect();
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (typeof streams === "string") {
|
|
385
|
+
streams = [streams];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
for (let stream of streams) {
|
|
389
|
+
let websocketStream = websocketClient.getStream(stream)
|
|
390
|
+
if (websocketStream) {
|
|
391
|
+
websocketStream.stop();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
|
|
287
397
|
/**
|
|
288
398
|
* Map raw object to this instance
|
|
289
399
|
*
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const Stream = require("./Stream");
|
|
2
|
+
|
|
3
|
+
class ConsoleStream extends Stream {
|
|
4
|
+
#ansiRegex = new RegExp('[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))', "g");
|
|
5
|
+
name = "console";
|
|
6
|
+
startData = {tail: 0};
|
|
7
|
+
|
|
8
|
+
onDataMessage(type, message) {
|
|
9
|
+
switch (type) {
|
|
10
|
+
case "line":
|
|
11
|
+
this.emitEvent("line", {rawLine: message.data, line: this.parseLine(message.data)});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
parseReturns(str) {
|
|
16
|
+
str = str.replace(/^\r|\r$/, '');
|
|
17
|
+
let rIndex = str.lastIndexOf('\r');
|
|
18
|
+
if (rIndex !== -1) {
|
|
19
|
+
str = str.substr(rIndex + 1);
|
|
20
|
+
}
|
|
21
|
+
return str;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
parseLine(line) {
|
|
25
|
+
return this.parseReturns(line).replace(this.#ansiRegex, '');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
sendCommand(command) {
|
|
29
|
+
this.send("command", command);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = ConsoleStream;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
const EventEmitter = require('events');
|
|
2
|
+
|
|
3
|
+
class Stream extends EventEmitter {
|
|
4
|
+
/**
|
|
5
|
+
* @type {WebsocketClient}
|
|
6
|
+
*/
|
|
7
|
+
#client;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @type {boolean}
|
|
11
|
+
*/
|
|
12
|
+
#started = false;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @type {boolean}
|
|
16
|
+
*/
|
|
17
|
+
#shouldStart = false;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @type {string}
|
|
21
|
+
*/
|
|
22
|
+
name;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @type {{}}
|
|
26
|
+
*/
|
|
27
|
+
startData;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @type {number[]}
|
|
31
|
+
*/
|
|
32
|
+
startStatuses = [1, 2, 3, 4];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {WebsocketClient} client
|
|
36
|
+
*/
|
|
37
|
+
constructor(client) {
|
|
38
|
+
super();
|
|
39
|
+
this.#client = client;
|
|
40
|
+
this.#client.on('status', this.onStatusChange.bind(this));
|
|
41
|
+
this.#client.on('ready', this.onStatusChange.bind(this));
|
|
42
|
+
this.#client.on('disconnected', this.onDisconnected.bind(this));
|
|
43
|
+
this.#client.on('close', this.onDisconnected.bind(this));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param type
|
|
48
|
+
* @param data
|
|
49
|
+
*/
|
|
50
|
+
send(type, data) {
|
|
51
|
+
this.#client.send(this.name, type, data);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Status change event
|
|
56
|
+
*/
|
|
57
|
+
onStatusChange() {
|
|
58
|
+
this.tryToStart();
|
|
59
|
+
this.tryToStop();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Message event listener
|
|
64
|
+
*
|
|
65
|
+
* @param message
|
|
66
|
+
*/
|
|
67
|
+
onMessage(message) {
|
|
68
|
+
switch (message.type) {
|
|
69
|
+
case "started":
|
|
70
|
+
this.emit("started");
|
|
71
|
+
this.#started = true;
|
|
72
|
+
break;
|
|
73
|
+
case "stopped":
|
|
74
|
+
this.emit("stopped");
|
|
75
|
+
this.#started = false;
|
|
76
|
+
break;
|
|
77
|
+
default:
|
|
78
|
+
this.onDataMessage(message.type, message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
onDataMessage(type, message) {
|
|
83
|
+
this.emitEvent(type, message.data);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
onDisconnected() {
|
|
87
|
+
this.#started = false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Double event emitter for generic or specific event handling
|
|
92
|
+
*
|
|
93
|
+
* @param type
|
|
94
|
+
* @param data
|
|
95
|
+
*/
|
|
96
|
+
emitEvent(type, data) {
|
|
97
|
+
this.emit(type, data);
|
|
98
|
+
this.emit("event", {stream: this.name, type: type, data: data});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Start this stream
|
|
103
|
+
*/
|
|
104
|
+
start(data) {
|
|
105
|
+
if (data) {
|
|
106
|
+
this.startData = data;
|
|
107
|
+
}
|
|
108
|
+
this.#shouldStart = true;
|
|
109
|
+
this.tryToStart();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Should/can this stream be started
|
|
114
|
+
*
|
|
115
|
+
* @return {boolean}
|
|
116
|
+
*/
|
|
117
|
+
async shouldBeStarted() {
|
|
118
|
+
return this.#shouldStart && this.startStatuses.includes(await this.#client.getServerStatus());
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Try to start if possible
|
|
123
|
+
*
|
|
124
|
+
* @return {boolean}
|
|
125
|
+
*/
|
|
126
|
+
async tryToStart() {
|
|
127
|
+
if (this.#started || !this.#client.isReady() || !await this.shouldBeStarted()) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.send("start", this.startData);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Stop this stream
|
|
136
|
+
*/
|
|
137
|
+
stop() {
|
|
138
|
+
this.#shouldStart = false;
|
|
139
|
+
this.tryToStop();
|
|
140
|
+
delete this.#client.removeStream(this.name);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Try to stop this stream if possible
|
|
145
|
+
*
|
|
146
|
+
* @return {boolean}
|
|
147
|
+
*/
|
|
148
|
+
async tryToStop() {
|
|
149
|
+
if (!this.#started || await this.shouldBeStarted()) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
this.send("stop");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @return {boolean}
|
|
157
|
+
*/
|
|
158
|
+
isStarted() {
|
|
159
|
+
return this.#started;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = Stream;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const Stream = require("./Stream");
|
|
2
|
+
|
|
3
|
+
class TickStream extends Stream {
|
|
4
|
+
name = "tick";
|
|
5
|
+
startStatuses = [1];
|
|
6
|
+
|
|
7
|
+
onDataMessage(type, message) {
|
|
8
|
+
switch(type) {
|
|
9
|
+
case "tick":
|
|
10
|
+
message.data.tps = Math.round(Math.min(1000 / message.data.averageTickTime, 20) * 10) / 10;
|
|
11
|
+
this.emitEvent("tick", message.data);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = TickStream;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
const EventEmitter = require('events');
|
|
2
|
+
const WebSocket = require('ws');
|
|
3
|
+
|
|
4
|
+
const ConsoleStream = require("./ConsoleStream");
|
|
5
|
+
const HeapStream = require("./HeapStream");
|
|
6
|
+
const StatsStream = require("./StatsStream");
|
|
7
|
+
const TickStream = require("./TickStream");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Websocket client to connect to the websocket for this server
|
|
11
|
+
*/
|
|
12
|
+
class WebsocketClient extends EventEmitter {
|
|
13
|
+
/**
|
|
14
|
+
* @type {string}
|
|
15
|
+
*/
|
|
16
|
+
protocol = "wss";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @type {Client}
|
|
20
|
+
* @private
|
|
21
|
+
*/
|
|
22
|
+
#client;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @type {Server}
|
|
26
|
+
* @private
|
|
27
|
+
*/
|
|
28
|
+
#server;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @type {WebSocket}
|
|
32
|
+
*/
|
|
33
|
+
#websocket;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Automatically reconnect in case of a disconnect
|
|
37
|
+
*
|
|
38
|
+
* @type {boolean}
|
|
39
|
+
*/
|
|
40
|
+
autoReconnect = true;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Time to wait to reconnect
|
|
44
|
+
*
|
|
45
|
+
* Only change this with caution. A time too low here can
|
|
46
|
+
* cause a spam in requests which can get your application
|
|
47
|
+
* rate limited or even blocked.
|
|
48
|
+
*
|
|
49
|
+
* @type {number}
|
|
50
|
+
*/
|
|
51
|
+
reconnectTimeout = 3000;
|
|
52
|
+
|
|
53
|
+
#reconnectInterval;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @type {boolean}
|
|
57
|
+
*/
|
|
58
|
+
#connected = false;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @type {boolean}
|
|
62
|
+
*/
|
|
63
|
+
#shouldConnect = false;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @type {boolean}
|
|
67
|
+
*/
|
|
68
|
+
#serverConnected = false;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @type {boolean}
|
|
72
|
+
*/
|
|
73
|
+
#ready = false;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @type {{{string}: Stream}}
|
|
77
|
+
*/
|
|
78
|
+
#streams = {};
|
|
79
|
+
|
|
80
|
+
#availableStreams = {
|
|
81
|
+
"console": ConsoleStream,
|
|
82
|
+
"heap": HeapStream,
|
|
83
|
+
"stats": StatsStream,
|
|
84
|
+
"tick": TickStream
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {Server} server
|
|
89
|
+
*/
|
|
90
|
+
constructor(server) {
|
|
91
|
+
super();
|
|
92
|
+
this.#server = server;
|
|
93
|
+
this.#client = server.getClient();
|
|
94
|
+
this.protocol = this.#client.protocol === "https" ? "wss" : "ws";
|
|
95
|
+
this.url = this.protocol + "://" + this.#client.host + this.#client.basePath + "servers/" + this.#server.id + "/websocket";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Connect to websocket
|
|
100
|
+
*/
|
|
101
|
+
connect() {
|
|
102
|
+
this.#shouldConnect = true;
|
|
103
|
+
this.#websocket = new WebSocket(this.url, {headers: {authorization: "Bearer " + this.#client.getAPIToken()}});
|
|
104
|
+
this.#websocket.on('open', this.onOpen.bind(this));
|
|
105
|
+
this.#websocket.on('close', this.onClose.bind(this));
|
|
106
|
+
this.#websocket.on('error', this.onError.bind(this));
|
|
107
|
+
this.#websocket.on('message', this.onMessage.bind(this));
|
|
108
|
+
if (!this.streamRetryInterval) {
|
|
109
|
+
this.streamRetryInterval = setInterval(this.tryToStartStreams.bind(this), 15000);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Disconnect from the websocket and all streams
|
|
115
|
+
*/
|
|
116
|
+
disconnect() {
|
|
117
|
+
this.#shouldConnect = false;
|
|
118
|
+
this.#websocket.close();
|
|
119
|
+
this.#streams = {};
|
|
120
|
+
clearInterval(this.#reconnectInterval);
|
|
121
|
+
clearInterval(this.streamRetryInterval);
|
|
122
|
+
this.streamRetryInterval = null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
onOpen() {
|
|
126
|
+
this.#connected = true;
|
|
127
|
+
clearInterval(this.#reconnectInterval);
|
|
128
|
+
this.emit('open');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
onClose() {
|
|
132
|
+
this.emit('close');
|
|
133
|
+
this.#ready = false;
|
|
134
|
+
if (this.autoReconnect && this.#shouldConnect) {
|
|
135
|
+
this.#reconnectInterval = setInterval(this.connect.bind(this), this.reconnectTimeout);
|
|
136
|
+
} else {
|
|
137
|
+
this.#connected = false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
onError(error) {
|
|
142
|
+
this.emit('error', error);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
onMessage(rawMessage) {
|
|
146
|
+
let message = JSON.parse(rawMessage);
|
|
147
|
+
|
|
148
|
+
// noinspection FallThroughInSwitchStatementJS
|
|
149
|
+
switch (message.type) {
|
|
150
|
+
case "keep-alive":
|
|
151
|
+
break;
|
|
152
|
+
case "ready":
|
|
153
|
+
this.#ready = true;
|
|
154
|
+
this.emit('ready');
|
|
155
|
+
break;
|
|
156
|
+
case "connected":
|
|
157
|
+
this.#serverConnected = true;
|
|
158
|
+
this.emit('connected');
|
|
159
|
+
break;
|
|
160
|
+
case "disconnected":
|
|
161
|
+
this.#serverConnected = false;
|
|
162
|
+
this.emit('disconnected');
|
|
163
|
+
if (this.autoReconnect) {
|
|
164
|
+
setTimeout(this.tryToStartStreams.bind(this), this.reconnectTimeout);
|
|
165
|
+
}
|
|
166
|
+
break;
|
|
167
|
+
case "status":
|
|
168
|
+
if (message.stream === "status") {
|
|
169
|
+
this.#server.setFromObject(message.data);
|
|
170
|
+
this.emit('status', this.#server);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
default:
|
|
174
|
+
if (message.stream && this.#streams[message.stream]) {
|
|
175
|
+
this.#streams[message.stream].onMessage(message);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @return {boolean}
|
|
183
|
+
*/
|
|
184
|
+
isConnected() {
|
|
185
|
+
return this.#connected;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @return {boolean}
|
|
190
|
+
*/
|
|
191
|
+
isReady() {
|
|
192
|
+
return this.#ready;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @return {Server}
|
|
197
|
+
*/
|
|
198
|
+
getServer() {
|
|
199
|
+
return this.#server;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @return {int|number}
|
|
204
|
+
*/
|
|
205
|
+
async getServerStatus() {
|
|
206
|
+
if (!Number.isInteger(this.#server.status)) {
|
|
207
|
+
await this.#server.get();
|
|
208
|
+
}
|
|
209
|
+
return this.#server.status;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get a stream by name
|
|
214
|
+
*
|
|
215
|
+
* @param {string} stream
|
|
216
|
+
* @return {boolean|Stream}
|
|
217
|
+
*/
|
|
218
|
+
getStream(stream) {
|
|
219
|
+
if (!this.#availableStreams[stream]) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (this.#streams[stream]) {
|
|
224
|
+
return this.#streams[stream];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
this.#streams[stream] = new this.#availableStreams[stream](this);
|
|
228
|
+
this.#streams[stream].on('event', (data) => this.emit('event', data));
|
|
229
|
+
return this.#streams[stream];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @param stream
|
|
234
|
+
* @return {boolean}
|
|
235
|
+
*/
|
|
236
|
+
hasStream(stream) {
|
|
237
|
+
return !!this.#streams[stream];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
tryToStartStreams() {
|
|
241
|
+
for (let stream of Object.keys(this.#streams)) {
|
|
242
|
+
this.#streams[stream].tryToStart();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
removeStream(stream) {
|
|
247
|
+
delete this.#streams[stream];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @param stream
|
|
252
|
+
* @param type
|
|
253
|
+
* @param data
|
|
254
|
+
* @return {boolean}
|
|
255
|
+
*/
|
|
256
|
+
send(stream, type, data) {
|
|
257
|
+
if (this.#websocket.readyState !== 1 || !this.isReady()) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let message = {stream: stream, type: type};
|
|
262
|
+
if (typeof data !== "undefined") {
|
|
263
|
+
message.data = data;
|
|
264
|
+
}
|
|
265
|
+
this.#websocket.send(JSON.stringify(message));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
module.exports = WebsocketClient;
|