@soundworks/core 3.6.0 → 4.0.0-alpha.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/.eslintrc +16 -0
- package/LICENSE +1 -1
- package/README.md +32 -30
- package/package.json +57 -26
- package/src/client/Client.js +391 -0
- package/src/client/Context.js +225 -0
- package/src/client/ContextManager.js +111 -0
- package/src/client/Plugin.js +66 -0
- package/src/client/PluginManager.js +123 -0
- package/src/client/SharedState.js +84 -0
- package/src/client/Socket.js +389 -0
- package/src/client/StateManager.js +67 -0
- package/src/client/index.js +77 -0
- package/src/common/BasePlugin.js +163 -0
- package/src/common/BasePluginManager.js +227 -0
- package/src/common/BaseSharedState.js +473 -0
- package/src/common/BaseStateManager.js +200 -0
- package/{common → src/common}/ParameterBag.js +82 -122
- package/{common → src/common}/SharedStatePrivate.js +56 -64
- package/src/common/constants.js +51 -0
- package/src/common/logger.js +154 -0
- package/src/common/promise-store.js +25 -0
- package/src/common/sockets-utils.js +73 -0
- package/src/common/utils.js +22 -0
- package/src/server/Client.js +50 -0
- package/src/server/Context.js +99 -0
- package/src/server/ContextManager.js +266 -0
- package/src/server/Plugin.js +77 -0
- package/src/server/PluginManager.js +150 -0
- package/src/server/Server.js +849 -0
- package/src/server/SharedState.js +86 -0
- package/{server → src/server}/Socket.js +119 -94
- package/{server → src/server}/Sockets.js +73 -61
- package/{common/SharedStateManagerServer.js → src/server/StateManager.js} +183 -131
- package/src/server/audit-network-latency.worker.js +57 -0
- package/src/server/audit-schema.js +42 -0
- package/src/server/index.js +45 -0
- package/tsconfig.json +21 -0
- package/types/client/Client.d.ts +194 -0
- package/types/client/Client.d.ts.map +1 -0
- package/types/client/Context.d.ts +139 -0
- package/types/client/Context.d.ts.map +1 -0
- package/types/client/ContextManager.d.ts +45 -0
- package/types/client/ContextManager.d.ts.map +1 -0
- package/types/client/Plugin.d.ts +53 -0
- package/types/client/Plugin.d.ts.map +1 -0
- package/types/client/PluginManager.d.ts +96 -0
- package/types/client/PluginManager.d.ts.map +1 -0
- package/types/client/SharedState.d.ts +85 -0
- package/types/client/SharedState.d.ts.map +1 -0
- package/types/client/Socket.d.ts +138 -0
- package/types/client/Socket.d.ts.map +1 -0
- package/types/client/StateManager.d.ts +72 -0
- package/types/client/StateManager.d.ts.map +1 -0
- package/types/client/index.d.ts +11 -0
- package/types/client/index.d.ts.map +1 -0
- package/types/common/BasePlugin.d.ts +145 -0
- package/types/common/BasePlugin.d.ts.map +1 -0
- package/types/common/BasePluginManager.d.ts +88 -0
- package/types/common/BasePluginManager.d.ts.map +1 -0
- package/types/common/BaseSharedState.d.ts +173 -0
- package/types/common/BaseSharedState.d.ts.map +1 -0
- package/types/common/BaseStateManager.d.ts +70 -0
- package/types/common/BaseStateManager.d.ts.map +1 -0
- package/types/common/ParameterBag.d.ts +143 -0
- package/types/common/ParameterBag.d.ts.map +1 -0
- package/types/common/SharedStatePrivate.d.ts +20 -0
- package/types/common/SharedStatePrivate.d.ts.map +1 -0
- package/types/common/constants.d.ts +35 -0
- package/types/common/constants.d.ts.map +1 -0
- package/types/common/logger.d.ts +16 -0
- package/types/common/logger.d.ts.map +1 -0
- package/types/common/promise-store.d.ts +4 -0
- package/types/common/promise-store.d.ts.map +1 -0
- package/types/common/sockets-utils.d.ts +9 -0
- package/types/common/sockets-utils.d.ts.map +1 -0
- package/types/common/utils.d.ts +5 -0
- package/types/common/utils.d.ts.map +1 -0
- package/types/server/Client.d.ts +40 -0
- package/types/server/Client.d.ts.map +1 -0
- package/types/server/Context.d.ts +59 -0
- package/types/server/Context.d.ts.map +1 -0
- package/types/server/ContextManager.d.ts +69 -0
- package/types/server/ContextManager.d.ts.map +1 -0
- package/types/server/Plugin.d.ts +70 -0
- package/types/server/Plugin.d.ts.map +1 -0
- package/types/server/PluginManager.d.ts +91 -0
- package/types/server/PluginManager.d.ts.map +1 -0
- package/types/server/Server.d.ts +223 -0
- package/types/server/Server.d.ts.map +1 -0
- package/types/server/SharedState.d.ts +86 -0
- package/types/server/SharedState.d.ts.map +1 -0
- package/types/server/Socket.d.ts +128 -0
- package/types/server/Socket.d.ts.map +1 -0
- package/types/server/Sockets.d.ts +73 -0
- package/types/server/Sockets.d.ts.map +1 -0
- package/types/server/StateManager.d.ts +190 -0
- package/types/server/StateManager.d.ts.map +1 -0
- package/types/server/audit-network-latency.worker.d.ts +2 -0
- package/types/server/audit-network-latency.worker.d.ts.map +1 -0
- package/types/server/audit-schema.d.ts +27 -0
- package/types/server/audit-schema.d.ts.map +1 -0
- package/types/server/index.d.ts +7 -0
- package/types/server/index.d.ts.map +1 -0
- package/client/AbstractExperience.js +0 -54
- package/client/AbstractPlugin.js +0 -230
- package/client/Client.js +0 -190
- package/client/PluginManager.js +0 -267
- package/client/Socket.js +0 -338
- package/client/index.js +0 -48
- package/client.js +0 -28
- package/common/SharedState.js +0 -480
- package/common/SharedStateManagerClient.js +0 -194
- package/common/Signal.js +0 -40
- package/common/SignalAll.js +0 -44
- package/common/shared-state-utils.js +0 -90
- package/common/sockets-encoder-decoder.js +0 -73
- package/server/AbstractExperience.js +0 -126
- package/server/AbstractPlugin.js +0 -211
- package/server/Client.js +0 -70
- package/server/PluginManager.js +0 -177
- package/server/Server.js +0 -746
- package/server/index.js +0 -49
- package/server/utils/logger.js +0 -134
- package/server.js +0 -28
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import WebSocket from 'isomorphic-ws';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
packBinaryMessage,
|
|
5
|
+
unpackBinaryMessage,
|
|
6
|
+
packStringMessage,
|
|
7
|
+
unpackStringMessage,
|
|
8
|
+
} from '../common/sockets-utils.js';
|
|
9
|
+
import logger from '../common/logger.js';
|
|
10
|
+
import { isBrowser } from '../common/utils.js';
|
|
11
|
+
|
|
12
|
+
// WebSocket events:
|
|
13
|
+
//
|
|
14
|
+
// `close`:
|
|
15
|
+
// Fired when a connection with a websocket is closed.
|
|
16
|
+
// Also available via the onclose property
|
|
17
|
+
// `error`:
|
|
18
|
+
// Fired when a connection with a websocket has been closed because of an error,
|
|
19
|
+
// such as whensome data couldn't be sent.
|
|
20
|
+
// Also available via the onerror property.
|
|
21
|
+
// `message`:
|
|
22
|
+
// Fired when data is received through a websocket.
|
|
23
|
+
// Also available via the onmessage property.
|
|
24
|
+
// `open`:
|
|
25
|
+
// Fired when a connection with a websocket is opened.
|
|
26
|
+
// Also available via the onopen property.
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The Socket class is a simple publish / subscribe wrapper built on top of the
|
|
30
|
+
* [isomorphic-ws](https://github.com/heineiuo/isomorphic-ws) library.
|
|
31
|
+
* An instance of `Socket` is automatically created by the `soundworks.Client`.
|
|
32
|
+
*
|
|
33
|
+
* _Important: In most cases, you should consider using a {@link client.SharedState} rather than
|
|
34
|
+
* sending messages directly through the sockets._
|
|
35
|
+
*
|
|
36
|
+
* The Socket class concurrently opens two different WebSockets:
|
|
37
|
+
* - a socket configured with `binaryType = 'string'` for JSON compatible string
|
|
38
|
+
* messages.
|
|
39
|
+
* - a socket configured with `binaryType=arraybuffer` for efficient streaming
|
|
40
|
+
* of binary data.
|
|
41
|
+
*
|
|
42
|
+
* Both sockets re-emits all "native" ws events ('open', 'upgrade', 'close', 'error'
|
|
43
|
+
* and 'message'.
|
|
44
|
+
*
|
|
45
|
+
* See {@link client.Client#socket}
|
|
46
|
+
*
|
|
47
|
+
* @memberof client
|
|
48
|
+
* @hideconstructor
|
|
49
|
+
*/
|
|
50
|
+
class Socket {
|
|
51
|
+
constructor() {
|
|
52
|
+
/**
|
|
53
|
+
* WebSocket instance w/ string protocol, i.e. `binaryType = 'string'`.
|
|
54
|
+
*
|
|
55
|
+
* @private
|
|
56
|
+
*/
|
|
57
|
+
this.ws = null;
|
|
58
|
+
/**
|
|
59
|
+
* WebSocket instance w/ binary protocol, i.e. `binaryType = 'arraybuffer'`.
|
|
60
|
+
*
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
this.binaryWs = null;
|
|
64
|
+
/** @private */
|
|
65
|
+
this._stringListeners = new Map();
|
|
66
|
+
/** @private */
|
|
67
|
+
this._binaryListeners = new Map();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Initialize a websocket connection with the server. Automatically called
|
|
72
|
+
* during `client.init()`
|
|
73
|
+
*
|
|
74
|
+
* @param {string} role - Role of the client (see {@link client.Client#role})
|
|
75
|
+
* @param {object} config - Configuration of the sockets
|
|
76
|
+
* @protected
|
|
77
|
+
* @ignore
|
|
78
|
+
*/
|
|
79
|
+
async init(role, config) {
|
|
80
|
+
// unique key that allows to associate the two sockets to the same client.
|
|
81
|
+
// note: the key is only used to pair to two sockets, so its usage is very
|
|
82
|
+
// limited in time therefore a random number should hopefully be sufficient.
|
|
83
|
+
const key = (Math.random() + '').replace(/^0\./, '');
|
|
84
|
+
|
|
85
|
+
// open web sockets
|
|
86
|
+
let { path } = config.env.websockets;
|
|
87
|
+
|
|
88
|
+
// @note: keep this check
|
|
89
|
+
// backward compatibility w/ assetsDomain and websocket old config way
|
|
90
|
+
// cf. https://github.com/collective-soundworks/soundworks/issues/35
|
|
91
|
+
if (config.env.subpath) {
|
|
92
|
+
path = `${config.env.subpath}/${path}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let url;
|
|
96
|
+
let webSocketOptions;
|
|
97
|
+
|
|
98
|
+
if (isBrowser()) {
|
|
99
|
+
const protocol = window.location.protocol.replace(/^http?/, 'ws');
|
|
100
|
+
const { hostname, port } = window.location;
|
|
101
|
+
|
|
102
|
+
url = `${protocol}//${hostname}:${port}/${path}`;
|
|
103
|
+
webSocketOptions = [];
|
|
104
|
+
} else {
|
|
105
|
+
const protocol = config.env.useHttps ? 'wss:' : 'ws:';
|
|
106
|
+
const { serverAddress, port } = config.env;
|
|
107
|
+
|
|
108
|
+
url = `${protocol}//${serverAddress}:${port}/${path}`;
|
|
109
|
+
webSocketOptions = {
|
|
110
|
+
rejectUnauthorized: false,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const queryParams = `role=${role}&key=${key}`;
|
|
115
|
+
|
|
116
|
+
// ----------------------------------------------------------
|
|
117
|
+
// init string socket
|
|
118
|
+
// ----------------------------------------------------------
|
|
119
|
+
const stringSocketUrl = `${url}?binary=0&${queryParams}`;
|
|
120
|
+
|
|
121
|
+
await new Promise(resolve => {
|
|
122
|
+
let connectionRefusedLogged = false;
|
|
123
|
+
|
|
124
|
+
const trySocket = async () => {
|
|
125
|
+
const ws = new WebSocket(stringSocketUrl, webSocketOptions);
|
|
126
|
+
|
|
127
|
+
ws.addEventListener('open', connectEvent => {
|
|
128
|
+
// parse incoming messages for pubsub
|
|
129
|
+
this.ws = ws;
|
|
130
|
+
this.ws.addEventListener('message', e => {
|
|
131
|
+
const [channel, args] = unpackStringMessage(e.data);
|
|
132
|
+
this._emit(false, channel, ...args);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// broadcast all `WebSocket` native events
|
|
136
|
+
['close', 'error', 'upgrade', 'message'].forEach(eventName => {
|
|
137
|
+
this.ws.addEventListener(eventName, (e) => {
|
|
138
|
+
this._emit(false, eventName, e);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// forward open event
|
|
143
|
+
this._emit(false, 'open', connectEvent);
|
|
144
|
+
// continue with raw socket
|
|
145
|
+
resolve();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// cf. https://github.com/collective-soundworks/soundworks/issues/17
|
|
149
|
+
ws.addEventListener('error', e => {
|
|
150
|
+
if (e.type === 'error' && e.error.code === 'ECONNREFUSED') {
|
|
151
|
+
if (!connectionRefusedLogged) {
|
|
152
|
+
logger.log('[soundworks.Socket] Connection refused, waiting for the server to start');
|
|
153
|
+
connectionRefusedLogged = true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (ws.terminate) {
|
|
157
|
+
ws.terminate();
|
|
158
|
+
} else {
|
|
159
|
+
ws.close();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
setTimeout(trySocket, 1000);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
trySocket();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// @todo - review/fix
|
|
171
|
+
// - the `ws.on` method only exists on node implementation, and the 'ping'
|
|
172
|
+
// message is not received on addEventListener
|
|
173
|
+
// - there seems to be no way to access the ping event in browsers...
|
|
174
|
+
//
|
|
175
|
+
// let pingTimeoutId = null;
|
|
176
|
+
// const pingInterval = config.env.websockets.pingInterval;
|
|
177
|
+
// // detect broken connection
|
|
178
|
+
// // cf. https://github.com/websockets/ws#how-to-detect-and-close-broken-connections
|
|
179
|
+
// const heartbeat = () => {
|
|
180
|
+
// try {
|
|
181
|
+
// console.log('ping received');
|
|
182
|
+
// clearTimeout(pingTimeoutId);
|
|
183
|
+
|
|
184
|
+
// // pingTimeoutId = setTimeout(() => {
|
|
185
|
+
// // console.log('terminate');
|
|
186
|
+
// // this.terminate();
|
|
187
|
+
// // }, pingInterval + 2000);
|
|
188
|
+
// } catch (err) {
|
|
189
|
+
// console.error(err);
|
|
190
|
+
// }
|
|
191
|
+
// };
|
|
192
|
+
//
|
|
193
|
+
// this.ws.on('ping', heartbeat);
|
|
194
|
+
// this.ws.addEventListener('close', () => {
|
|
195
|
+
// clearTimeout(pingTimeoutId);
|
|
196
|
+
// });
|
|
197
|
+
|
|
198
|
+
// heartbeat();
|
|
199
|
+
|
|
200
|
+
// ----------------------------------------------------------
|
|
201
|
+
// init binary socket
|
|
202
|
+
// ----------------------------------------------------------
|
|
203
|
+
const binarySocketUrl = `${url}?binary=1&${queryParams}`;
|
|
204
|
+
|
|
205
|
+
await new Promise(resolve => {
|
|
206
|
+
const ws = new WebSocket(binarySocketUrl, webSocketOptions);
|
|
207
|
+
ws.binaryType = 'arraybuffer';
|
|
208
|
+
|
|
209
|
+
ws.addEventListener('open', connectEvent => {
|
|
210
|
+
// parse incoming messages for pubsub
|
|
211
|
+
this.binaryWs = ws;
|
|
212
|
+
this.binaryWs.addEventListener('message', e => {
|
|
213
|
+
const [channel, data] = unpackBinaryMessage(e.data);
|
|
214
|
+
this._emit(true, channel, data);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// broadcast all `WebSocket` native events
|
|
218
|
+
['close', 'error', 'upgrade', 'message'].forEach(eventName => {
|
|
219
|
+
this.binaryWs.addEventListener(eventName, (e) => {
|
|
220
|
+
this._emit(true, eventName, e);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// forward open event
|
|
225
|
+
this._emit(true, 'open', connectEvent);
|
|
226
|
+
resolve();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// wait for both sockets connected
|
|
231
|
+
return Promise.resolve();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @param {boolean} binary - Emit to either the string or binary socket.
|
|
236
|
+
* @param {string} channel - Channel name.
|
|
237
|
+
* @param {...*} args - Content of the message.
|
|
238
|
+
* @private
|
|
239
|
+
*/
|
|
240
|
+
_emit(binary, channel, ...args) {
|
|
241
|
+
const listeners = binary ? this._binaryListeners : this._stringListeners;
|
|
242
|
+
|
|
243
|
+
if (listeners.has(channel)) {
|
|
244
|
+
const callbacks = listeners.get(channel);
|
|
245
|
+
callbacks.forEach(callback => callback(...args));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* @param {Function[]} listeners - List of listeners, either for the string or binary socket.
|
|
251
|
+
* @param {string} channel - Channel name.
|
|
252
|
+
* @param {Function} callback - The function to be added to the listeners.
|
|
253
|
+
* @private
|
|
254
|
+
*/
|
|
255
|
+
_addListener(listeners, channel, callback) {
|
|
256
|
+
if (!listeners.has(channel)) {
|
|
257
|
+
listeners.set(channel, new Set());
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const callbacks = listeners.get(channel);
|
|
261
|
+
callbacks.add(callback);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* @param {Function[]} listeners - List of listeners, either for the string or binary socket.
|
|
266
|
+
* @param {string} channel - Channel name.
|
|
267
|
+
* @param {Function} callback - The function to be removed from the listeners.
|
|
268
|
+
* @private
|
|
269
|
+
*/
|
|
270
|
+
_removeListener(listeners, channel, callback) {
|
|
271
|
+
if (listeners.has(channel)) {
|
|
272
|
+
const callbacks = listeners.get(channel);
|
|
273
|
+
callbacks.delete(callback);
|
|
274
|
+
|
|
275
|
+
if (callbacks.size === 0) {
|
|
276
|
+
listeners.delete(channel);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* @param {Function[]} listeners - List of listeners, either for the string or binary socket.
|
|
283
|
+
* @param {string} [channel=null] - Channel name of the listeners to remove. If null
|
|
284
|
+
* all the listeners are cleared.
|
|
285
|
+
* @private
|
|
286
|
+
*/
|
|
287
|
+
_removeAllListeners(listeners, channel = null) {
|
|
288
|
+
if (channel === null) {
|
|
289
|
+
listeners.clear();
|
|
290
|
+
} else if (listeners.has(channel)) {
|
|
291
|
+
listeners.delete(channel);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Send JSON compatible messages on a given channel.
|
|
297
|
+
*
|
|
298
|
+
* @param {string} channel - Channel name of the message.
|
|
299
|
+
* @param {...*} args - Message list, as many as needed, of any serializable type).
|
|
300
|
+
*/
|
|
301
|
+
send(channel, ...args) {
|
|
302
|
+
const msg = packStringMessage(channel, ...args);
|
|
303
|
+
this.ws.send(msg);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Listen to JSON compatible messages on a given channel.
|
|
308
|
+
*
|
|
309
|
+
* @param {string} channel - Channel name of the message.
|
|
310
|
+
* @param {Function} callback - Callback to execute when a message is received.
|
|
311
|
+
*/
|
|
312
|
+
addListener(channel, callback) {
|
|
313
|
+
this._addListener(this._stringListeners, channel, callback);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Remove a listener from JSON compatible messages on a given channel.
|
|
318
|
+
*
|
|
319
|
+
* @param {string} channel - Channel name of the message.
|
|
320
|
+
* @param {Function} callback - Callback to remove.
|
|
321
|
+
*/
|
|
322
|
+
removeListener(channel, callback) {
|
|
323
|
+
this._removeListener(this._stringListeners, channel, callback);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Remove all listeners from JSON compatible messages on a given channel.
|
|
328
|
+
*
|
|
329
|
+
* @param {string} channel - Channel name of the message.
|
|
330
|
+
*/
|
|
331
|
+
removeAllListeners(channel = null) {
|
|
332
|
+
this._removeAllListeners(this._stringListeners, channel);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Send binary messages on a given channel.
|
|
337
|
+
*
|
|
338
|
+
* @param {string} channel - Channel name of the message.
|
|
339
|
+
* @param {TypedArray} typedArray - Binary data to be sent.
|
|
340
|
+
*/
|
|
341
|
+
sendBinary(channel, typedArray) {
|
|
342
|
+
const msg = packBinaryMessage(channel, typedArray);
|
|
343
|
+
this.binaryWs.send(msg);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Listen binary messages on a given channel.
|
|
348
|
+
*
|
|
349
|
+
* @param {string} channel - Channel name of the message.
|
|
350
|
+
* @param {Function} callback - Callback to execute when a message is received.
|
|
351
|
+
*/
|
|
352
|
+
addBinaryListener(channel, callback) {
|
|
353
|
+
this._addListener(this._binaryListeners, channel, callback);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Remove a listener from binary compatible messages on a given channel
|
|
358
|
+
*
|
|
359
|
+
* @param {string} channel - Channel of the message.
|
|
360
|
+
* @param {Function} callback - Callback to cancel.
|
|
361
|
+
*/
|
|
362
|
+
removeBinaryListener(channel, callback) {
|
|
363
|
+
this._removeListener(this._binaryListeners, channel, callback);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Remove all listeners from binary compatible messages on a given channel
|
|
368
|
+
*
|
|
369
|
+
* @param {string} channel - Channel of the message.
|
|
370
|
+
*/
|
|
371
|
+
removeAllBinaryListeners(channel = null) {
|
|
372
|
+
this._removeAllListeners(this._binaryListeners, channel);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Removes all listeners and immediately close the two sockets.
|
|
377
|
+
*/
|
|
378
|
+
async terminate() {
|
|
379
|
+
this.removeAllListeners();
|
|
380
|
+
this.removeAllBinaryListeners();
|
|
381
|
+
|
|
382
|
+
this.ws.close();
|
|
383
|
+
this.binaryWs.close();
|
|
384
|
+
|
|
385
|
+
return Promise.resolve();
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export default Socket;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import BaseStateManager from '../common/BaseStateManager.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @callback client.StateManager~ObserveCallback
|
|
5
|
+
* @async
|
|
6
|
+
* @param {String} schemaName - name of the schema
|
|
7
|
+
* @param {Number} stateId - id of the state
|
|
8
|
+
* @param {Number} nodeId - id of the node that created the state
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The `StateManager` allows to create new {@link client.SharedState}s, or attach
|
|
13
|
+
* to {@link client.SharedState}s created by other nodes (clients or server). It
|
|
14
|
+
* can also track all the {@link client.SharedState}s created by other nodes.
|
|
15
|
+
*
|
|
16
|
+
* An instance of `StateManager` is automatically created by the `soundworks.Client`
|
|
17
|
+
* at initialization (cf. {@link client.Client#stateManager}).
|
|
18
|
+
*
|
|
19
|
+
* See {@link client.Client#stateManager}
|
|
20
|
+
*
|
|
21
|
+
* Tutorial: {@link https://soundworks.dev/guide/state-manager.html}
|
|
22
|
+
*
|
|
23
|
+
* ```
|
|
24
|
+
* // server-side
|
|
25
|
+
* import { Server } from '@soundworks/server/index.js';
|
|
26
|
+
*
|
|
27
|
+
* const server = new Server(config);
|
|
28
|
+
* // declare and register the schema of a shared state.
|
|
29
|
+
* server.stateManager.registerSchema('some-global-state', {
|
|
30
|
+
* myRandom: {
|
|
31
|
+
* type: 'float',
|
|
32
|
+
* default: 0,
|
|
33
|
+
* }
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* await server.start();
|
|
37
|
+
*
|
|
38
|
+
* // create a global state server-side
|
|
39
|
+
* const globalState = await server.stateManager.create('some-global-state');
|
|
40
|
+
* // listen and react to the changes made by the clients
|
|
41
|
+
* globalState.onUpdate(updates => console.log(updates));
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* ```
|
|
45
|
+
* // client-side
|
|
46
|
+
* import { Client } from '@soundworks/client.index.js';
|
|
47
|
+
*
|
|
48
|
+
* const client = new Client(config);
|
|
49
|
+
* await client.start();
|
|
50
|
+
*
|
|
51
|
+
* // attach to the global state created by the server
|
|
52
|
+
* const globalState = await client.stateManager.attach('some-global-state');
|
|
53
|
+
*
|
|
54
|
+
* // update the value of a `myRandom` parameter every seconds
|
|
55
|
+
* setInterval(() => {
|
|
56
|
+
* globalState.set({ myRandom: Math.random() });
|
|
57
|
+
* }, 1000);
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @memberof client
|
|
61
|
+
* @extends BaseStateManager
|
|
62
|
+
* @inheritdoc
|
|
63
|
+
* @hideconstructor
|
|
64
|
+
*/
|
|
65
|
+
class StateManager extends BaseStateManager {}
|
|
66
|
+
|
|
67
|
+
export default StateManager;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration object for a client running in a browser runtime.
|
|
3
|
+
*
|
|
4
|
+
* @typedef BrowserClientConfig
|
|
5
|
+
* @memberof client
|
|
6
|
+
* @type {object}
|
|
7
|
+
* @property {string} role - Role of the client in the application (e.g. 'player', 'controller').
|
|
8
|
+
* @property {object} [app] - Application configration object.
|
|
9
|
+
* @property {string} [app.name=''] - Name of the application.
|
|
10
|
+
* @property {string} [app.author=''] - Name of the author.
|
|
11
|
+
* @property {object} [env] - Environment configration object.
|
|
12
|
+
* @property {string} [env.websockets={}] - Configuration options for websockets.
|
|
13
|
+
* @property {string} [env.subpath=''] - If running behind a proxy, path to the application.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Configuration object for a client running in a node runtime.
|
|
18
|
+
*
|
|
19
|
+
* @typedef NodeClientConfig
|
|
20
|
+
* @memberof client
|
|
21
|
+
* @type {object}
|
|
22
|
+
* @property {string} role - Role of the client in the application (e.g. 'player', 'controller').
|
|
23
|
+
* @property {object} [app] - Application configration object.
|
|
24
|
+
* @property {string} [app.name=''] - Name of the application.
|
|
25
|
+
* @property {string} [app.author=''] - Name of the author.
|
|
26
|
+
* @property {object} env - Environment configration object.
|
|
27
|
+
* @property {boolean} env.serverAddress - Domain name or IP of the server.
|
|
28
|
+
* @property {boolean} env.useHttps - Define is the server run in http or in https.
|
|
29
|
+
* @property {boolean} env.port - Port on which the server is listening.
|
|
30
|
+
* @property {string} [env.websockets={}] - Configuration options for websockets.
|
|
31
|
+
* @property {string} [env.subpath=''] - If running behind a proxy, path to the application.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Client-side part of the `soundworks` framework.
|
|
36
|
+
*
|
|
37
|
+
* A `soundworks` client can run seamlessly in a browser or in a Node.js runtime.
|
|
38
|
+
*
|
|
39
|
+
* ```
|
|
40
|
+
* import '@soundworks/helpers/polyfills.js';
|
|
41
|
+
* import { Client } from '@soundworks/core/client.js';
|
|
42
|
+
* import launcher from '@soundworks/helpers/launcher.js';
|
|
43
|
+
*
|
|
44
|
+
* // - General documentation: https://soundworks.dev/
|
|
45
|
+
* // - API documentation: https://soundworks.dev/api
|
|
46
|
+
* // - Issue Tracker: https://github.com/collective-soundworks/soundworks/issues
|
|
47
|
+
* // - Wizard & Tools: `npx soundworks`
|
|
48
|
+
*
|
|
49
|
+
* const config = window.SOUNDWORKS_CONFIG;
|
|
50
|
+
*
|
|
51
|
+
* async function main($container) {
|
|
52
|
+
* try {
|
|
53
|
+
* const client = new Client(config);
|
|
54
|
+
*
|
|
55
|
+
* // client.pluginManager.register(pluginName, pluginFactory, {options}, [dependencies])
|
|
56
|
+
*
|
|
57
|
+
* launcher.register(client, {
|
|
58
|
+
* initScreensContainer: $container,
|
|
59
|
+
* reloadOnVisibilityChange: false,
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* await client.start();
|
|
63
|
+
*
|
|
64
|
+
* $container.innerText = `client ${client.id} started`);
|
|
65
|
+
*
|
|
66
|
+
* } catch(err) {
|
|
67
|
+
* console.error(err);
|
|
68
|
+
* }
|
|
69
|
+
* }
|
|
70
|
+
*
|
|
71
|
+
* launcher.execute(main);
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* @namespace client
|
|
75
|
+
*/
|
|
76
|
+
export { default as Context } from './Context.js';
|
|
77
|
+
export { default as Client } from './Client.js';
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import merge from 'lodash.merge';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @private
|
|
5
|
+
*/
|
|
6
|
+
class BasePlugin {
|
|
7
|
+
constructor(id) {
|
|
8
|
+
/**
|
|
9
|
+
* User defined ID of the plugin.
|
|
10
|
+
*
|
|
11
|
+
* @type {string}
|
|
12
|
+
* @see {@link client.PluginManager#register}
|
|
13
|
+
* @see {@link server.PluginManager#register}
|
|
14
|
+
*/
|
|
15
|
+
this.id = id;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Type of the plugin, i.e. the ClassName.
|
|
19
|
+
*
|
|
20
|
+
* Usefull to do perform some logic based on certain types of plugins without
|
|
21
|
+
* knowing under which `id` they have been registered. (e.g. creating some generic
|
|
22
|
+
* views, etc.)
|
|
23
|
+
*
|
|
24
|
+
* @type {string}
|
|
25
|
+
* @readonly
|
|
26
|
+
*/
|
|
27
|
+
this.type = this.constructor.name;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Options of the plugin.
|
|
31
|
+
*
|
|
32
|
+
* @type {object}
|
|
33
|
+
*/
|
|
34
|
+
this.options = {};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Placeholder that stores internal (local) state of the plugin. The state
|
|
38
|
+
* should be modified through the `propagateStateChange` method to ensure
|
|
39
|
+
* the change to be properly propagated to `onStateChange` callbacks.
|
|
40
|
+
*
|
|
41
|
+
* @type {object}
|
|
42
|
+
* @protected
|
|
43
|
+
* @see {@link client.Plugin#onStateChange}
|
|
44
|
+
* @see {@link server.Plugin#onStateChange}
|
|
45
|
+
* @see {@link client.Plugin#propagateStateChange}
|
|
46
|
+
* @see {@link server.Plugin#propagateStateChange}
|
|
47
|
+
*/
|
|
48
|
+
this.state = {};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Current status of the plugin, i.e. 'idle', 'inited', 'started', 'errored'
|
|
52
|
+
*
|
|
53
|
+
* @type {string}
|
|
54
|
+
*/
|
|
55
|
+
this.status = 'idle';
|
|
56
|
+
|
|
57
|
+
/** @private */
|
|
58
|
+
this._onStateChangeCallbacks = new Set();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Start the plugin. This method is automatically called during the client or
|
|
63
|
+
* server `init()` lifecyle step. After `start()` is fulfilled the plugin should
|
|
64
|
+
* be ready to use.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* // server-side couterpart of a plugin that creates a dedicated global shared
|
|
68
|
+
* // state on which the server-side part can attach.
|
|
69
|
+
* class MyPlugin extends Plugin {
|
|
70
|
+
* constructor(server, id) {
|
|
71
|
+
* super(server, id);
|
|
72
|
+
*
|
|
73
|
+
* this.server.stateManager.registerSchema(`my-plugin:${this.id}`, {
|
|
74
|
+
* someParam: {
|
|
75
|
+
* type: 'boolean',
|
|
76
|
+
* default: false,
|
|
77
|
+
* },
|
|
78
|
+
* // ...
|
|
79
|
+
* });
|
|
80
|
+
* }
|
|
81
|
+
*
|
|
82
|
+
* async start() {
|
|
83
|
+
* await super.start()
|
|
84
|
+
* this.sharedState = await this.server.stateManager.create(`my-plugin:${this.id}`);
|
|
85
|
+
* }
|
|
86
|
+
*
|
|
87
|
+
* async stop() {
|
|
88
|
+
* await this.sharedState.delete();
|
|
89
|
+
* }
|
|
90
|
+
* }
|
|
91
|
+
*/
|
|
92
|
+
async start() {}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Stop the plugin. This method is automatically called during the client or server
|
|
96
|
+
* `stop()` lifecyle step.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* // server-side couterpart of a plugin that creates a dedicated global shared
|
|
100
|
+
* // state on which the server-side part can attach.
|
|
101
|
+
* class MyPlugin extends Plugin {
|
|
102
|
+
* constructor(server, id) {
|
|
103
|
+
* super(server, id);
|
|
104
|
+
*
|
|
105
|
+
* this.server.stateManager.registerSchema(`my-plugin:${this.id}`, {
|
|
106
|
+
* someParam: {
|
|
107
|
+
* type: 'boolean',
|
|
108
|
+
* default: false,
|
|
109
|
+
* },
|
|
110
|
+
* // ...
|
|
111
|
+
* });
|
|
112
|
+
* }
|
|
113
|
+
*
|
|
114
|
+
* async start() {
|
|
115
|
+
* await super.start()
|
|
116
|
+
* this.sharedState = await this.server.stateManager.create(`my-plugin:${this.id}`);
|
|
117
|
+
* this.sharedState.onUpdate(updates => this.doSomething(updates));
|
|
118
|
+
* }
|
|
119
|
+
*
|
|
120
|
+
* async stop() {
|
|
121
|
+
* await this.sharedState.delete();
|
|
122
|
+
* }
|
|
123
|
+
* }
|
|
124
|
+
*/
|
|
125
|
+
async stop() {}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Listen to the state changes propagated by {@link BasePlugin.propagateStateChange}
|
|
129
|
+
*
|
|
130
|
+
* @param {client.Plugin~onStateChangeCallback|server.Plugin~onStateChangeCallback} callback -
|
|
131
|
+
* Callback to execute when a state change is propagated.
|
|
132
|
+
* @returns {client.Plugin~deleteOnStateChangeCallback|server.Plugin~deleteOnStateChangeCallback}
|
|
133
|
+
* Execute the function to delete the listener from the callback list.
|
|
134
|
+
* @see {@link client.Plugin#propagateStateChange}
|
|
135
|
+
* @see {@link server.Plugin#propagateStateChange}
|
|
136
|
+
* @example
|
|
137
|
+
* const unsubscribe = plugin.onStateChange(pluginState => console.log(pluginState));
|
|
138
|
+
* // stop listening state changes
|
|
139
|
+
* unsubscribe();
|
|
140
|
+
*/
|
|
141
|
+
onStateChange(callback) {
|
|
142
|
+
this._onStateChangeCallbacks.add(callback);
|
|
143
|
+
return () => this._onStateChangeCallbacks.delete(callback);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Apply updates to the plugin state and propagate the updated state to the
|
|
148
|
+
* `onStateChange` listeners. The state changes will also be propagated
|
|
149
|
+
* through the `PluginManager#onStateChange` listeners.
|
|
150
|
+
*
|
|
151
|
+
* @param {object} updates - Updates to be merged in the plugin state.
|
|
152
|
+
* @see {@link client.Plugin#onStateChange}
|
|
153
|
+
* @see {@link server.Plugin#onStateChange}
|
|
154
|
+
* @see {@link client.PluginManager#onStateChange}
|
|
155
|
+
* @see {@link server.PluginManager#onStateChange}
|
|
156
|
+
*/
|
|
157
|
+
propagateStateChange(updates) {
|
|
158
|
+
merge(this.state, updates);
|
|
159
|
+
this._onStateChangeCallbacks.forEach(callback => callback(this.state));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export default BasePlugin;
|