@soundworks/core 4.0.0-alpha.21 → 4.0.0-alpha.23
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/CHANGELOG.md +14 -3
- package/package.json +15 -15
- package/src/client/Client.js +233 -188
- package/src/client/Socket.js +84 -247
- package/src/client/index.js +6 -0
- package/src/common/constants.js +0 -1
- package/src/common/logger.js +15 -0
- package/src/common/sockets-utils.js +0 -63
- package/src/common/version.js +2 -0
- package/src/server/Plugin.js +22 -10
- package/src/server/Server.js +37 -36
- package/src/server/Socket.js +126 -250
- package/src/server/Sockets.js +103 -127
- package/src/server/audit-network-latency.worker.js +4 -3
- package/src/server/index.js +6 -0
- package/types/client/Client.d.ts +41 -8
- package/types/client/Client.d.ts.map +1 -1
- package/types/client/Context.d.ts +40 -14
- package/types/client/Context.d.ts.map +1 -1
- package/types/client/Plugin.d.ts +42 -41
- package/types/client/Plugin.d.ts.map +1 -1
- package/types/client/PluginManager.d.ts +26 -11
- package/types/client/PluginManager.d.ts.map +1 -1
- package/types/client/SharedState.d.ts +1 -1
- package/types/client/SharedState.d.ts.map +1 -1
- package/types/client/SharedStateCollection.d.ts +41 -0
- package/types/client/SharedStateCollection.d.ts.map +1 -0
- package/types/client/Socket.d.ts +41 -30
- package/types/client/Socket.d.ts.map +1 -1
- package/types/client/StateManager.d.ts +1 -1
- package/types/client/StateManager.d.ts.map +1 -1
- package/types/client/index.d.ts +0 -8
- package/types/common/BasePlugin.d.ts +24 -19
- package/types/common/BasePlugin.d.ts.map +1 -1
- package/types/common/BasePluginManager.d.ts +17 -5
- package/types/common/BasePluginManager.d.ts.map +1 -1
- package/types/common/BaseSharedState.d.ts +100 -29
- package/types/common/BaseSharedState.d.ts.map +1 -1
- package/types/common/BaseSharedStateCollection.d.ts +183 -0
- package/types/common/BaseSharedStateCollection.d.ts.map +1 -0
- package/types/common/BaseStateManager.d.ts +83 -18
- package/types/common/BaseStateManager.d.ts.map +1 -1
- package/types/common/BatchedTransport.d.ts +21 -0
- package/types/common/BatchedTransport.d.ts.map +1 -0
- package/types/common/ParameterBag.d.ts +61 -40
- package/types/common/ParameterBag.d.ts.map +1 -1
- package/types/common/PromiseStore.d.ts +11 -0
- package/types/common/PromiseStore.d.ts.map +1 -0
- package/types/common/SharedStatePrivate.d.ts +4 -3
- package/types/common/SharedStatePrivate.d.ts.map +1 -1
- package/types/common/constants.d.ts +10 -1
- package/types/common/constants.d.ts.map +1 -1
- package/types/common/logger.d.ts +1 -1
- package/types/common/sockets-utils.d.ts.map +1 -1
- package/types/common/version.d.ts +3 -0
- package/types/common/version.d.ts.map +1 -0
- package/types/server/Client.d.ts +5 -0
- package/types/server/Client.d.ts.map +1 -1
- package/types/server/Context.d.ts +162 -22
- package/types/server/Context.d.ts.map +1 -1
- package/types/server/ContextManager.d.ts +4 -4
- package/types/server/ContextManager.d.ts.map +1 -1
- package/types/server/Plugin.d.ts +56 -55
- package/types/server/Plugin.d.ts.map +1 -1
- package/types/server/PluginManager.d.ts +26 -6
- package/types/server/PluginManager.d.ts.map +1 -1
- package/types/server/Server.d.ts +207 -70
- package/types/server/Server.d.ts.map +1 -1
- package/types/server/SharedState.d.ts +1 -1
- package/types/server/SharedState.d.ts.map +1 -1
- package/types/server/SharedStateCollection.d.ts +41 -0
- package/types/server/SharedStateCollection.d.ts.map +1 -0
- package/types/server/Socket.d.ts +93 -57
- package/types/server/Socket.d.ts.map +1 -1
- package/types/server/Sockets.d.ts +32 -25
- package/types/server/Sockets.d.ts.map +1 -1
- package/types/server/StateManager.d.ts +242 -27
- package/types/server/StateManager.d.ts.map +1 -1
- package/types/server/audit-network-latency.worker.d.ts +2 -1
- package/types/server/audit-schema.d.ts +8 -8
- package/types/server/crypto.d.ts +3 -0
- package/types/server/crypto.d.ts.map +1 -0
- package/types/server/index.d.ts +0 -4
- package/types/common/promise-store.d.ts +0 -4
- package/types/common/promise-store.d.ts.map +0 -1
- package/types/common/utils.d.ts +0 -5
- package/types/common/utils.d.ts.map +0 -1
package/src/server/Server.js
CHANGED
|
@@ -4,9 +4,16 @@ import http from 'node:http';
|
|
|
4
4
|
import https from 'node:https';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import os from 'node:os';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
X509Certificate,
|
|
9
|
+
createPrivateKey,
|
|
10
|
+
} from 'node:crypto';
|
|
8
11
|
|
|
9
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
isPlainObject,
|
|
14
|
+
idGenerator,
|
|
15
|
+
getTime,
|
|
16
|
+
} from '@ircam/sc-utils';
|
|
10
17
|
import chalk from 'chalk';
|
|
11
18
|
import compression from 'compression';
|
|
12
19
|
import express from 'express';
|
|
@@ -18,11 +25,18 @@ import pem from 'pem';
|
|
|
18
25
|
import compile from 'template-literal';
|
|
19
26
|
|
|
20
27
|
import auditSchema from './audit-schema.js';
|
|
21
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
encryptData,
|
|
30
|
+
decryptData,
|
|
31
|
+
} from './crypto.js';
|
|
22
32
|
import Client from './Client.js';
|
|
23
33
|
import ContextManager from './ContextManager.js';
|
|
24
34
|
import PluginManager from './PluginManager.js';
|
|
25
35
|
import StateManager from './StateManager.js';
|
|
36
|
+
import {
|
|
37
|
+
kSocketClientId,
|
|
38
|
+
kSocketTerminate,
|
|
39
|
+
} from './Socket.js';
|
|
26
40
|
import Sockets from './Sockets.js';
|
|
27
41
|
import logger from '../common/logger.js';
|
|
28
42
|
import {
|
|
@@ -32,7 +46,7 @@ import {
|
|
|
32
46
|
CLIENT_HANDSHAKE_ERROR,
|
|
33
47
|
AUDIT_STATE_NAME,
|
|
34
48
|
} from '../common/constants.js';
|
|
35
|
-
|
|
49
|
+
import version from '../common/version.js';
|
|
36
50
|
|
|
37
51
|
let _dbNamespaces = new Set();
|
|
38
52
|
|
|
@@ -80,27 +94,6 @@ const DEFAULT_CONFIG = {
|
|
|
80
94
|
|
|
81
95
|
const TOKEN_VALID_DURATION = 20; // sec
|
|
82
96
|
|
|
83
|
-
// set terminal title
|
|
84
|
-
/** @private */
|
|
85
|
-
function setTerminalTitle(server) {
|
|
86
|
-
let title = '';
|
|
87
|
-
|
|
88
|
-
if (server._auditState !== null) {
|
|
89
|
-
const numClients = server._auditState.get('numClients');
|
|
90
|
-
let numClientStrings = [];
|
|
91
|
-
for (let name in numClients) {
|
|
92
|
-
numClientStrings.push(`${name}: ${numClients[name]}`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
title = `${server.config.app.name} | ${numClientStrings.join(' - ')}`;
|
|
96
|
-
} else {
|
|
97
|
-
title = `${server.config.app.name}`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const msg = String.fromCharCode(27) + ']0;' + title + String.fromCharCode(7);
|
|
101
|
-
process.stdout.write(msg);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
97
|
/**
|
|
105
98
|
* The `Server` class is the main entry point for the server-side of a soundworks
|
|
106
99
|
* application.
|
|
@@ -162,6 +155,7 @@ class Server {
|
|
|
162
155
|
if (!isPlainObject(config)) {
|
|
163
156
|
throw new Error(`[soundworks:Server] Invalid argument for Server constructor, config should be an object`);
|
|
164
157
|
}
|
|
158
|
+
|
|
165
159
|
/**
|
|
166
160
|
* @description Given config object merged with the following defaults:
|
|
167
161
|
* @example
|
|
@@ -225,6 +219,8 @@ class Server {
|
|
|
225
219
|
}
|
|
226
220
|
}
|
|
227
221
|
|
|
222
|
+
this.version = version;
|
|
223
|
+
|
|
228
224
|
/**
|
|
229
225
|
* Instance of the express router.
|
|
230
226
|
*
|
|
@@ -334,7 +330,6 @@ class Server {
|
|
|
334
330
|
this.stateManager.registerSchema(AUDIT_STATE_NAME, auditSchema);
|
|
335
331
|
|
|
336
332
|
logger.configure(this.config.env.verbose);
|
|
337
|
-
setTerminalTitle(this);
|
|
338
333
|
}
|
|
339
334
|
|
|
340
335
|
/**
|
|
@@ -383,7 +378,6 @@ class Server {
|
|
|
383
378
|
}
|
|
384
379
|
/** @private */
|
|
385
380
|
this._auditState = await this.stateManager.create(AUDIT_STATE_NAME, { numClients });
|
|
386
|
-
this._auditState.onUpdate(() => setTerminalTitle(this));
|
|
387
381
|
|
|
388
382
|
// basic http authentication
|
|
389
383
|
if (this.config.env.auth) {
|
|
@@ -694,7 +688,7 @@ Invalid certificate files, please check your:
|
|
|
694
688
|
// this.httpsInfos.isValid = false; // for testing
|
|
695
689
|
if (!this.httpsInfos.isValid) {
|
|
696
690
|
logger.error(chalk.red` -------------------------------------------`);
|
|
697
|
-
logger.error(chalk.red` > INVALID CERTIFICATE`);
|
|
691
|
+
logger.error(chalk.red` > INVALID CERTIFICATE `);
|
|
698
692
|
logger.error(chalk.red` i.e. you pretend to be safe but you are not`);
|
|
699
693
|
logger.error(chalk.red` -------------------------------------------`);
|
|
700
694
|
} else {
|
|
@@ -856,16 +850,13 @@ Invalid certificate files, please check your:
|
|
|
856
850
|
*/
|
|
857
851
|
_onSocketConnection(role, socket, connectionToken) {
|
|
858
852
|
const client = new Client(role, socket);
|
|
853
|
+
socket[kSocketClientId] = client.id;
|
|
859
854
|
const roles = Object.keys(this.config.app.clients);
|
|
860
855
|
|
|
861
856
|
// this has been validated
|
|
862
857
|
if (this.isProtected(role) && this.isValidConnectionToken(connectionToken)) {
|
|
863
858
|
const { ip } = decryptData(connectionToken);
|
|
864
|
-
const newData = {
|
|
865
|
-
ip: ip,
|
|
866
|
-
id: client.id,
|
|
867
|
-
};
|
|
868
|
-
|
|
859
|
+
const newData = { ip, id: client.id };
|
|
869
860
|
const newToken = encryptData(newData);
|
|
870
861
|
|
|
871
862
|
client.token = newToken;
|
|
@@ -904,11 +895,11 @@ Invalid certificate files, please check your:
|
|
|
904
895
|
}
|
|
905
896
|
|
|
906
897
|
// clean sockets
|
|
907
|
-
socket
|
|
898
|
+
socket[kSocketTerminate]();
|
|
908
899
|
});
|
|
909
900
|
|
|
910
901
|
socket.addListener(CLIENT_HANDSHAKE_REQUEST, async payload => {
|
|
911
|
-
const { role, registeredPlugins } = payload;
|
|
902
|
+
const { role, version, registeredPlugins } = payload;
|
|
912
903
|
|
|
913
904
|
if (!roles.includes(role)) {
|
|
914
905
|
console.error(`[soundworks.Server] A client with invalid role ("${role}") attempted to connect`);
|
|
@@ -920,6 +911,10 @@ Invalid certificate files, please check your:
|
|
|
920
911
|
return;
|
|
921
912
|
}
|
|
922
913
|
|
|
914
|
+
if (version !== this.version) {
|
|
915
|
+
logger.warnVersionDiscepancies(role, version, this.version);
|
|
916
|
+
}
|
|
917
|
+
|
|
923
918
|
try {
|
|
924
919
|
this.pluginManager.checkRegisteredPlugins(registeredPlugins);
|
|
925
920
|
} catch (err) {
|
|
@@ -950,7 +945,7 @@ Invalid certificate files, please check your:
|
|
|
950
945
|
this._onClientConnectCallbacks.forEach(callback => callback(client));
|
|
951
946
|
|
|
952
947
|
const { id, uuid, token } = client;
|
|
953
|
-
socket.send(CLIENT_HANDSHAKE_RESPONSE, { id, uuid, token });
|
|
948
|
+
socket.send(CLIENT_HANDSHAKE_RESPONSE, { id, uuid, token, version: this.version });
|
|
954
949
|
});
|
|
955
950
|
}
|
|
956
951
|
|
|
@@ -1060,6 +1055,12 @@ Invalid certificate files, please check your:
|
|
|
1060
1055
|
},
|
|
1061
1056
|
env: {
|
|
1062
1057
|
type: config.env.type,
|
|
1058
|
+
// use to configure the socket if the server is running on a different
|
|
1059
|
+
// location than the one the client was served from (cf. #90)
|
|
1060
|
+
useHttps: config.env.useHttps,
|
|
1061
|
+
serverAddress: config.env.serverAddress,
|
|
1062
|
+
port: config.env.port,
|
|
1063
|
+
// other config, to review
|
|
1063
1064
|
websockets: config.env.websockets,
|
|
1064
1065
|
subpath: config.env.subpath,
|
|
1065
1066
|
useMinifiedFile: useMinifiedFile[role],
|
package/src/server/Socket.js
CHANGED
|
@@ -5,27 +5,27 @@ import {
|
|
|
5
5
|
PONG_MESSAGE,
|
|
6
6
|
} from '../common/constants.js';
|
|
7
7
|
import {
|
|
8
|
-
packBinaryMessage,
|
|
9
|
-
unpackBinaryMessage,
|
|
10
8
|
packStringMessage,
|
|
11
9
|
unpackStringMessage,
|
|
12
10
|
} from '../common/sockets-utils.js';
|
|
11
|
+
import {
|
|
12
|
+
kSocketsLatencyStatsWorker,
|
|
13
|
+
kSocketsDebugPreventHeartBeat,
|
|
14
|
+
kSocketsRemoveFromAllRooms,
|
|
15
|
+
} from './Sockets.js';
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// CONNECTING = 0;
|
|
17
|
-
// OPEN = 1;
|
|
18
|
-
// CLOSING = 2;
|
|
19
|
-
// CLOSED = 3;
|
|
20
|
-
// READY_STATES = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
|
|
17
|
+
export const kSocketClientId = Symbol('soundworks:socket-client-id');
|
|
18
|
+
export const kSocketTerminate = Symbol('soundworks:socket-terminate');
|
|
21
19
|
|
|
22
20
|
/**
|
|
23
|
-
*
|
|
24
|
-
* [ws](https://github.com/websockets/ws) library.
|
|
25
|
-
*
|
|
21
|
+
* Simple publish / subscribe wrapper built on top of the
|
|
22
|
+
* [ws](https://github.com/websockets/ws) library.
|
|
23
|
+
*
|
|
24
|
+
* An instance of {@link server.Socket} is automatically created per client
|
|
25
|
+
* when it connects (see {@link server.Client#socket}).
|
|
26
26
|
*
|
|
27
27
|
* _Important: In most cases, you should consider using a {@link client.SharedState}
|
|
28
|
-
* rather than directly using the
|
|
28
|
+
* rather than directly using the Socket instance._
|
|
29
29
|
*
|
|
30
30
|
* The Socket class contains two different WebSockets:
|
|
31
31
|
* - a socket configured with `binaryType = 'blob'` for JSON compatible data
|
|
@@ -37,22 +37,17 @@ import {
|
|
|
37
37
|
* @hideconstructor
|
|
38
38
|
*/
|
|
39
39
|
class Socket {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* @type {object}
|
|
45
|
-
* @private
|
|
46
|
-
*/
|
|
47
|
-
this.ws = ws;
|
|
40
|
+
#socket = null;
|
|
41
|
+
#sockets = null;
|
|
42
|
+
#listeners = new Map();
|
|
43
|
+
#heartbeatId = null;
|
|
48
44
|
|
|
45
|
+
constructor(ws, sockets) {
|
|
49
46
|
/**
|
|
50
|
-
* `ws` socket instance configured with `binaryType=
|
|
51
|
-
*
|
|
47
|
+
* `ws` socket instance configured with `binaryType=blob` (string)
|
|
52
48
|
* @type {object}
|
|
53
|
-
* @private
|
|
54
49
|
*/
|
|
55
|
-
this
|
|
50
|
+
this.#socket = ws;
|
|
56
51
|
|
|
57
52
|
/**
|
|
58
53
|
* Reference to the sockets object, is mainly dedicated to allow
|
|
@@ -62,28 +57,10 @@ class Socket {
|
|
|
62
57
|
* @example
|
|
63
58
|
* socket.sockets.broadcast('my-room', this, 'update-value', 1);
|
|
64
59
|
*/
|
|
65
|
-
this
|
|
60
|
+
this.#sockets = sockets;
|
|
66
61
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
*
|
|
70
|
-
* @type {Map}
|
|
71
|
-
* @private
|
|
72
|
-
*/
|
|
73
|
-
this.rooms = rooms;
|
|
74
|
-
|
|
75
|
-
/** @private */
|
|
76
|
-
this._stringListeners = new Map();
|
|
77
|
-
/** @private */
|
|
78
|
-
this._binaryListeners = new Map();
|
|
79
|
-
/** @private */
|
|
80
|
-
this._heartbeatId = null;
|
|
81
|
-
|
|
82
|
-
// heartbeat system (run only on string socket), adapted from:
|
|
83
|
-
// https://github.com/websockets/ws#how-to-detect-and-close-broken-connections
|
|
84
|
-
/** @private */
|
|
85
|
-
this._isAlive = true;
|
|
86
|
-
let msg = {
|
|
62
|
+
// @todo - review
|
|
63
|
+
const msg = {
|
|
87
64
|
type: 'add-measurement',
|
|
88
65
|
value: {
|
|
89
66
|
ping: 0,
|
|
@@ -91,43 +68,49 @@ class Socket {
|
|
|
91
68
|
},
|
|
92
69
|
};
|
|
93
70
|
|
|
94
|
-
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
this.ws.addEventListener('message', e => {
|
|
71
|
+
let heartbeatMissed = 0;
|
|
72
|
+
// heartbeat system (run only on string socket), adapted from:
|
|
73
|
+
// https://github.com/websockets/ws#how-to-detect-and-close-broken-connections
|
|
74
|
+
this.#socket.addEventListener('message', e => {
|
|
99
75
|
if (e.data === PONG_MESSAGE) {
|
|
100
|
-
|
|
76
|
+
heartbeatMissed = 0;
|
|
101
77
|
|
|
102
78
|
msg.value.pong = getTime();
|
|
103
|
-
this
|
|
79
|
+
this.#sockets[kSocketsLatencyStatsWorker].postMessage(msg);
|
|
104
80
|
// do not propagate ping / pong messages
|
|
105
81
|
return;
|
|
106
82
|
}
|
|
107
83
|
|
|
108
84
|
const [channel, args] = unpackStringMessage(e.data);
|
|
109
|
-
this
|
|
85
|
+
this.#dispatchEvent(channel, ...args);
|
|
110
86
|
});
|
|
111
87
|
|
|
112
|
-
this
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
88
|
+
this.#heartbeatId = setInterval(() => {
|
|
89
|
+
// we didn't receive the pong message
|
|
90
|
+
if (heartbeatMissed > 0) {
|
|
91
|
+
// Emit a 'close' event to go trough all the disconnection pipeline
|
|
92
|
+
//
|
|
93
|
+
// @note - this seems to create false positive disconnections when
|
|
94
|
+
// client is busy, e.g. when loading large sound files so let's just warn
|
|
95
|
+
// until we gather more feedback
|
|
96
|
+
// cf. https://making.close.com/posts/reliable-websockets/
|
|
97
|
+
console.warn(`[Socket] client (id: ${this[kSocketClientId]}) did not respond to ping message in time (missed: ${heartbeatMissed}, interval: ${PING_INTERVAL})`);
|
|
98
|
+
// this.#dispatchEvent('close');
|
|
99
|
+
// return;
|
|
117
100
|
}
|
|
118
101
|
|
|
119
|
-
|
|
102
|
+
heartbeatMissed += 1;
|
|
120
103
|
msg.value.ping = getTime();
|
|
121
104
|
|
|
122
|
-
this.
|
|
105
|
+
this.#socket.send(PING_MESSAGE);
|
|
123
106
|
}, PING_INTERVAL);
|
|
124
107
|
|
|
125
108
|
// for testing purpose
|
|
126
|
-
if (this
|
|
127
|
-
clearInterval(this
|
|
109
|
+
if (this.#sockets[kSocketsDebugPreventHeartBeat] === true) {
|
|
110
|
+
clearInterval(this.#heartbeatId);
|
|
128
111
|
}
|
|
129
112
|
|
|
130
|
-
//
|
|
113
|
+
// propagate "native" events
|
|
131
114
|
[
|
|
132
115
|
'close',
|
|
133
116
|
'error',
|
|
@@ -138,165 +121,69 @@ class Socket {
|
|
|
138
121
|
'unexpected-response',
|
|
139
122
|
'upgrade',
|
|
140
123
|
].forEach(eventName => {
|
|
141
|
-
this.
|
|
142
|
-
this
|
|
124
|
+
this.#socket.addEventListener(eventName, e => {
|
|
125
|
+
this.#dispatchEvent(eventName, e.data);
|
|
143
126
|
});
|
|
144
127
|
});
|
|
145
|
-
|
|
146
|
-
// ----------------------------------------------------------
|
|
147
|
-
// Binary socket
|
|
148
|
-
// ----------------------------------------------------------
|
|
149
|
-
this.binaryWs.addEventListener('message', e => {
|
|
150
|
-
const [channel, data] = unpackBinaryMessage(e.data);
|
|
151
|
-
this._emit(true, channel, data);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// broadcast all "native" events
|
|
155
|
-
[
|
|
156
|
-
'close',
|
|
157
|
-
'error',
|
|
158
|
-
'message',
|
|
159
|
-
'open',
|
|
160
|
-
'ping',
|
|
161
|
-
'pong',
|
|
162
|
-
'unexpected-response',
|
|
163
|
-
'upgrade',
|
|
164
|
-
].forEach(eventName => {
|
|
165
|
-
this.binaryWs.addEventListener(eventName, e => {
|
|
166
|
-
this._emit(true, eventName, e.data);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Removes all listeners and immediately close the two sockets. Is automatically
|
|
173
|
-
* called on `server.stop()`
|
|
174
|
-
*
|
|
175
|
-
* @private
|
|
176
|
-
*/
|
|
177
|
-
terminate() {
|
|
178
|
-
// clear ping/pong check
|
|
179
|
-
clearInterval(this._heartbeatId);
|
|
180
|
-
|
|
181
|
-
// clean rooms
|
|
182
|
-
for (let [_key, room] of this.rooms) {
|
|
183
|
-
room.delete(this);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// clear references to sockets and rooms
|
|
187
|
-
this.sockets = null;
|
|
188
|
-
this.rooms = null;
|
|
189
|
-
|
|
190
|
-
// clear all listeners
|
|
191
|
-
this._stringListeners.clear();
|
|
192
|
-
this._binaryListeners.clear();
|
|
193
|
-
|
|
194
|
-
// clear "native" listeners
|
|
195
|
-
[this.binaryWs, this.ws].forEach((socket) => {
|
|
196
|
-
[
|
|
197
|
-
'close',
|
|
198
|
-
'error',
|
|
199
|
-
'message',
|
|
200
|
-
'open',
|
|
201
|
-
'ping',
|
|
202
|
-
'pong',
|
|
203
|
-
'unexpected-response',
|
|
204
|
-
'upgrade',
|
|
205
|
-
].forEach(eventName => {
|
|
206
|
-
socket.removeAllListeners(eventName);
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
// clear binarySocket as this is called from the string one.
|
|
211
|
-
this.binaryWs.terminate();
|
|
212
|
-
this.ws.terminate();
|
|
213
128
|
}
|
|
214
129
|
|
|
215
130
|
/**
|
|
216
|
-
*
|
|
131
|
+
* Dipatch an event to the listeners of the given channel.
|
|
217
132
|
* @param {string} channel - Channel name.
|
|
218
133
|
* @param {...*} args - Content of the message.
|
|
219
|
-
* @private
|
|
220
134
|
*/
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (listeners.has(channel)) {
|
|
225
|
-
const callbacks = listeners.get(channel);
|
|
135
|
+
#dispatchEvent(channel, ...args) {
|
|
136
|
+
if (this.#listeners.has(channel)) {
|
|
137
|
+
const callbacks = this.#listeners.get(channel);
|
|
226
138
|
callbacks.forEach(callback => callback(...args));
|
|
227
139
|
}
|
|
228
140
|
}
|
|
229
141
|
|
|
230
142
|
/**
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*/
|
|
236
|
-
_addListener(listeners, channel, callback) {
|
|
237
|
-
if (!listeners.has(channel)) {
|
|
238
|
-
listeners.set(channel, new Set());
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const callbacks = listeners.get(channel);
|
|
242
|
-
callbacks.add(callback);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* @param {Function[]} listeners - List of listeners, either for the string or binary socket.
|
|
247
|
-
* @param {string} channel - Channel name.
|
|
248
|
-
* @param {Function} callback - The function to be removed from the listeners.
|
|
249
|
-
* @private
|
|
250
|
-
*/
|
|
251
|
-
_removeListener(listeners, channel, callback) {
|
|
252
|
-
if (listeners.has(channel)) {
|
|
253
|
-
const callbacks = listeners.get(channel);
|
|
254
|
-
callbacks.delete(callback);
|
|
255
|
-
|
|
256
|
-
if (callbacks.size === 0) {
|
|
257
|
-
listeners.delete(channel);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* @param {Function[]} listeners - List of listeners, either for the string or binary socket.
|
|
264
|
-
* @param {string} [channel=null] - Channel name of the listeners to remove. If null
|
|
265
|
-
* all the listeners are cleared.
|
|
266
|
-
* @private
|
|
143
|
+
* Removes all listeners and immediately close the web socket.
|
|
144
|
+
*
|
|
145
|
+
* Is automatically called when socket is closed on the client side or when
|
|
146
|
+
* Server is stopped
|
|
267
147
|
*/
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
148
|
+
[kSocketTerminate]() {
|
|
149
|
+
// clear ping / pong interval
|
|
150
|
+
clearInterval(this.#heartbeatId);
|
|
151
|
+
// remove socket from all rooms
|
|
152
|
+
this.#sockets[kSocketsRemoveFromAllRooms](this);
|
|
153
|
+
// clear references to sockets
|
|
154
|
+
this.#sockets = null;
|
|
155
|
+
// clear all listeners
|
|
156
|
+
this.#listeners.clear();
|
|
157
|
+
// clear native listeners
|
|
158
|
+
[
|
|
159
|
+
'close',
|
|
160
|
+
'error',
|
|
161
|
+
'message',
|
|
162
|
+
'open',
|
|
163
|
+
'ping',
|
|
164
|
+
'pong',
|
|
165
|
+
'unexpected-response',
|
|
166
|
+
'upgrade',
|
|
167
|
+
].forEach(eventName => this.#socket.removeAllListeners(eventName));
|
|
168
|
+
// terminate the socket
|
|
169
|
+
this.#socket.terminate();
|
|
274
170
|
}
|
|
275
171
|
|
|
276
172
|
/**
|
|
277
|
-
*
|
|
173
|
+
* Reference to the @link{server.Sockets} instance.
|
|
278
174
|
*
|
|
279
|
-
*
|
|
175
|
+
* Allows for broadcasting from a given socket instance.
|
|
176
|
+
*
|
|
177
|
+
* @type {server.Sockets}
|
|
178
|
+
* @example
|
|
179
|
+
* socket.sockets.broadcast('my-room', this, 'update-value', 1);
|
|
280
180
|
*/
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
this.rooms.set(roomId, new Set());
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const room = this.rooms.get(roomId);
|
|
287
|
-
room.add(this);
|
|
181
|
+
get sockets() {
|
|
182
|
+
return this.#sockets;
|
|
288
183
|
}
|
|
289
184
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
*
|
|
293
|
-
* @param {string} roomId - Id of the room.
|
|
294
|
-
*/
|
|
295
|
-
removeFromRoom(roomId) {
|
|
296
|
-
if (this.rooms.has(roomId)) {
|
|
297
|
-
const room = this.rooms.get(roomId);
|
|
298
|
-
room.delete(this);
|
|
299
|
-
}
|
|
185
|
+
get readyState() {
|
|
186
|
+
return this.#socket.readyState;
|
|
300
187
|
}
|
|
301
188
|
|
|
302
189
|
/**
|
|
@@ -309,14 +196,15 @@ class Socket {
|
|
|
309
196
|
send(channel, ...args) {
|
|
310
197
|
const msg = packStringMessage(channel, ...args);
|
|
311
198
|
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
199
|
+
// ## Status codes:
|
|
200
|
+
// CONNECTING = 0; Socket has been created. The connection is not yet open.
|
|
201
|
+
// OPEN = 1; The connection is open and ready to communicate.
|
|
202
|
+
// CLOSING = 2; The connection is in the process of closing.
|
|
203
|
+
// CLOSED = 3; The connection is closed or couldn't be opened.
|
|
204
|
+
if (this.#socket.readyState === 1) {
|
|
205
|
+
this.#socket.send(msg, (err) => {
|
|
318
206
|
if (err) {
|
|
319
|
-
console.error('error sending msg:', channel, args, err);
|
|
207
|
+
console.error('[Socket] error sending msg:', channel, args, err.message);
|
|
320
208
|
}
|
|
321
209
|
});
|
|
322
210
|
}
|
|
@@ -326,12 +214,17 @@ class Socket {
|
|
|
326
214
|
* Listen messages with JSON compatible data types on a given channel.
|
|
327
215
|
*
|
|
328
216
|
* @param {string} channel - Channel name.
|
|
329
|
-
* @param {Function} callback - Callback to execute when a message is received
|
|
330
|
-
*
|
|
217
|
+
* @param {Function} callback - Callback to execute when a message is received.
|
|
218
|
+
* Arguments of the callback function will match the arguments sent using the
|
|
331
219
|
* {@link server.Socket#send} method.
|
|
332
220
|
*/
|
|
333
221
|
addListener(channel, callback) {
|
|
334
|
-
this.
|
|
222
|
+
if (!this.#listeners.has(channel)) {
|
|
223
|
+
this.#listeners.set(channel, new Set());
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const callbacks = this.#listeners.get(channel);
|
|
227
|
+
callbacks.add(callback);
|
|
335
228
|
}
|
|
336
229
|
|
|
337
230
|
/**
|
|
@@ -341,7 +234,14 @@ class Socket {
|
|
|
341
234
|
* @param {Function} callback - Callback to remove.
|
|
342
235
|
*/
|
|
343
236
|
removeListener(channel, callback) {
|
|
344
|
-
this.
|
|
237
|
+
if (this.#listeners.has(channel)) {
|
|
238
|
+
const callbacks = this.#listeners.get(channel);
|
|
239
|
+
callbacks.delete(callback);
|
|
240
|
+
|
|
241
|
+
if (callbacks.size === 0) {
|
|
242
|
+
this.#listeners.delete(channel);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
345
245
|
}
|
|
346
246
|
|
|
347
247
|
/**
|
|
@@ -350,53 +250,29 @@ class Socket {
|
|
|
350
250
|
* @param {string} channel - Channel name.
|
|
351
251
|
*/
|
|
352
252
|
removeAllListeners(channel = null) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
*
|
|
359
|
-
* @param {string} channel - Channel name.
|
|
360
|
-
* @param {TypedArray} typedArray - Binary data to be sent.
|
|
361
|
-
*/
|
|
362
|
-
sendBinary(channel, typedArray) {
|
|
363
|
-
const msg = packBinaryMessage(channel, typedArray);
|
|
364
|
-
|
|
365
|
-
this.binaryWs.send(msg, (err) => {
|
|
366
|
-
if (err) {
|
|
367
|
-
console.error('error sending msg:', channel, typedArray);
|
|
368
|
-
}
|
|
369
|
-
});
|
|
253
|
+
if (channel === null) {
|
|
254
|
+
this.#listeners.clear();
|
|
255
|
+
} else if (this.#listeners.has(channel)) {
|
|
256
|
+
this.#listeners.delete(channel);
|
|
257
|
+
}
|
|
370
258
|
}
|
|
371
259
|
|
|
372
260
|
/**
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
* @param {string} channel - Channel name.
|
|
376
|
-
* @param {Function} callback - Callback to execute when a message is received.
|
|
261
|
+
* Add the socket to a room
|
|
262
|
+
* @param {string} roomId - Id of the room.
|
|
377
263
|
*/
|
|
378
|
-
|
|
379
|
-
this.
|
|
264
|
+
addToRoom(roomId) {
|
|
265
|
+
this.sockets.addToRoom(this, roomId);
|
|
380
266
|
}
|
|
381
267
|
|
|
382
268
|
/**
|
|
383
|
-
* Remove
|
|
384
|
-
*
|
|
385
|
-
* @param {string} channel - Channel name.
|
|
386
|
-
* @param {Function} callback - Callback to remove.
|
|
269
|
+
* Remove the socket from a room
|
|
270
|
+
* @param {string} roomId - Id of the room.
|
|
387
271
|
*/
|
|
388
|
-
|
|
389
|
-
this.
|
|
272
|
+
removeFromRoom(roomId) {
|
|
273
|
+
this.sockets.removeFromRoom(this, roomId);
|
|
390
274
|
}
|
|
391
275
|
|
|
392
|
-
/**
|
|
393
|
-
* Remove all listeners of binary compatible messages on a given channel
|
|
394
|
-
*
|
|
395
|
-
* @param {string} channel - Channel of the message.
|
|
396
|
-
*/
|
|
397
|
-
removeAllBinaryListeners(channel = null) {
|
|
398
|
-
this._removeAllListeners(this._binaryListeners, channel);
|
|
399
|
-
}
|
|
400
276
|
}
|
|
401
277
|
|
|
402
278
|
export default Socket;
|