@soundworks/core 4.0.0-alpha.9 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/README.md +14 -29
- package/package.json +24 -42
- package/src/client/Client.js +306 -256
- package/src/client/{Context.js → ClientContext.js} +63 -54
- package/src/client/ClientContextManager.js +115 -0
- package/src/client/{Plugin.js → ClientPlugin.js} +23 -19
- package/src/client/{PluginManager.js → ClientPluginManager.js} +43 -40
- package/src/client/ClientSocket.js +270 -0
- package/src/client/{StateManager.js → ClientStateManager.js} +13 -13
- package/src/client/index.js +36 -39
- package/src/common/BasePlugin.js +63 -58
- package/src/common/BasePluginManager.js +191 -142
- package/src/common/BaseStateManager.js +635 -145
- package/src/common/BatchedTransport.js +73 -0
- package/src/common/ParameterBag.js +90 -93
- package/src/common/PromiseStore.js +54 -0
- package/src/common/SharedState.js +821 -0
- package/src/common/SharedStateCollection.js +492 -0
- package/src/common/constants.js +14 -2
- package/src/common/logger.js +64 -68
- package/src/common/shared-state-types.js +82 -0
- package/src/common/sockets-utils.js +0 -64
- package/src/common/version.js +2 -0
- package/src/server/Server.js +552 -824
- package/src/server/ServerClient.js +74 -0
- package/src/server/{Context.js → ServerContext.js} +96 -77
- package/src/server/{ContextManager.js → ServerContextManager.js} +107 -87
- package/src/server/{Plugin.js → ServerPlugin.js} +30 -32
- package/src/server/ServerPluginManager.js +177 -0
- package/src/server/ServerSocket.js +256 -0
- package/src/server/ServerSockets.js +150 -0
- package/src/server/ServerStateManager.js +706 -0
- package/src/server/SharedStatePrivate.js +277 -0
- package/src/server/audit-network-latency.worker.js +19 -4
- package/src/server/{audit-schema.js → audit-state-class-description.js} +3 -2
- package/src/server/create-http-server.js +127 -0
- package/src/server/crypto.js +8 -7
- package/src/server/index.js +60 -17
- package/types/client/Client.d.ts +95 -70
- package/types/client/{Context.d.ts → ClientContext.d.ts} +59 -34
- package/types/client/{ContextManager.d.ts → ClientContextManager.d.ts} +19 -22
- package/types/client/ClientPlugin.d.ts +51 -0
- package/types/client/ClientPluginManager.d.ts +112 -0
- package/types/client/ClientSocket.d.ts +76 -0
- package/types/client/{StateManager.d.ts → ClientStateManager.d.ts} +18 -21
- package/types/client/index.d.ts +33 -6
- package/types/common/BasePlugin.d.ts +64 -53
- package/types/common/BasePluginManager.d.ts +84 -39
- package/types/common/BaseStateManager.d.ts +227 -49
- package/types/common/BatchedTransport.d.ts +17 -0
- package/types/common/ParameterBag.d.ts +73 -76
- package/types/common/PromiseStore.d.ts +11 -0
- package/types/common/SharedState.d.ts +291 -0
- package/types/common/SharedStateCollection.d.ts +311 -0
- package/types/common/constants.d.ts +10 -3
- package/types/common/logger.d.ts +8 -6
- package/types/common/shared-state-types.d.ts +95 -0
- package/types/common/sockets-utils.d.ts +0 -5
- package/types/common/version.d.ts +2 -0
- package/types/server/Server.d.ts +215 -136
- package/types/server/ServerClient.d.ts +45 -0
- package/types/server/ServerContext.d.ts +209 -0
- package/types/server/ServerContextManager.d.ts +75 -0
- package/types/server/ServerPlugin.d.ts +57 -0
- package/types/server/ServerPluginManager.d.ts +115 -0
- package/types/server/ServerSocket.d.ts +69 -0
- package/types/server/ServerSockets.d.ts +42 -0
- package/types/server/ServerStateManager.d.ts +284 -0
- package/types/server/SharedStatePrivate.d.ts +23 -0
- package/types/server/audit-network-latency.worker.d.ts +2 -2
- package/types/server/{audit-schema.d.ts → audit-state-class-description.d.ts} +8 -9
- package/types/server/create-http-server.d.ts +5 -0
- package/types/server/crypto.d.ts +4 -0
- package/types/server/index.d.ts +51 -3
- package/.eslintrc +0 -15
- package/.github/workflows/gh-pages.yml +0 -31
- package/.github/workflows/unit-tests.yml +0 -18
- package/src/client/ContextManager.js +0 -111
- package/src/client/SharedState.js +0 -84
- package/src/client/SharedStateCollection.js +0 -35
- package/src/client/Socket.js +0 -402
- package/src/common/BaseSharedState.js +0 -583
- package/src/common/BaseSharedStateCollection.js +0 -179
- package/src/common/SharedStatePrivate.js +0 -192
- package/src/common/promise-store.js +0 -25
- package/src/server/Client.js +0 -55
- package/src/server/PluginManager.js +0 -170
- package/src/server/SharedState.js +0 -85
- package/src/server/SharedStateCollection.js +0 -36
- package/src/server/Socket.js +0 -396
- package/src/server/Sockets.js +0 -224
- package/src/server/StateManager.js +0 -620
- package/tsconfig.json +0 -21
- package/types/client/Client.d.ts.map +0 -1
- package/types/client/Context.d.ts.map +0 -1
- package/types/client/ContextManager.d.ts.map +0 -1
- package/types/client/Plugin.d.ts +0 -53
- package/types/client/Plugin.d.ts.map +0 -1
- package/types/client/PluginManager.d.ts +0 -96
- package/types/client/PluginManager.d.ts.map +0 -1
- package/types/client/SharedState.d.ts +0 -85
- package/types/client/SharedState.d.ts.map +0 -1
- package/types/client/Socket.d.ts +0 -138
- package/types/client/Socket.d.ts.map +0 -1
- package/types/client/StateManager.d.ts.map +0 -1
- package/types/client/index.d.ts.map +0 -1
- package/types/common/BasePlugin.d.ts.map +0 -1
- package/types/common/BasePluginManager.d.ts.map +0 -1
- package/types/common/BaseSharedState.d.ts +0 -173
- package/types/common/BaseSharedState.d.ts.map +0 -1
- package/types/common/BaseStateManager.d.ts.map +0 -1
- package/types/common/ParameterBag.d.ts.map +0 -1
- package/types/common/SharedStatePrivate.d.ts +0 -20
- package/types/common/SharedStatePrivate.d.ts.map +0 -1
- package/types/common/constants.d.ts.map +0 -1
- package/types/common/logger.d.ts.map +0 -1
- package/types/common/promise-store.d.ts +0 -4
- package/types/common/promise-store.d.ts.map +0 -1
- package/types/common/sockets-utils.d.ts.map +0 -1
- package/types/common/utils.d.ts +0 -5
- package/types/common/utils.d.ts.map +0 -1
- package/types/server/Client.d.ts +0 -40
- package/types/server/Client.d.ts.map +0 -1
- package/types/server/Context.d.ts +0 -59
- package/types/server/Context.d.ts.map +0 -1
- package/types/server/ContextManager.d.ts +0 -69
- package/types/server/ContextManager.d.ts.map +0 -1
- package/types/server/Plugin.d.ts +0 -70
- package/types/server/Plugin.d.ts.map +0 -1
- package/types/server/PluginManager.d.ts +0 -91
- package/types/server/PluginManager.d.ts.map +0 -1
- package/types/server/Server.d.ts.map +0 -1
- package/types/server/SharedState.d.ts +0 -86
- package/types/server/SharedState.d.ts.map +0 -1
- package/types/server/Socket.d.ts +0 -128
- package/types/server/Socket.d.ts.map +0 -1
- package/types/server/Sockets.d.ts +0 -73
- package/types/server/Sockets.d.ts.map +0 -1
- package/types/server/StateManager.d.ts +0 -190
- package/types/server/StateManager.d.ts.map +0 -1
- package/types/server/audit-network-latency.worker.d.ts.map +0 -1
- package/types/server/audit-schema.d.ts.map +0 -1
- package/types/server/index.d.ts.map +0 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { isBrowser } from '@ircam/sc-utils';
|
|
2
|
+
import WebSocket from 'isomorphic-ws';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
PING_MESSAGE,
|
|
6
|
+
PONG_MESSAGE,
|
|
7
|
+
} from '../common/constants.js';
|
|
8
|
+
import logger from '../common/logger.js';
|
|
9
|
+
import {
|
|
10
|
+
packStringMessage,
|
|
11
|
+
unpackStringMessage,
|
|
12
|
+
} from '../common/sockets-utils.js';
|
|
13
|
+
|
|
14
|
+
export const kSocketTerminate = Symbol('soundworks:socket-terminate');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The ClientSocket class is a simple publish / subscribe wrapper built on top of the
|
|
18
|
+
* [isomorphic-ws](https://github.com/heineiuo/isomorphic-ws) library.
|
|
19
|
+
* An instance of `ClientSocket` is automatically created by the `soundworks.Client`
|
|
20
|
+
* (see {@link Client#socket}).
|
|
21
|
+
*
|
|
22
|
+
* _Important: In most cases, you should consider using a {@link SharedState}
|
|
23
|
+
* rather than directly using the sockets._
|
|
24
|
+
*
|
|
25
|
+
* The ClientSocket class concurrently opens two different WebSockets:
|
|
26
|
+
* - a socket configured with `binaryType = 'blob'` for JSON compatible data
|
|
27
|
+
* types (i.e. string, number, boolean, object, array and null).
|
|
28
|
+
* - a socket configured with `binaryType= 'arraybuffer'` for efficient streaming
|
|
29
|
+
* of binary data.
|
|
30
|
+
*
|
|
31
|
+
* Both sockets re-emits all "native" ws events ('open', 'upgrade', 'close', 'error'
|
|
32
|
+
* and 'message'.
|
|
33
|
+
*
|
|
34
|
+
* @hideconstructor
|
|
35
|
+
*/
|
|
36
|
+
class ClientSocket {
|
|
37
|
+
#role = null;
|
|
38
|
+
#config = null;
|
|
39
|
+
#socketOptions = null;
|
|
40
|
+
#socket = null;
|
|
41
|
+
#listeners = new Map();
|
|
42
|
+
|
|
43
|
+
constructor(role, config, socketOptions) {
|
|
44
|
+
this.#role = role;
|
|
45
|
+
this.#config = config;
|
|
46
|
+
this.#socketOptions = socketOptions;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Initialize a websocket connection with the server. Automatically called
|
|
51
|
+
* during {@link Client#init}
|
|
52
|
+
*
|
|
53
|
+
* @param {string} role - Role of the client (see {@link Client#role})
|
|
54
|
+
* @param {object} config - Configuration of the sockets
|
|
55
|
+
* @private
|
|
56
|
+
*/
|
|
57
|
+
async init() {
|
|
58
|
+
let { path } = this.#socketOptions;
|
|
59
|
+
// cf. https://github.com/collective-soundworks/soundworks/issues/35
|
|
60
|
+
if (this.#config.env.baseUrl) {
|
|
61
|
+
path = `${this.#config.env.baseUrl}/${path}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const protocol = this.#config.env.useHttps ? 'wss:' : 'ws:';
|
|
65
|
+
const port = this.#config.env.port;
|
|
66
|
+
let serverAddress;
|
|
67
|
+
let webSocketOptions;
|
|
68
|
+
|
|
69
|
+
if (isBrowser()) {
|
|
70
|
+
const hostname = window.location.hostname;
|
|
71
|
+
|
|
72
|
+
if (this.#config.env.serverAddress === '') {
|
|
73
|
+
serverAddress = hostname;
|
|
74
|
+
} else {
|
|
75
|
+
serverAddress = this.#config.env.serverAddress;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// when in https with self-signed certificates, we don't want to use
|
|
79
|
+
// the serverAddress defined in config as the socket would be blocked, so we
|
|
80
|
+
// just override serverAddress with hostname in this case
|
|
81
|
+
const localhosts = ['127.0.0.1', 'localhost'];
|
|
82
|
+
|
|
83
|
+
if (this.#config.env.useHttps && localhosts.includes(hostname)) {
|
|
84
|
+
serverAddress = window.location.hostname;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (this.#config.env.useHttps && window.location.hostname !== serverAddress) {
|
|
88
|
+
console.warn(`ClientSocket will try to connect at ${serverAddress} while the page is accessed from ${hostname}. This can lead to socket errors, e.g. If you run the application with self-signed certificates as the certificate may not have been accepted for ${serverAddress}. In such case you should rather access the page from ${serverAddress}.`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
webSocketOptions = [];
|
|
92
|
+
} else {
|
|
93
|
+
if (this.#config.env.serverAddress === '') {
|
|
94
|
+
serverAddress = '127.0.0.1';
|
|
95
|
+
} else {
|
|
96
|
+
serverAddress = this.#config.env.serverAddress;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
webSocketOptions = {
|
|
100
|
+
// handshakeTimeout: 2000,
|
|
101
|
+
// do not reject self-signed certificates
|
|
102
|
+
rejectUnauthorized: false,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let queryParams = `role=${this.#role}`;
|
|
107
|
+
|
|
108
|
+
if (this.#config.token) {
|
|
109
|
+
queryParams += `&token=${this.#config.token}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const url = `${protocol}//${serverAddress}:${port}/${path}?${queryParams}`;
|
|
113
|
+
|
|
114
|
+
// ----------------------------------------------------------
|
|
115
|
+
// Init socket
|
|
116
|
+
// ----------------------------------------------------------
|
|
117
|
+
return new Promise(resolve => {
|
|
118
|
+
const trySocket = async () => {
|
|
119
|
+
const ws = new WebSocket(url, webSocketOptions);
|
|
120
|
+
|
|
121
|
+
// WebSocket "native" events:
|
|
122
|
+
// - `close`: Fired when a connection with a websocket is closed.
|
|
123
|
+
// - `error`: Fired when a connection with a websocket has been closed
|
|
124
|
+
// because of an error, such as when some data couldn't be sent.
|
|
125
|
+
// - `message`: Fired when data is received through a websocket.
|
|
126
|
+
// - `open`: Fired when a connection with a websocket is opened.
|
|
127
|
+
|
|
128
|
+
ws.addEventListener('open', openEvent => {
|
|
129
|
+
this.#socket = ws;
|
|
130
|
+
|
|
131
|
+
this.#socket.addEventListener('message', e => {
|
|
132
|
+
if (e.data === PING_MESSAGE) {
|
|
133
|
+
this.#socket.send(PONG_MESSAGE);
|
|
134
|
+
return; // do not propagate ping pong messages
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Parse incoming message, dispatch in pubsub system and propagate raw event.
|
|
138
|
+
const [channel, args] = unpackStringMessage(e.data);
|
|
139
|
+
this.#dispatchEvent(channel, ...args);
|
|
140
|
+
this.#dispatchEvent('message', e);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Propagate other native events
|
|
144
|
+
// @note - isn't it too late for the 'upgrade' message?
|
|
145
|
+
['close', 'error', 'upgrade'].forEach(eventName => {
|
|
146
|
+
this.#socket.addEventListener(eventName, e => this.#dispatchEvent(eventName, e));
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Forward open event and continue initialization
|
|
150
|
+
this.#dispatchEvent('open', openEvent);
|
|
151
|
+
resolve();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// cf. https://github.com/collective-soundworks/soundworks/issues/17
|
|
155
|
+
// cf. https://github.com/collective-soundworks/soundworks/issues/97
|
|
156
|
+
ws.addEventListener('error', e => {
|
|
157
|
+
ws.terminate ? ws.terminate() : ws.close();
|
|
158
|
+
|
|
159
|
+
if (e.error) {
|
|
160
|
+
const msg = `ClientSocket error - code: ${e.error.code}, message: ${e.error.message}`;
|
|
161
|
+
logger.log(msg);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Try to reconnect in all cases. Note that if the socket has been connected,
|
|
165
|
+
// the close event will be propagated and the launcher will restart the process.
|
|
166
|
+
setTimeout(trySocket, 1000);
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
trySocket();
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Removes all listeners and immediately close the two sockets. Is automatically
|
|
176
|
+
* called on `client.stop()`
|
|
177
|
+
*
|
|
178
|
+
* Is also called when a disconnection is detected by the heartbeat (note that
|
|
179
|
+
* in this case, the launcher will call `client.stop()` but the listeners are
|
|
180
|
+
* already cleared so the event will be triggered only once.
|
|
181
|
+
*
|
|
182
|
+
* @private
|
|
183
|
+
*/
|
|
184
|
+
async [kSocketTerminate]() {
|
|
185
|
+
const closeListeners = this.#listeners.get('close');
|
|
186
|
+
this.removeAllListeners();
|
|
187
|
+
|
|
188
|
+
this.#socket.close();
|
|
189
|
+
|
|
190
|
+
if (closeListeners) {
|
|
191
|
+
closeListeners.forEach(listener => listener());
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return Promise.resolve();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @param {string} channel - Channel name.
|
|
199
|
+
* @param {...*} args - Content of the message.
|
|
200
|
+
*/
|
|
201
|
+
#dispatchEvent(channel, ...args) {
|
|
202
|
+
if (this.#listeners.has(channel)) {
|
|
203
|
+
const callbacks = this.#listeners.get(channel);
|
|
204
|
+
callbacks.forEach(callback => callback(...args));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Send messages with JSON compatible data types on a given channel.
|
|
210
|
+
*
|
|
211
|
+
* @param {string} channel - Channel name.
|
|
212
|
+
* @param {...*} args - Payload of the message. As many arguments as needed, of
|
|
213
|
+
* JSON compatible data types (i.e. string, number, boolean, object, array and null).
|
|
214
|
+
*/
|
|
215
|
+
send(channel, ...args) {
|
|
216
|
+
if (this.#socket.readyState === 1) {
|
|
217
|
+
const msg = packStringMessage(channel, ...args);
|
|
218
|
+
this.#socket.send(msg);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Listen messages with JSON compatible data types on a given channel.
|
|
224
|
+
*
|
|
225
|
+
* @param {string} channel - Channel name.
|
|
226
|
+
* @param {Function} callback - Callback to execute when a message is received,
|
|
227
|
+
* arguments of the callback function will match the arguments sent using the
|
|
228
|
+
* {@link ServerSocket#send} method.
|
|
229
|
+
*/
|
|
230
|
+
addListener(channel, callback) {
|
|
231
|
+
if (!this.#listeners.has(channel)) {
|
|
232
|
+
this.#listeners.set(channel, new Set());
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const callbacks = this.#listeners.get(channel);
|
|
236
|
+
callbacks.add(callback);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Remove a listener from JSON compatible messages on a given channel.
|
|
241
|
+
*
|
|
242
|
+
* @param {string} channel - Channel name.
|
|
243
|
+
* @param {Function} callback - Callback to remove.
|
|
244
|
+
*/
|
|
245
|
+
removeListener(channel, callback) {
|
|
246
|
+
if (this.#listeners.has(channel)) {
|
|
247
|
+
const callbacks = this.#listeners.get(channel);
|
|
248
|
+
callbacks.delete(callback);
|
|
249
|
+
|
|
250
|
+
if (callbacks.size === 0) {
|
|
251
|
+
this.#listeners.delete(channel);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Remove all listeners of messages with JSON compatible data types.
|
|
258
|
+
*
|
|
259
|
+
* @param {string} channel - Channel name.
|
|
260
|
+
*/
|
|
261
|
+
removeAllListeners(channel = null) {
|
|
262
|
+
if (channel === null) {
|
|
263
|
+
this.#listeners.clear();
|
|
264
|
+
} else if (this.#listeners.has(channel)) {
|
|
265
|
+
this.#listeners.delete(channel);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export default ClientSocket;
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import BaseStateManager from '../common/BaseStateManager.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @callback
|
|
4
|
+
* @callback ClientStateManager~ObserveCallback
|
|
5
5
|
* @async
|
|
6
|
-
* @param {String}
|
|
7
|
-
* @param {Number} stateId -
|
|
8
|
-
* @param {Number} nodeId -
|
|
6
|
+
* @param {String} className - Name of the shared state class.
|
|
7
|
+
* @param {Number} stateId - Id of the state.
|
|
8
|
+
* @param {Number} nodeId - Id of the node that created the state.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* The `
|
|
13
|
-
* to {@link
|
|
14
|
-
* can also track all the {@link client.SharedState}s created by other nodes.
|
|
12
|
+
* The `ClientStateManager` allows to create new {@link SharedState}s, or attach
|
|
13
|
+
* to {@link SharedState}s created by other nodes (clients or server) on the network.
|
|
15
14
|
*
|
|
16
|
-
*
|
|
15
|
+
* It can also observe all the {@link SharedState}s created on the network.
|
|
16
|
+
*
|
|
17
|
+
* An instance of `ClientStateManager` is automatically created by the `soundworks.Client`
|
|
17
18
|
* at initialization (cf. {@link client.Client#stateManager}).
|
|
18
19
|
*
|
|
19
20
|
* See {@link client.Client#stateManager}
|
|
@@ -25,8 +26,8 @@ import BaseStateManager from '../common/BaseStateManager.js';
|
|
|
25
26
|
* import { Server } from '@soundworks/server/index.js';
|
|
26
27
|
*
|
|
27
28
|
* const server = new Server(config);
|
|
28
|
-
* //
|
|
29
|
-
* server.stateManager.
|
|
29
|
+
* // define a class of shared state.
|
|
30
|
+
* server.stateManager.defineClass('some-global-state', {
|
|
30
31
|
* myRandom: {
|
|
31
32
|
* type: 'float',
|
|
32
33
|
* default: 0,
|
|
@@ -57,11 +58,10 @@ import BaseStateManager from '../common/BaseStateManager.js';
|
|
|
57
58
|
* }, 1000);
|
|
58
59
|
* ```
|
|
59
60
|
*
|
|
60
|
-
* @memberof client
|
|
61
61
|
* @extends BaseStateManager
|
|
62
62
|
* @inheritdoc
|
|
63
63
|
* @hideconstructor
|
|
64
64
|
*/
|
|
65
|
-
class
|
|
65
|
+
class ClientStateManager extends BaseStateManager {}
|
|
66
66
|
|
|
67
|
-
export default
|
|
67
|
+
export default ClientStateManager;
|
package/src/client/index.js
CHANGED
|
@@ -1,44 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
* // - General documentation: https://soundworks.dev/
|
|
12
|
-
* // - API documentation: https://soundworks.dev/api
|
|
13
|
-
* // - Issue Tracker: https://github.com/collective-soundworks/soundworks/issues
|
|
14
|
-
* // - Wizard & Tools: `npx soundworks`
|
|
15
|
-
*
|
|
16
|
-
* const config = window.SOUNDWORKS_CONFIG;
|
|
17
|
-
*
|
|
18
|
-
* async function main($container) {
|
|
19
|
-
* try {
|
|
20
|
-
* const client = new Client(config);
|
|
21
|
-
*
|
|
22
|
-
* // client.pluginManager.register(pluginName, pluginFactory, {options}, [dependencies])
|
|
23
|
-
*
|
|
24
|
-
* launcher.register(client, {
|
|
25
|
-
* initScreensContainer: $container,
|
|
26
|
-
* reloadOnVisibilityChange: false,
|
|
27
|
-
* });
|
|
28
|
-
*
|
|
29
|
-
* await client.start();
|
|
30
|
-
*
|
|
31
|
-
* $container.innerText = `client ${client.id} started`);
|
|
32
|
-
*
|
|
33
|
-
* } catch(err) {
|
|
34
|
-
* console.error(err);
|
|
35
|
-
* }
|
|
36
|
-
* }
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2014-present IRCAM – Centre Pompidou (France, Paris)
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// these types must be defined here, so that they can be imported into the application
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Application configuration object.
|
|
37
11
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
12
|
+
* @typedef ClientAppConfig
|
|
13
|
+
* @type {object}
|
|
14
|
+
* @property {string} [name=''] - Name of the application.
|
|
15
|
+
* @property {string} [author=''] - Name of the author.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Environment configuration object.
|
|
19
|
+
*
|
|
20
|
+
* @typedef ClientEnvConfig
|
|
21
|
+
* @type {objecy}
|
|
22
|
+
* @property {number} env.port - Port on which the server is listening.
|
|
23
|
+
* @property {string} env.serverAddress - Domain name or IP of the server.
|
|
24
|
+
* Mandatory when node clients are declared
|
|
25
|
+
* @property {boolean} env.useHttps - Define is the server run in http or in https.
|
|
26
|
+
* @property {string} [env.baseUrl=''] - If running behind a proxy, base URL of the application.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Configuration object for a client running in a browser runtime.
|
|
40
30
|
*
|
|
41
|
-
* @
|
|
31
|
+
* @typedef ClientConfig
|
|
32
|
+
* @type {object}
|
|
33
|
+
* @property {string} role - Role of the client in the application (e.g. 'player', 'controller').
|
|
34
|
+
* @property {ClientAppConfig} app - Application configuration object.
|
|
35
|
+
* @property {ClientEnvConfig} env - Environment configuration object.
|
|
42
36
|
*/
|
|
43
|
-
|
|
37
|
+
|
|
44
38
|
export { default as Client } from './Client.js';
|
|
39
|
+
export { default as ClientPlugin } from './ClientPlugin.js';
|
|
40
|
+
export { default as ClientContext } from './ClientContext.js';
|
|
41
|
+
export { default as version } from '../common/version.js';
|
package/src/common/BasePlugin.js
CHANGED
|
@@ -1,45 +1,43 @@
|
|
|
1
|
-
import merge from 'lodash.
|
|
1
|
+
import merge from 'lodash/merge.js';
|
|
2
|
+
|
|
3
|
+
export const kBasePluginStatus = Symbol('soundworks:base-plugin-status');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Callback executed when the plugin state is updated.
|
|
7
|
+
*
|
|
8
|
+
* @callback pluginOnStateChangeCallback
|
|
9
|
+
* @param {BasePlugin#state} state - Current state of the plugin.
|
|
10
|
+
*/
|
|
2
11
|
|
|
3
12
|
/**
|
|
4
|
-
* @
|
|
13
|
+
* Delete the registered {@link pluginOnStateChangeCallback}.
|
|
14
|
+
*
|
|
15
|
+
* @callback pluginDeleteOnStateChangeCallback
|
|
5
16
|
*/
|
|
17
|
+
|
|
18
|
+
/** @private */
|
|
6
19
|
class BasePlugin {
|
|
20
|
+
#id = null;
|
|
21
|
+
#onStateChangeCallbacks = new Set();
|
|
22
|
+
|
|
7
23
|
constructor(id) {
|
|
24
|
+
this.#id = id;
|
|
8
25
|
/** @private */
|
|
9
|
-
this
|
|
10
|
-
/** @private */
|
|
11
|
-
this._type = this.constructor.name;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Options of the plugin.
|
|
15
|
-
*
|
|
16
|
-
* @type {object}
|
|
17
|
-
*/
|
|
18
|
-
this.options = {};
|
|
26
|
+
this[kBasePluginStatus] = 'idle';
|
|
19
27
|
|
|
20
28
|
/**
|
|
21
29
|
* Placeholder that stores internal (local) state of the plugin. The state
|
|
22
30
|
* should be modified through the `propagateStateChange` method to ensure
|
|
23
|
-
* the change to be properly propagated to `onStateChange` callbacks.
|
|
31
|
+
* the change to be properly propagated to manager `onStateChange` callbacks.
|
|
24
32
|
*
|
|
25
33
|
* @type {object}
|
|
26
34
|
* @protected
|
|
27
|
-
* @see {@link
|
|
28
|
-
* @see {@link
|
|
29
|
-
* @see {@link
|
|
30
|
-
* @see {@link
|
|
35
|
+
* @see {@link ClientPlugin#onStateChange}
|
|
36
|
+
* @see {@link ServerPlugin#onStateChange}
|
|
37
|
+
* @see {@link ClientPlugin#propagateStateChange}
|
|
38
|
+
* @see {@link ServerPlugin#propagateStateChange}
|
|
31
39
|
*/
|
|
32
40
|
this.state = {};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Current status of the plugin, i.e. 'idle', 'inited', 'started', 'errored'
|
|
36
|
-
*
|
|
37
|
-
* @type {string}
|
|
38
|
-
*/
|
|
39
|
-
this.status = 'idle';
|
|
40
|
-
|
|
41
|
-
/** @private */
|
|
42
|
-
this._onStateChangeCallbacks = new Set();
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
/**
|
|
@@ -47,17 +45,17 @@ class BasePlugin {
|
|
|
47
45
|
*
|
|
48
46
|
* @type {string}
|
|
49
47
|
* @readonly
|
|
50
|
-
* @see {@link
|
|
51
|
-
* @see {@link
|
|
48
|
+
* @see {@link ClientPluginManager#register}
|
|
49
|
+
* @see {@link ServerPluginManager#register}
|
|
52
50
|
*/
|
|
53
51
|
get id() {
|
|
54
|
-
return this
|
|
52
|
+
return this.#id;
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
/**
|
|
58
56
|
* Type of the plugin, i.e. the ClassName.
|
|
59
57
|
*
|
|
60
|
-
*
|
|
58
|
+
* Useful to do perform some logic based on certain types of plugins without
|
|
61
59
|
* knowing under which `id` they have been registered. (e.g. creating some generic
|
|
62
60
|
* views, etc.)
|
|
63
61
|
*
|
|
@@ -65,22 +63,32 @@ class BasePlugin {
|
|
|
65
63
|
* @readonly
|
|
66
64
|
*/
|
|
67
65
|
get type() {
|
|
68
|
-
return this.
|
|
66
|
+
return this.constructor.name;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Current status of the plugin.
|
|
71
|
+
*
|
|
72
|
+
* @type {'idle'|'inited'|'started'|'errored'}
|
|
73
|
+
*/
|
|
74
|
+
get status() {
|
|
75
|
+
return this[kBasePluginStatus];
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
/**
|
|
72
|
-
* Start the plugin.
|
|
73
|
-
*
|
|
74
|
-
*
|
|
79
|
+
* Start the plugin.
|
|
80
|
+
*
|
|
81
|
+
* This method is automatically called during the client or server `init()` lifecycle
|
|
82
|
+
* step. After `start()` is fulfilled the plugin should be ready to use.
|
|
75
83
|
*
|
|
76
84
|
* @example
|
|
77
|
-
* // server-side
|
|
85
|
+
* // server-side counterpart of a plugin that creates a dedicated global shared
|
|
78
86
|
* // state on which the server-side part can attach.
|
|
79
|
-
* class MyPlugin extends
|
|
87
|
+
* class MyPlugin extends ServerPlugin {
|
|
80
88
|
* constructor(server, id) {
|
|
81
89
|
* super(server, id);
|
|
82
90
|
*
|
|
83
|
-
* this.server.stateManager.
|
|
91
|
+
* this.server.stateManager.defineClass(`my-plugin:${this.id}`, {
|
|
84
92
|
* someParam: {
|
|
85
93
|
* type: 'boolean',
|
|
86
94
|
* default: false,
|
|
@@ -102,17 +110,18 @@ class BasePlugin {
|
|
|
102
110
|
async start() {}
|
|
103
111
|
|
|
104
112
|
/**
|
|
105
|
-
* Stop the plugin.
|
|
106
|
-
*
|
|
113
|
+
* Stop the plugin.
|
|
114
|
+
*
|
|
115
|
+
* This method is automatically called during the client or server `stop()` lifecycle step.
|
|
107
116
|
*
|
|
108
117
|
* @example
|
|
109
|
-
* // server-side
|
|
110
|
-
* // state on which the
|
|
111
|
-
* class MyPlugin extends
|
|
118
|
+
* // server-side counterpart of a plugin that creates a dedicated global shared
|
|
119
|
+
* // state on which the client-side part can attach.
|
|
120
|
+
* class MyPlugin extends ServerPlugin {
|
|
112
121
|
* constructor(server, id) {
|
|
113
122
|
* super(server, id);
|
|
114
123
|
*
|
|
115
|
-
* this.server.stateManager.
|
|
124
|
+
* this.server.stateManager.defineClass(`my-plugin:${this.id}`, {
|
|
116
125
|
* someParam: {
|
|
117
126
|
* type: 'boolean',
|
|
118
127
|
* default: false,
|
|
@@ -137,20 +146,17 @@ class BasePlugin {
|
|
|
137
146
|
/**
|
|
138
147
|
* Listen to the state changes propagated by {@link BasePlugin.propagateStateChange}
|
|
139
148
|
*
|
|
140
|
-
* @param {
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* Execute the function to delete the listener from the callback list.
|
|
144
|
-
* @see {@link client.Plugin#propagateStateChange}
|
|
145
|
-
* @see {@link server.Plugin#propagateStateChange}
|
|
149
|
+
* @param {pluginOnStateChangeCallback} callback - Callback to execute when a state change is propagated.
|
|
150
|
+
* @returns {pluginDeleteOnStateChangeCallback}
|
|
151
|
+
*
|
|
146
152
|
* @example
|
|
147
153
|
* const unsubscribe = plugin.onStateChange(pluginState => console.log(pluginState));
|
|
148
154
|
* // stop listening state changes
|
|
149
155
|
* unsubscribe();
|
|
150
156
|
*/
|
|
151
157
|
onStateChange(callback) {
|
|
152
|
-
this.
|
|
153
|
-
return () => this.
|
|
158
|
+
this.#onStateChangeCallbacks.add(callback);
|
|
159
|
+
return () => this.#onStateChangeCallbacks.delete(callback);
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
/**
|
|
@@ -159,14 +165,13 @@ class BasePlugin {
|
|
|
159
165
|
* through the `PluginManager#onStateChange` listeners.
|
|
160
166
|
*
|
|
161
167
|
* @param {object} updates - Updates to be merged in the plugin state.
|
|
162
|
-
*
|
|
163
|
-
* @see {@link
|
|
164
|
-
* @see {@link
|
|
165
|
-
* @see {@link server.PluginManager#onStateChange}
|
|
168
|
+
*
|
|
169
|
+
* @see {@link BasePlugin#onStateChange}
|
|
170
|
+
* @see {@link BasePluginManager#onStateChange}
|
|
166
171
|
*/
|
|
167
172
|
propagateStateChange(updates) {
|
|
168
|
-
merge(this.state, updates);
|
|
169
|
-
this.
|
|
173
|
+
this.state = merge(this.state, updates);
|
|
174
|
+
this.#onStateChangeCallbacks.forEach(callback => callback(this.state));
|
|
170
175
|
}
|
|
171
176
|
}
|
|
172
177
|
|