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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # Node.js exaroton API client
2
2
 
3
- ### About
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://support.exaroton.com/hc/en-us/articles/360011926177
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
- ### Usage
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
- #### Create a client object
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://support.exaroton.com/hc/en-us/articles/360011926177#account).
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://support.exaroton.com/hc/en-us/articles/360011926177#servers).
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://support.exaroton.com/hc/en-us/articles/360011926177#servers). You can use
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.5.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 = "https://api.exaroton.com/v1/";
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
  *
@@ -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,8 @@
1
+ const Stream = require("./Stream");
2
+
3
+ class HeapStream extends Stream {
4
+ name = "heap";
5
+ startStatuses = [1];
6
+ }
7
+
8
+ module.exports = HeapStream;
@@ -0,0 +1,8 @@
1
+ const Stream = require("./Stream");
2
+
3
+ class StatsStream extends Stream {
4
+ name = "stats";
5
+ startStatuses = [1];
6
+ }
7
+
8
+ module.exports = StatsStream;
@@ -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;