@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
package/src/client/Client.js
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
import { isBrowser, isPlainObject } from '@ircam/sc-utils';
|
|
1
|
+
import { isBrowser, isPlainObject, isString } from '@ircam/sc-utils';
|
|
2
|
+
|
|
3
|
+
import ClientContextManager, {
|
|
4
|
+
kClientContextManagerStart,
|
|
5
|
+
kClientContextManagerStop,
|
|
6
|
+
} from './ClientContextManager.js';
|
|
7
|
+
import ClientPluginManager from './ClientPluginManager.js';
|
|
8
|
+
import {
|
|
9
|
+
kPluginManagerStart,
|
|
10
|
+
kPluginManagerStop,
|
|
11
|
+
} from '../common/BasePluginManager.js';
|
|
12
|
+
import ClientSocket, {
|
|
13
|
+
kSocketTerminate,
|
|
14
|
+
} from './ClientSocket.js';
|
|
15
|
+
import ClientStateManager from './ClientStateManager.js';
|
|
16
|
+
import {
|
|
17
|
+
kStateManagerInit,
|
|
18
|
+
} from '../common/BaseStateManager.js';
|
|
2
19
|
|
|
3
|
-
import ContextManager from './ContextManager.js';
|
|
4
|
-
import PluginManager from './PluginManager.js';
|
|
5
|
-
import Socket from './Socket.js';
|
|
6
|
-
import StateManager from './StateManager.js';
|
|
7
20
|
import {
|
|
8
21
|
CLIENT_HANDSHAKE_REQUEST,
|
|
9
22
|
CLIENT_HANDSHAKE_RESPONSE,
|
|
@@ -11,78 +24,68 @@ import {
|
|
|
11
24
|
AUDIT_STATE_NAME,
|
|
12
25
|
} from '../common/constants.js';
|
|
13
26
|
import logger from '../common/logger.js';
|
|
27
|
+
import VERSION from '../common/version.js';
|
|
14
28
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* @typedef BrowserClientConfig
|
|
19
|
-
* @memberof client
|
|
20
|
-
* @type {object}
|
|
21
|
-
* @property {string} role - Role of the client in the application (e.g. 'player', 'controller').
|
|
22
|
-
* @property {object} [app] - Application configration object.
|
|
23
|
-
* @property {string} [app.name=''] - Name of the application.
|
|
24
|
-
* @property {string} [app.author=''] - Name of the author.
|
|
25
|
-
* @property {object} [env] - Environment configration object.
|
|
26
|
-
* @property {string} [env.websockets={}] - Configuration options for websockets.
|
|
27
|
-
* @property {string} [env.subpath=''] - If running behind a proxy, path to the application.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Configuration object for a client running in a node runtime.
|
|
32
|
-
*
|
|
33
|
-
* @typedef NodeClientConfig
|
|
34
|
-
* @memberof client
|
|
35
|
-
* @type {object}
|
|
36
|
-
* @property {string} role - Role of the client in the application (e.g. 'player', 'controller').
|
|
37
|
-
* @property {object} [app] - Application configration object.
|
|
38
|
-
* @property {string} [app.name=''] - Name of the application.
|
|
39
|
-
* @property {string} [app.author=''] - Name of the author.
|
|
40
|
-
* @property {object} env - Environment configration object.
|
|
41
|
-
* @property {boolean} env.serverAddress - Domain name or IP of the server.
|
|
42
|
-
* @property {boolean} env.useHttps - Define is the server run in http or in https.
|
|
43
|
-
* @property {boolean} env.port - Port on which the server is listening.
|
|
44
|
-
* @property {string} [env.websockets={}] - Configuration options for websockets.
|
|
45
|
-
* @property {string} [env.subpath=''] - If running behind a proxy, path to the application.
|
|
46
|
-
*/
|
|
29
|
+
// for testing purposes
|
|
30
|
+
export const kClientVersionTest = Symbol('soundworks:client-version-test');
|
|
31
|
+
export const kClientOnStatusChangeCallbacks = Symbol('soundworks:client-on-status-change-callbacks');
|
|
47
32
|
|
|
48
33
|
/**
|
|
49
34
|
* The `Client` class is the main entry point for the client-side of a soundworks
|
|
50
35
|
* application.
|
|
51
36
|
*
|
|
52
|
-
* A `
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
37
|
+
* A `soundworks` client can run seamlessly in a browser or in a Node.js runtime.
|
|
38
|
+
*
|
|
39
|
+
* It provides an access to the different soundworks components such as the {@link ClientStateManager},
|
|
40
|
+
* {@link ClientPluginManager}, {@link ClientSocket} and the {@link ClientContextManager}.
|
|
56
41
|
*
|
|
57
42
|
* ```
|
|
58
43
|
* import { Client } from '@soundworks/core/client.js';
|
|
59
|
-
* // create a
|
|
60
|
-
* const client = new Client({
|
|
61
|
-
*
|
|
44
|
+
* // create a `Client` instance
|
|
45
|
+
* const client = new Client({
|
|
46
|
+
* role: 'player',
|
|
47
|
+
* env: {
|
|
48
|
+
* useHttps: false,
|
|
49
|
+
* serverAddress: 'localhost',
|
|
50
|
+
* port: 8000,
|
|
51
|
+
* },
|
|
52
|
+
* });
|
|
53
|
+
* // start the client
|
|
62
54
|
* await client.start();
|
|
63
55
|
* ```
|
|
64
|
-
*
|
|
65
|
-
* @memberof client
|
|
66
56
|
*/
|
|
67
57
|
class Client {
|
|
58
|
+
#config = null;
|
|
59
|
+
#version = null;
|
|
60
|
+
#role = null;
|
|
61
|
+
#id = null;
|
|
62
|
+
#uuid = null;
|
|
63
|
+
#token = null;
|
|
64
|
+
#runtime = null;
|
|
65
|
+
#socket = null;
|
|
66
|
+
#contextManager = null;
|
|
67
|
+
#pluginManager = null;
|
|
68
|
+
#stateManager = null;
|
|
69
|
+
#status = 'idle';
|
|
70
|
+
#auditState = null;
|
|
71
|
+
|
|
68
72
|
/**
|
|
69
|
-
* @param {
|
|
70
|
-
* Configuration of the soundworks client.
|
|
73
|
+
* @param {ClientConfig} config - Configuration of the soundworks client.
|
|
71
74
|
* @throws Will throw if the given config object is invalid.
|
|
72
75
|
*/
|
|
73
76
|
constructor(config) {
|
|
74
77
|
if (!isPlainObject(config)) {
|
|
75
|
-
throw new Error(`
|
|
78
|
+
throw new Error(`Cannot construct Client: argument 1 must be an object`);
|
|
76
79
|
}
|
|
77
80
|
|
|
78
|
-
if (!('role' in config)) {
|
|
79
|
-
throw new Error('
|
|
81
|
+
if (!('role' in config) || !isString(config.role)) {
|
|
82
|
+
throw new Error('Cannot construct Client: Invalid ClientConfig object: Property "role" must be a string');
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
// for node clients env.https is
|
|
85
|
+
// for node clients env.https is required to open the websocket
|
|
83
86
|
if (!isBrowser()) {
|
|
84
87
|
if (!('env' in config)) {
|
|
85
|
-
throw new Error(
|
|
88
|
+
throw new Error(`Cannot construct Client: The 'env' property with type 'ClientEnvConfig' must be defined`);
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
let missing = [];
|
|
@@ -100,125 +103,197 @@ class Client {
|
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
if (missing.length) {
|
|
103
|
-
throw new Error(`
|
|
106
|
+
throw new Error(`Cannot construct Client: Invalid 'env' property of type 'ClientEnvConfig': Fields ${missing.join(', ')} are missing`);
|
|
104
107
|
}
|
|
105
108
|
}
|
|
106
109
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
* @type {client.BrowserClientConfig|client.NodeClientConfig}
|
|
118
|
-
*/
|
|
119
|
-
this.config = config;
|
|
120
|
-
|
|
121
|
-
if (!this.config.env) {
|
|
122
|
-
this.config.env = {};
|
|
110
|
+
this.#config = config;
|
|
111
|
+
|
|
112
|
+
if (!this.#config.env) {
|
|
113
|
+
this.#config.env = {};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.#version = VERSION;
|
|
117
|
+
// allow override though config for testing
|
|
118
|
+
if (config[kClientVersionTest]) {
|
|
119
|
+
this.#version = config[kClientVersionTest];
|
|
123
120
|
}
|
|
124
121
|
|
|
125
|
-
|
|
126
|
-
this
|
|
122
|
+
this.#role = config.role;
|
|
123
|
+
this.#runtime = isBrowser() ? 'browser' : 'node';
|
|
124
|
+
this.#socket = new ClientSocket(this.#role, this.#config, {
|
|
127
125
|
path: 'socket',
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
* @type {number}
|
|
137
|
-
*/
|
|
138
|
-
this.id = null;
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Unique session uuid of the client (uuidv4), generated and retrieved by
|
|
142
|
-
* the server during {@link client.Client#init}.
|
|
143
|
-
*
|
|
144
|
-
* @type {string}
|
|
145
|
-
*/
|
|
146
|
-
this.uuid = null;
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Instance of the {@link client.Socket} class that handle websockets communications with
|
|
150
|
-
* the server.
|
|
151
|
-
*
|
|
152
|
-
* @see {@link client.Socket}
|
|
153
|
-
* @type {client.Socket}
|
|
154
|
-
*/
|
|
155
|
-
this.socket = new Socket();
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Runtime platform on which the client is executed, i.e. 'browser' or 'node'.
|
|
159
|
-
*
|
|
160
|
-
* @type {string}
|
|
161
|
-
*/
|
|
162
|
-
this.target = isBrowser() ? 'browser' : 'node';
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Instance of the {@link client.ContextManager} class.
|
|
166
|
-
*
|
|
167
|
-
* The context manager can be safely used after `client.init()` has been fulfilled.
|
|
168
|
-
*
|
|
169
|
-
* @see {@link client.ContextManager}
|
|
170
|
-
* @type {client.ContextManager}
|
|
171
|
-
*/
|
|
172
|
-
this.contextManager = new ContextManager();
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Instance of the {@link client.PluginManager} class.
|
|
176
|
-
*
|
|
177
|
-
* The context manager can be safely used after `client.init()` has been fulfilled.
|
|
178
|
-
*
|
|
179
|
-
* @see {@link client.PluginManager}
|
|
180
|
-
* @type {client.PluginManager}
|
|
181
|
-
*/
|
|
182
|
-
this.pluginManager = new PluginManager(this);
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Instance of the {@link client.StateManager} class.
|
|
186
|
-
*
|
|
187
|
-
* The context manager can be safely used after `client.init()` has been fulfilled.
|
|
188
|
-
*
|
|
189
|
-
* @see {@link client.StateManager}
|
|
190
|
-
* @type {client.StateManager}
|
|
191
|
-
*/
|
|
192
|
-
this.stateManager = null;
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Status of the client, 'idle', 'inited', 'started' or 'errored'.
|
|
196
|
-
*
|
|
197
|
-
* @type {string}
|
|
198
|
-
*/
|
|
199
|
-
this.status = 'idle';
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Token of the client if connected through HTTP authentication.
|
|
203
|
-
* @private
|
|
204
|
-
*/
|
|
205
|
-
this.token = null;
|
|
206
|
-
|
|
207
|
-
/** @private */
|
|
208
|
-
this._onStatusChangeCallbacks = new Set();
|
|
209
|
-
/** @private */
|
|
210
|
-
this._auditState = null;
|
|
126
|
+
retryConnectionRefusedTimeout: 1000,
|
|
127
|
+
});
|
|
128
|
+
this.#contextManager = new ClientContextManager();
|
|
129
|
+
this.#pluginManager = new ClientPluginManager(this);
|
|
130
|
+
this.#stateManager = new ClientStateManager();
|
|
131
|
+
this.#status = 'idle';
|
|
132
|
+
|
|
133
|
+
this[kClientOnStatusChangeCallbacks] = new Set();
|
|
211
134
|
|
|
212
135
|
logger.configure(!!config.env.verbose);
|
|
213
136
|
}
|
|
214
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Package version.
|
|
140
|
+
*
|
|
141
|
+
* @type {string}
|
|
142
|
+
*/
|
|
143
|
+
get version() {
|
|
144
|
+
return this.#version;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Role of the client in the application.
|
|
149
|
+
*
|
|
150
|
+
* @type {string}
|
|
151
|
+
*/
|
|
152
|
+
get role() {
|
|
153
|
+
return this.#role;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Configuration object.
|
|
158
|
+
*
|
|
159
|
+
* @type {ClientConfig}
|
|
160
|
+
*/
|
|
161
|
+
get config() {
|
|
162
|
+
return this.#config;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Session id of the client.
|
|
167
|
+
*
|
|
168
|
+
* Incremented positive integer generated and retrieved by the server during
|
|
169
|
+
* {@link Client#init}. The counter is reset when the server restarts.
|
|
170
|
+
*
|
|
171
|
+
* @type {number}
|
|
172
|
+
*/
|
|
173
|
+
get id() {
|
|
174
|
+
return this.#id;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Unique session uuid of the client (uuidv4).
|
|
179
|
+
*
|
|
180
|
+
* Generated and retrieved by the server during {@link Client#init}.
|
|
181
|
+
* @type {string}
|
|
182
|
+
*/
|
|
183
|
+
get uuid() {
|
|
184
|
+
return this.#uuid;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Authentication token generated by the server.
|
|
189
|
+
*
|
|
190
|
+
* Only set if client has gone through authentication process and is considered trusted.
|
|
191
|
+
*
|
|
192
|
+
* Generated and retrieved by the server during {@link Client#init}.
|
|
193
|
+
* @type {string}
|
|
194
|
+
*/
|
|
195
|
+
get token() {
|
|
196
|
+
return this.#token;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Runtime platform on which the client is executed, i.e. 'browser' or 'node'.
|
|
201
|
+
*
|
|
202
|
+
* @type {'node'|'browser'}
|
|
203
|
+
*/
|
|
204
|
+
get runtime() {
|
|
205
|
+
return this.#runtime;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @deprecated Use {@link Client#runtime} instead.
|
|
210
|
+
*/
|
|
211
|
+
get target() {
|
|
212
|
+
logger.deprecated('Client#target', 'Client#runtime', '4.0.0-alpha.29');
|
|
213
|
+
return this.runtime;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Instance of the {@link client.Socket} class.
|
|
218
|
+
*
|
|
219
|
+
* @see {@link ClientSocket}
|
|
220
|
+
* @type {ClientSocket}
|
|
221
|
+
*/
|
|
222
|
+
get socket() {
|
|
223
|
+
return this.#socket;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Instance of the {@link ClientContextManager} class.
|
|
228
|
+
*
|
|
229
|
+
* The context manager can be safely used after `client.init()` has been fulfilled.
|
|
230
|
+
*
|
|
231
|
+
* @see {@link ClientContextManager}
|
|
232
|
+
* @type {ClientContextManager}
|
|
233
|
+
*/
|
|
234
|
+
get contextManager() {
|
|
235
|
+
return this.#contextManager;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Instance of the {@link ClientPluginManager} class.
|
|
240
|
+
*
|
|
241
|
+
* The context manager can be safely used after `client.init()` has been fulfilled.
|
|
242
|
+
*
|
|
243
|
+
* @see {@link ClientPluginManager}
|
|
244
|
+
* @type {ClientPluginManager}
|
|
245
|
+
*/
|
|
246
|
+
get pluginManager() {
|
|
247
|
+
return this.#pluginManager;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Instance of the {@link ClientStateManager} class.
|
|
252
|
+
*
|
|
253
|
+
* The context manager can be safely used after `client.init()` has been fulfilled.
|
|
254
|
+
*
|
|
255
|
+
* @see {@link ClientStateManager}
|
|
256
|
+
* @type {ClientStateManager}
|
|
257
|
+
*/
|
|
258
|
+
get stateManager() {
|
|
259
|
+
return this.#stateManager;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Status of the client.
|
|
264
|
+
*
|
|
265
|
+
* @type {'idle'|'inited'|'started'|'errored'}
|
|
266
|
+
*/
|
|
267
|
+
get status() {
|
|
268
|
+
return this.#status;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** @private */
|
|
272
|
+
async #dispatchStatus(status) {
|
|
273
|
+
this.#status = status;
|
|
274
|
+
|
|
275
|
+
// if node target and launched in a child process, forward status to parent process
|
|
276
|
+
if (this.#runtime === 'node' && process.send !== undefined) {
|
|
277
|
+
process.send(`soundworks:client:${status}`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// execute all callbacks in parallel
|
|
281
|
+
const promises = [];
|
|
282
|
+
|
|
283
|
+
for (let callback of this[kClientOnStatusChangeCallbacks]) {
|
|
284
|
+
promises.push(callback(status));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
await Promise.all(promises);
|
|
288
|
+
}
|
|
289
|
+
|
|
215
290
|
/**
|
|
216
291
|
* The `init` method is part of the initialization lifecycle of the `soundworks`
|
|
217
|
-
* client. Most of the time,
|
|
218
|
-
* {@link
|
|
292
|
+
* client. Most of the time, this method will be implicitly executed by the
|
|
293
|
+
* {@link Client#start} method.
|
|
219
294
|
*
|
|
220
295
|
* In some situations you might want to call this method manually, in such cases
|
|
221
|
-
* the method should be called before the {@link
|
|
296
|
+
* the method should be called before the {@link Client#start} method.
|
|
222
297
|
*
|
|
223
298
|
* What it does:
|
|
224
299
|
* - connect the sockets to be server
|
|
@@ -226,89 +301,81 @@ class Client {
|
|
|
226
301
|
* - launch the state manager
|
|
227
302
|
* - initialize all registered plugin
|
|
228
303
|
*
|
|
229
|
-
* After `await client.init()` is fulfilled, the {@link
|
|
230
|
-
* the {@link
|
|
304
|
+
* After `await client.init()` is fulfilled, the {@link Client#stateManager},
|
|
305
|
+
* the {@link Client#pluginManager} and the {@link Client#socket}
|
|
231
306
|
* can be safely used.
|
|
232
307
|
*
|
|
233
308
|
* @example
|
|
234
309
|
* import { Client } from '@soundworks/core/client.js'
|
|
235
310
|
*
|
|
236
311
|
* const client = new Client(config);
|
|
237
|
-
* //
|
|
312
|
+
* // optional explicit call of `init` before `start`
|
|
238
313
|
* await client.init();
|
|
239
314
|
* await client.start();
|
|
240
315
|
*/
|
|
241
316
|
async init() {
|
|
242
|
-
|
|
243
|
-
|
|
317
|
+
if (this.#status !== 'idle') {
|
|
318
|
+
throw new DOMException(`Cannot execute 'init' on Client: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// init socket communications
|
|
322
|
+
await this.#socket.init();
|
|
244
323
|
|
|
245
324
|
// we need the try/catch block to change the promise rejection into proper error
|
|
246
325
|
try {
|
|
247
326
|
await new Promise((resolve, reject) => {
|
|
248
327
|
// wait for handshake response before starting stateManager and pluginManager
|
|
249
|
-
this
|
|
250
|
-
this
|
|
251
|
-
this
|
|
252
|
-
this
|
|
328
|
+
this.#socket.addListener(CLIENT_HANDSHAKE_RESPONSE, async ({ id, uuid, token, version }) => {
|
|
329
|
+
this.#id = id;
|
|
330
|
+
this.#uuid = uuid;
|
|
331
|
+
this.#token = token;
|
|
332
|
+
|
|
333
|
+
if (version !== this.#version) {
|
|
334
|
+
logger.warnVersionDiscrepancies(this.#role, this.#version, version);
|
|
335
|
+
}
|
|
253
336
|
|
|
254
337
|
resolve();
|
|
255
338
|
});
|
|
256
339
|
|
|
257
|
-
this
|
|
258
|
-
let msg =
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
case 'invalid-client-type':
|
|
262
|
-
msg = `[soundworks:Client] ${err.message}`;
|
|
263
|
-
break;
|
|
264
|
-
case 'invalid-plugin-list':
|
|
265
|
-
msg = `[soundworks:Client] ${err.message}`;
|
|
266
|
-
break;
|
|
267
|
-
default:
|
|
268
|
-
msg = `Undefined error`;
|
|
269
|
-
break;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
this.socket.terminate();
|
|
340
|
+
this.#socket.addListener(CLIENT_HANDSHAKE_ERROR, (err) => {
|
|
341
|
+
let msg = `Cannot execute 'init' on Client: ${err.message}`;
|
|
342
|
+
// These are development errors, we can just hang. If we terminate the
|
|
343
|
+
// socket, a reload is triggered by the launcher which is not good DX.
|
|
273
344
|
reject(msg);
|
|
274
345
|
});
|
|
275
346
|
|
|
276
347
|
// send handshake request
|
|
277
348
|
const payload = {
|
|
278
|
-
role: this
|
|
279
|
-
|
|
349
|
+
role: this.#role,
|
|
350
|
+
version: this.#version,
|
|
351
|
+
registeredPlugins: this.#pluginManager.getRegisteredPlugins(),
|
|
280
352
|
};
|
|
281
353
|
|
|
282
|
-
this
|
|
354
|
+
this.#socket.send(CLIENT_HANDSHAKE_REQUEST, payload);
|
|
283
355
|
});
|
|
284
|
-
} catch (
|
|
285
|
-
throw new Error(
|
|
356
|
+
} catch (msg) {
|
|
357
|
+
throw new Error(msg);
|
|
286
358
|
}
|
|
287
359
|
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
addListener: this.socket.addListener.bind(this.socket),
|
|
294
|
-
removeAllListeners: this.socket.removeAllListeners.bind(this.socket),
|
|
360
|
+
// init state manager
|
|
361
|
+
this.#stateManager[kStateManagerInit](this.id, {
|
|
362
|
+
emit: this.#socket.send.bind(this.#socket), // need to alias this
|
|
363
|
+
addListener: this.#socket.addListener.bind(this.#socket),
|
|
364
|
+
removeAllListeners: this.#socket.removeAllListeners.bind(this.#socket),
|
|
295
365
|
});
|
|
296
366
|
|
|
297
|
-
|
|
298
|
-
// INIT PLUGIN MANAGER
|
|
299
|
-
// ------------------------------------------------------------
|
|
300
|
-
await this.pluginManager.start();
|
|
367
|
+
await this.#pluginManager[kPluginManagerStart]();
|
|
301
368
|
|
|
302
|
-
await this
|
|
369
|
+
await this.#dispatchStatus('inited');
|
|
303
370
|
}
|
|
304
371
|
|
|
305
372
|
/**
|
|
306
373
|
* The `start` method is part of the initialization lifecycle of the `soundworks`
|
|
307
|
-
* client.
|
|
308
|
-
*
|
|
374
|
+
* client. This method will implicitly execute {@link Client#init} method if it
|
|
375
|
+
* has not been called manually.
|
|
309
376
|
*
|
|
310
377
|
* What it does:
|
|
311
|
-
* - implicitly call {@link
|
|
378
|
+
* - implicitly call {@link Client#init} if not done manually
|
|
312
379
|
* - start all created contexts. For that to happen, you will have to call `client.init`
|
|
313
380
|
* manually and instantiate the contexts between `client.init()` and `client.start()`
|
|
314
381
|
*
|
|
@@ -319,31 +386,24 @@ class Client {
|
|
|
319
386
|
* await client.start();
|
|
320
387
|
*/
|
|
321
388
|
async start() {
|
|
322
|
-
|
|
389
|
+
// lazily call init for convenience
|
|
390
|
+
if (this.#status === 'idle') {
|
|
323
391
|
await this.init();
|
|
324
392
|
}
|
|
325
393
|
|
|
326
|
-
if (this
|
|
327
|
-
throw new
|
|
394
|
+
if (this.#status !== 'inited') {
|
|
395
|
+
throw new DOMException(`Cannot execute 'start' on Client: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
|
|
328
396
|
}
|
|
329
397
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// ------------------------------------------------------------
|
|
335
|
-
// START CONTEXT MANAGER
|
|
336
|
-
// ------------------------------------------------------------
|
|
337
|
-
await this.contextManager.start();
|
|
338
|
-
|
|
339
|
-
await this._dispatchStatus('started');
|
|
398
|
+
await this.#contextManager[kClientContextManagerStart]();
|
|
399
|
+
await this.#dispatchStatus('started');
|
|
340
400
|
}
|
|
341
401
|
|
|
342
402
|
/**
|
|
343
403
|
* Stops all started contexts, plugins and terminates the socket connections.
|
|
344
404
|
*
|
|
345
405
|
* In most situations, you might not need to call this method. However, it can
|
|
346
|
-
* be
|
|
406
|
+
* be useful for unit testing or similar situations where you want to create
|
|
347
407
|
* and delete several clients in the same process.
|
|
348
408
|
*
|
|
349
409
|
* @example
|
|
@@ -356,76 +416,66 @@ class Client {
|
|
|
356
416
|
* await client.stop();
|
|
357
417
|
*/
|
|
358
418
|
async stop() {
|
|
359
|
-
if (this
|
|
360
|
-
throw new
|
|
419
|
+
if (this.#status !== 'started') {
|
|
420
|
+
throw new DOMException(`Cannot execute 'stop' on Client: Lifecycle methods must be called in following order: init, start, stop`, 'InvalidAccessError');
|
|
361
421
|
}
|
|
362
422
|
|
|
363
|
-
await this
|
|
364
|
-
await this
|
|
365
|
-
await this
|
|
423
|
+
await this.#contextManager[kClientContextManagerStop]();
|
|
424
|
+
await this.#pluginManager[kPluginManagerStop]();
|
|
425
|
+
await this.#socket[kSocketTerminate]();
|
|
366
426
|
|
|
367
|
-
await this
|
|
427
|
+
await this.#dispatchStatus('stopped');
|
|
368
428
|
}
|
|
369
429
|
|
|
370
430
|
/**
|
|
371
431
|
* Attach and retrieve the global audit state of the application.
|
|
372
432
|
*
|
|
373
|
-
* The audit state is a {@link
|
|
374
|
-
* global
|
|
375
|
-
* clients, network latency estimation, etc. It is
|
|
433
|
+
* The audit state is a {@link SharedState} instance that keeps track of
|
|
434
|
+
* global information about the application such as, the number of connected
|
|
435
|
+
* clients, network latency estimation, etc. It is useful for controller client
|
|
376
436
|
* roles to give the user an overview about the state of the application.
|
|
377
437
|
*
|
|
378
438
|
* The audit state is lazily attached to the client only if this method is called.
|
|
379
439
|
*
|
|
380
|
-
* @returns {Promise<
|
|
440
|
+
* @returns {Promise<SharedState>}
|
|
381
441
|
* @throws Will throw if called before `client.init()`
|
|
382
|
-
* @see {@link
|
|
442
|
+
* @see {@link SharedState}
|
|
383
443
|
* @example
|
|
384
444
|
* const auditState = await client.getAuditState();
|
|
385
445
|
* auditState.onUpdate(() => console.log(auditState.getValues()), true);
|
|
386
446
|
*/
|
|
387
447
|
async getAuditState() {
|
|
388
|
-
if (this
|
|
389
|
-
throw new
|
|
448
|
+
if (this.#status === 'idle') {
|
|
449
|
+
throw new DOMException(`Cannot execute 'getAuditState' on Server: 'init' must be called first`, 'InvalidAccessError');
|
|
390
450
|
}
|
|
391
451
|
|
|
392
|
-
if (this
|
|
393
|
-
this
|
|
452
|
+
if (this.#auditState === null) {
|
|
453
|
+
this.#auditState = await this.#stateManager.attach(AUDIT_STATE_NAME);
|
|
394
454
|
}
|
|
395
455
|
|
|
396
|
-
return this
|
|
456
|
+
return this.#auditState;
|
|
397
457
|
}
|
|
398
458
|
|
|
399
459
|
/**
|
|
400
460
|
* Listen for the status change ('inited', 'started', 'stopped') of the client.
|
|
401
461
|
*
|
|
402
462
|
* @param {Function} callback - Listener to the status change.
|
|
403
|
-
* @returns {Function}
|
|
463
|
+
* @returns {Function} Function that delete the listener when executed.
|
|
404
464
|
*/
|
|
405
465
|
onStatusChange(callback) {
|
|
406
|
-
this.
|
|
407
|
-
|
|
408
|
-
return () => this._onStatusChangeCallbacks.delete(callback);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/** @private */
|
|
412
|
-
async _dispatchStatus(status) {
|
|
413
|
-
this.status = status;
|
|
414
|
-
|
|
415
|
-
// if node target and launched in a child process, forward status to parent process
|
|
416
|
-
if (this.target === 'node' && process.send !== undefined) {
|
|
417
|
-
process.send(`soundworks:client:${status}`);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// execute all callbacks in parallel
|
|
421
|
-
const promises = [];
|
|
422
|
-
|
|
423
|
-
for (let callback of this._onStatusChangeCallbacks) {
|
|
424
|
-
promises.push(callback(status));
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
await Promise.all(promises);
|
|
466
|
+
this[kClientOnStatusChangeCallbacks].add(callback);
|
|
467
|
+
return () => this[kClientOnStatusChangeCallbacks].delete(callback);
|
|
428
468
|
}
|
|
429
469
|
}
|
|
430
470
|
|
|
471
|
+
Object.defineProperties(Client.prototype, {
|
|
472
|
+
[Symbol.toStringTag]: {
|
|
473
|
+
__proto__: null,
|
|
474
|
+
writable: false,
|
|
475
|
+
enumerable: false,
|
|
476
|
+
configurable: true,
|
|
477
|
+
value: 'Client',
|
|
478
|
+
},
|
|
479
|
+
});
|
|
480
|
+
|
|
431
481
|
export default Client;
|