@soundworks/core 4.0.0-alpha.27 → 4.0.0-alpha.29
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/.jsdoc +3 -1
- package/CHANGELOG.md +15 -5
- package/package.json +5 -5
- package/src/client/Client.js +78 -83
- package/src/client/{Context.js → ClientContext.js} +48 -38
- package/src/client/ClientContextManager.js +115 -0
- package/src/client/{Plugin.js → ClientPlugin.js} +22 -18
- package/src/client/{PluginManager.js → ClientPluginManager.js} +39 -35
- package/src/client/{Socket.js → ClientSocket.js} +54 -65
- package/src/client/{StateManager.js → ClientStateManager.js} +7 -8
- package/src/client/index.js +17 -41
- package/src/common/BasePlugin.js +56 -51
- package/src/common/BasePluginManager.js +175 -139
- package/src/common/BaseStateManager.js +316 -120
- package/src/common/BatchedTransport.js +24 -24
- package/src/common/ParameterBag.js +18 -6
- package/src/common/PromiseStore.js +1 -0
- package/src/common/{BaseSharedState.js → SharedState.js} +271 -199
- package/src/common/{BaseSharedStateCollection.js → SharedStateCollection.js} +92 -61
- package/src/common/logger.js +5 -0
- package/src/common/shared-state-types.js +76 -0
- package/src/common/version.js +1 -1
- package/src/server/Server.js +391 -354
- package/src/server/ServerClient.js +73 -0
- package/src/server/{Context.js → ServerContext.js} +85 -66
- package/src/server/{ContextManager.js → ServerContextManager.js} +98 -85
- package/src/server/{Plugin.js → ServerPlugin.js} +10 -24
- package/src/server/{PluginManager.js → ServerPluginManager.js} +76 -69
- package/src/server/{Socket.js → ServerSocket.js} +19 -18
- package/src/server/{Sockets.js → ServerSockets.js} +36 -27
- package/src/server/ServerStateManager.js +515 -0
- package/src/{common → server}/SharedStatePrivate.js +61 -26
- package/src/server/audit-network-latency.worker.js +9 -7
- package/src/server/audit-schema.js +1 -1
- package/src/server/crypto.js +2 -0
- package/src/server/index.js +18 -21
- package/types/client/Client.d.ts +74 -95
- package/types/client/{Context.d.ts → ClientContext.d.ts} +25 -26
- package/types/client/{ContextManager.d.ts → ClientContextManager.d.ts} +19 -22
- package/types/client/{Plugin.d.ts → ClientPlugin.d.ts} +17 -20
- package/types/client/{PluginManager.d.ts → ClientPluginManager.d.ts} +13 -36
- package/types/client/ClientSocket.d.ts +74 -0
- package/types/client/{StateManager.d.ts → ClientStateManager.d.ts} +11 -15
- package/types/client/index.d.ts +26 -2
- package/types/common/BasePlugin.d.ts +52 -46
- package/types/common/BasePluginManager.d.ts +68 -44
- package/types/common/BaseStateManager.d.ts +207 -97
- package/types/common/BatchedTransport.d.ts +1 -5
- package/types/common/ParameterBag.d.ts +16 -18
- package/types/common/PromiseStore.d.ts +1 -1
- package/types/common/SharedState.d.ts +297 -0
- package/types/common/{BaseSharedStateCollection.d.ts → SharedStateCollection.d.ts} +34 -18
- package/types/common/constants.d.ts +0 -2
- package/types/common/logger.d.ts +2 -1
- package/types/common/shared-state-types.d.ts +83 -0
- package/types/common/sockets-utils.d.ts +0 -5
- package/types/common/version.d.ts +1 -2
- package/types/server/Server.d.ts +78 -132
- package/types/server/ServerClient.d.ts +45 -0
- package/types/server/{Context.d.ts → ServerContext.d.ts} +53 -43
- package/types/server/ServerContextManager.d.ts +80 -0
- package/types/server/{Plugin.d.ts → ServerPlugin.d.ts} +19 -33
- package/types/server/{PluginManager.d.ts → ServerPluginManager.d.ts} +39 -37
- package/types/server/ServerSocket.d.ts +85 -0
- package/types/server/ServerSockets.d.ts +50 -0
- package/types/server/ServerStateManager.d.ts +189 -0
- package/types/server/SharedStatePrivate.d.ts +21 -0
- package/types/server/audit-network-latency.worker.d.ts +1 -2
- package/types/server/audit-schema.d.ts +0 -1
- package/types/server/crypto.d.ts +2 -1
- package/types/server/index.d.ts +24 -2
- package/src/client/ContextManager.js +0 -111
- package/src/client/SharedState.js +0 -84
- package/src/client/SharedStateCollection.js +0 -38
- package/src/server/Client.js +0 -55
- package/src/server/SharedState.js +0 -85
- package/src/server/SharedStateCollection.js +0 -39
- package/src/server/StateManager.js +0 -696
- 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.map +0 -1
- 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/SharedStateCollection.d.ts +0 -41
- package/types/client/SharedStateCollection.d.ts.map +0 -1
- package/types/client/Socket.d.ts +0 -149
- 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 -244
- package/types/common/BaseSharedState.d.ts.map +0 -1
- package/types/common/BaseSharedStateCollection.d.ts.map +0 -1
- package/types/common/BaseStateManager.d.ts.map +0 -1
- package/types/common/BatchedTransport.d.ts.map +0 -1
- package/types/common/ParameterBag.d.ts.map +0 -1
- package/types/common/PromiseStore.d.ts.map +0 -1
- package/types/common/SharedStatePrivate.d.ts +0 -21
- 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/sockets-utils.d.ts.map +0 -1
- package/types/common/version.d.ts.map +0 -1
- package/types/server/Client.d.ts +0 -45
- package/types/server/Client.d.ts.map +0 -1
- 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.map +0 -1
- 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/SharedStateCollection.d.ts +0 -41
- package/types/server/SharedStateCollection.d.ts.map +0 -1
- package/types/server/Socket.d.ts +0 -164
- package/types/server/Socket.d.ts.map +0 -1
- package/types/server/Sockets.d.ts +0 -80
- package/types/server/Sockets.d.ts.map +0 -1
- package/types/server/StateManager.d.ts +0 -405
- 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/crypto.d.ts.map +0 -1
- package/types/server/index.d.ts.map +0 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
kClientContextStatus,
|
|
3
|
+
} from './ClientContext.js';
|
|
4
|
+
|
|
5
|
+
export const kClientContextManagerStart = Symbol('soundworks:client-context-manager-start');
|
|
6
|
+
export const kClientContextManagerStop = Symbol('soundworks:client-context-manager-stop');
|
|
7
|
+
export const kClientContextManagerRegister = Symbol('soundworks:client-context-manager-register');
|
|
8
|
+
|
|
9
|
+
/** @private */
|
|
10
|
+
class ContextCollection {
|
|
11
|
+
#inner = [];
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.#inner = [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
add(context) {
|
|
18
|
+
this.#inner.push(context);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
has(name) {
|
|
22
|
+
return this.#inner.find(c => c.name === name) !== undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get(name) {
|
|
26
|
+
return this.#inner.find(c => c.name === name);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
map(func) {
|
|
30
|
+
return this.#inner.map(func);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Manage the different contexts and their lifecycle.
|
|
36
|
+
*
|
|
37
|
+
* See {@link Client#contextManager}
|
|
38
|
+
*
|
|
39
|
+
* _WARNING: In most cases, you should not have to manipulate the context manager directly._
|
|
40
|
+
*
|
|
41
|
+
* @hideconstructor
|
|
42
|
+
*/
|
|
43
|
+
class ClientContextManager {
|
|
44
|
+
#contexts = new ContextCollection();
|
|
45
|
+
#contextStartPromises = new Map();
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Register the context into the manager. Is called in context constructor.
|
|
49
|
+
*
|
|
50
|
+
* @param {ClientContext} context - The context to be registered.
|
|
51
|
+
* @private
|
|
52
|
+
*/
|
|
53
|
+
[kClientContextManagerRegister](context) {
|
|
54
|
+
if (this.#contexts.has(context.name)) {
|
|
55
|
+
throw new Error(`[soundworks:context-manager] Context "${context.name}" already registered`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.#contexts.add(context);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Start all registered contexts. Called during `client.start()`
|
|
63
|
+
* @private
|
|
64
|
+
*/
|
|
65
|
+
async [kClientContextManagerStart]() {
|
|
66
|
+
const promises = this.#contexts.map(context => this.get(context.name));
|
|
67
|
+
await Promise.all(promises);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Stop all registered contexts. Called during `client.stop()`
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
async [kClientContextManagerStop]() {
|
|
75
|
+
const promises = this.#contexts.map(context => context.stop());
|
|
76
|
+
await Promise.all(promises);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Retrieve a started context from its name.
|
|
81
|
+
*
|
|
82
|
+
* _WARNING: Most of the time, you should not have to call this method manually._
|
|
83
|
+
*
|
|
84
|
+
* @param {string} contextName - Name of the context.
|
|
85
|
+
* @returns {Promise<ClientContext>}
|
|
86
|
+
*/
|
|
87
|
+
async get(contextName) {
|
|
88
|
+
if (!this.#contexts.has(contextName)) {
|
|
89
|
+
throw new Error(`[soundworks:ClientContextManager] Can't get context "${contextName}", not registered`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const context = this.#contexts.get(contextName);
|
|
93
|
+
|
|
94
|
+
if (this.#contextStartPromises.has(contextName)) {
|
|
95
|
+
const startPromise = this.#contextStartPromises.get(contextName);
|
|
96
|
+
await startPromise;
|
|
97
|
+
} else {
|
|
98
|
+
context[kClientContextStatus] = 'inited';
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const startPromise = context.start();
|
|
102
|
+
this.#contextStartPromises.set(contextName, startPromise);
|
|
103
|
+
await startPromise;
|
|
104
|
+
context[kClientContextStatus] = 'started';
|
|
105
|
+
} catch (err) {
|
|
106
|
+
context[kClientContextStatus] = 'errored';
|
|
107
|
+
throw new Error(err);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return context;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export default ClientContextManager;
|
|
@@ -3,14 +3,14 @@ import BasePlugin from '../common/BasePlugin.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Callback executed when the plugin state is updated.
|
|
5
5
|
*
|
|
6
|
-
* @callback
|
|
7
|
-
* @param {
|
|
6
|
+
* @callback ClientPlugin~onStateChangeCallback
|
|
7
|
+
* @param {ClientPlugin#state} state - Current state of the plugin.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Delete the registered {@link
|
|
11
|
+
* Delete the registered {@link ClientPlugin~onStateChangeCallback}.
|
|
12
12
|
*
|
|
13
|
-
* @callback
|
|
13
|
+
* @callback ClientPlugin~deleteOnStateChangeCallback
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -28,40 +28,44 @@ import BasePlugin from '../common/BasePlugin.js';
|
|
|
28
28
|
*
|
|
29
29
|
* _Creating new plugins should be considered an advanced usage._
|
|
30
30
|
*
|
|
31
|
-
* @memberof client
|
|
32
31
|
* @extends BasePlugin
|
|
33
32
|
* @inheritdoc
|
|
34
33
|
*/
|
|
35
|
-
class
|
|
34
|
+
class ClientPlugin extends BasePlugin {
|
|
35
|
+
#client = null;
|
|
36
36
|
/**
|
|
37
|
-
* @param {
|
|
37
|
+
* @param {Client} client - A soundworks client instance.
|
|
38
38
|
* @param {string} id - User defined id of the plugin as defined in
|
|
39
|
-
* {@link
|
|
39
|
+
* {@link ClientPluginManager#register}.
|
|
40
40
|
*/
|
|
41
41
|
constructor(client, id) {
|
|
42
42
|
super(id);
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
this.#client = client;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Instance of soundworks client.
|
|
49
|
+
*
|
|
50
|
+
* @type {Client}
|
|
51
|
+
* @see {@link Client}
|
|
52
|
+
*/
|
|
53
|
+
get client() {
|
|
54
|
+
return this.#client;
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
// @todo
|
|
54
58
|
// /**
|
|
55
59
|
// * Interface method to implement if specific logic should be done when a
|
|
56
|
-
// * {@link
|
|
60
|
+
// * {@link Client} enters the plugin.
|
|
57
61
|
// */
|
|
58
62
|
// async activate() {}
|
|
59
63
|
//
|
|
60
64
|
// /**
|
|
61
65
|
// * Interface method to implement if specific logic should be done when a
|
|
62
|
-
// * {@link
|
|
66
|
+
// * {@link Client} exists the plugin.
|
|
63
67
|
// */
|
|
64
68
|
// async deactivate() {}
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
export default
|
|
71
|
+
export default ClientPlugin;
|
|
@@ -1,28 +1,12 @@
|
|
|
1
1
|
import BasePluginManager from '../common/BasePluginManager.js';
|
|
2
|
-
import
|
|
2
|
+
import ClientPlugin from './ClientPlugin.js';
|
|
3
3
|
import Client from './Client.js';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Callback executed when a plugin internal state is updated.
|
|
7
|
-
*
|
|
8
|
-
* @callback client.PluginManager~onStateChangeCallback
|
|
9
|
-
* @param {object<client.Plugin#id, client.Plugin>} fullState - List of all plugins.
|
|
10
|
-
* @param {client.Plugin|null} initiator - Plugin that initiated the update or `null`
|
|
11
|
-
* if the change was initiated by the state manager (i.e. when the initialization
|
|
12
|
-
* of the plugins starts).
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Delete the registered {@link client.PluginManager~onStateChangeCallback}.
|
|
17
|
-
*
|
|
18
|
-
* @callback client.PluginManager~deleteOnStateChangeCallback
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
5
|
/**
|
|
22
6
|
* The `PluginManager` allows to register and retrieve `soundworks` plugins.
|
|
23
7
|
*
|
|
24
8
|
* Plugins should always be registered both client-side and server-side,
|
|
25
|
-
* and before {@link
|
|
9
|
+
* and before {@link Client#start} or {@link Server#start}
|
|
26
10
|
* to be properly initialized.
|
|
27
11
|
*
|
|
28
12
|
* In some sitautions, you might want to register the same plugin factory several times
|
|
@@ -32,7 +16,7 @@ import Client from './Client.js';
|
|
|
32
16
|
* they expose. See [https://soundworks.dev/guide/ecosystem](https://soundworks.dev/guide/ecosystem)
|
|
33
17
|
* for more informations on the available plugins.
|
|
34
18
|
*
|
|
35
|
-
* See {@link
|
|
19
|
+
* See {@link Client#pluginManager}
|
|
36
20
|
*
|
|
37
21
|
* ```
|
|
38
22
|
* // client-side
|
|
@@ -72,28 +56,50 @@ import Client from './Client.js';
|
|
|
72
56
|
* }, 1000);
|
|
73
57
|
* ```
|
|
74
58
|
*
|
|
75
|
-
* @memberof client
|
|
76
59
|
* @extends BasePluginManager
|
|
77
60
|
* @inheritdoc
|
|
78
61
|
* @hideconstructor
|
|
79
62
|
*/
|
|
80
|
-
class
|
|
63
|
+
class ClientPluginManager extends BasePluginManager {
|
|
81
64
|
/**
|
|
82
|
-
* @param {
|
|
65
|
+
* @param {Client} client - The soundworks client instance.
|
|
83
66
|
*/
|
|
84
67
|
constructor(client) {
|
|
85
68
|
if (!(client instanceof Client)) {
|
|
86
|
-
throw new Error(`[soundworks.
|
|
69
|
+
throw new Error(`[soundworks.ClientPluginManager] Invalid argument, "new ClientPluginManager(client)" should receive an instance of "soundworks.Client" as argument`);
|
|
87
70
|
}
|
|
88
71
|
|
|
89
72
|
super(client);
|
|
90
73
|
}
|
|
91
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Register a plugin into the manager.
|
|
77
|
+
*
|
|
78
|
+
* _A plugin must always be registered both on client-side and on server-side_
|
|
79
|
+
*
|
|
80
|
+
* Refer to the plugin documentation to check its options and proper way of
|
|
81
|
+
* registering it.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} id - Unique id of the plugin. Enables the registration of the
|
|
84
|
+
* same plugin factory under different ids.
|
|
85
|
+
* @param {Function} factory - Factory function that returns the Plugin class.
|
|
86
|
+
* @param {object} [options={}] - Options to configure the plugin.
|
|
87
|
+
* @param {array} [deps=[]] - List of plugins' names the plugin depends on, i.e.
|
|
88
|
+
* the plugin initialization will start only after the plugins it depends on are
|
|
89
|
+
* fully started themselves.
|
|
90
|
+
* @see {@link ClientPluginManager#register}
|
|
91
|
+
* @see {@link ServerPluginManager#register}
|
|
92
|
+
* @example
|
|
93
|
+
* // client-side
|
|
94
|
+
* client.pluginManager.register('user-defined-id', pluginFactory);
|
|
95
|
+
* // server-side
|
|
96
|
+
* server.pluginManager.register('user-defined-id', pluginFactory);
|
|
97
|
+
*/
|
|
92
98
|
register(id, factory, options = {}, deps = []) {
|
|
93
|
-
const ctor = factory(
|
|
99
|
+
const ctor = factory(ClientPlugin);
|
|
94
100
|
|
|
95
|
-
if (!(ctor.prototype instanceof
|
|
96
|
-
throw new Error(`[soundworks.
|
|
101
|
+
if (!(ctor.prototype instanceof ClientPlugin)) {
|
|
102
|
+
throw new Error(`[soundworks.ClientPluginManager] Invalid argument, "pluginManager.register" second argument should be a factory function returning a class extending the "Plugin" base class`);
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
super.register(id, ctor, options, deps);
|
|
@@ -112,21 +118,19 @@ class PluginManager extends BasePluginManager {
|
|
|
112
118
|
* on the `onStateChange` method.
|
|
113
119
|
*
|
|
114
120
|
* _Note: the async API is designed to enable the dynamic creation of plugins
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* @param {client.Plugin#id} id - Id of the plugin as defined when registered.
|
|
118
|
-
* @returns {client.Plugin}
|
|
119
|
-
* @see {@link client.PluginManager#onStateChange}
|
|
121
|
+
* in a future release._
|
|
120
122
|
*
|
|
121
|
-
* @
|
|
123
|
+
* @param {string} id - Id of the plugin as defined when registered.
|
|
124
|
+
* @returns {Promise<ClientPlugin>}
|
|
125
|
+
* @see {@link ClientPluginManager#onStateChange}
|
|
122
126
|
*/
|
|
123
127
|
async get(id) {
|
|
124
128
|
if (this.status !== 'started') {
|
|
125
|
-
throw new Error(`[soundworks.
|
|
129
|
+
throw new Error(`[soundworks.ClientPluginManager] Cannot get plugin before "client.init()"`);
|
|
126
130
|
}
|
|
127
131
|
|
|
128
|
-
return super.
|
|
132
|
+
return super.getUnsafe(id);
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
|
|
132
|
-
export default
|
|
136
|
+
export default ClientPluginManager;
|
|
@@ -14,15 +14,15 @@ import {
|
|
|
14
14
|
export const kSocketTerminate = Symbol('soundworks:socket-terminate');
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* The
|
|
17
|
+
* The ClientSocket class is a simple publish / subscribe wrapper built on top of the
|
|
18
18
|
* [isomorphic-ws](https://github.com/heineiuo/isomorphic-ws) library.
|
|
19
|
-
* An instance of `
|
|
20
|
-
* (see {@link
|
|
19
|
+
* An instance of `ClientSocket` is automatically created by the `soundworks.Client`
|
|
20
|
+
* (see {@link Client#socket}).
|
|
21
21
|
*
|
|
22
|
-
* _Important: In most cases, you should consider using a {@link
|
|
22
|
+
* _Important: In most cases, you should consider using a {@link SharedState}
|
|
23
23
|
* rather than directly using the sockets._
|
|
24
24
|
*
|
|
25
|
-
* The
|
|
25
|
+
* The ClientSocket class concurrently opens two different WebSockets:
|
|
26
26
|
* - a socket configured with `binaryType = 'blob'` for JSON compatible data
|
|
27
27
|
* types (i.e. string, number, boolean, object, array and null).
|
|
28
28
|
* - a socket configured with `binaryType= 'arraybuffer'` for efficient streaming
|
|
@@ -31,42 +31,48 @@ export const kSocketTerminate = Symbol('soundworks:socket-terminate');
|
|
|
31
31
|
* Both sockets re-emits all "native" ws events ('open', 'upgrade', 'close', 'error'
|
|
32
32
|
* and 'message'.
|
|
33
33
|
*
|
|
34
|
-
* @memberof client
|
|
35
34
|
* @hideconstructor
|
|
36
35
|
*/
|
|
37
|
-
class
|
|
36
|
+
class ClientSocket {
|
|
37
|
+
#role = null;
|
|
38
|
+
#config = null;
|
|
39
|
+
#socketOptions = null;
|
|
38
40
|
#socket = null;
|
|
39
41
|
#listeners = new Map();
|
|
40
42
|
|
|
41
|
-
constructor() {
|
|
43
|
+
constructor(role, config, socketOptions) {
|
|
44
|
+
this.#role = role;
|
|
45
|
+
this.#config = config;
|
|
46
|
+
this.#socketOptions = socketOptions;
|
|
47
|
+
}
|
|
42
48
|
|
|
43
49
|
/**
|
|
44
50
|
* Initialize a websocket connection with the server. Automatically called
|
|
45
|
-
* during
|
|
51
|
+
* during {@link Client#init}
|
|
46
52
|
*
|
|
47
|
-
* @param {string} role - Role of the client (see {@link
|
|
53
|
+
* @param {string} role - Role of the client (see {@link Client#role})
|
|
48
54
|
* @param {object} config - Configuration of the sockets
|
|
49
55
|
* @private
|
|
50
56
|
*/
|
|
51
|
-
async init(
|
|
52
|
-
let { path } =
|
|
57
|
+
async init() {
|
|
58
|
+
let { path } = this.#socketOptions;
|
|
53
59
|
// cf. https://github.com/collective-soundworks/soundworks/issues/35
|
|
54
|
-
if (config.env.subpath) {
|
|
55
|
-
path = `${config.env.subpath}/${path}`;
|
|
60
|
+
if (this.#config.env.subpath) {
|
|
61
|
+
path = `${this.#config.env.subpath}/${path}`;
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
const protocol = config.env.useHttps ? 'wss:' : 'ws:';
|
|
59
|
-
const port = config.env.port;
|
|
64
|
+
const protocol = this.#config.env.useHttps ? 'wss:' : 'ws:';
|
|
65
|
+
const port = this.#config.env.port;
|
|
60
66
|
let serverAddress;
|
|
61
67
|
let webSocketOptions;
|
|
62
68
|
|
|
63
69
|
if (isBrowser()) {
|
|
64
70
|
const hostname = window.location.hostname;
|
|
65
71
|
|
|
66
|
-
if (config.env.serverAddress === '') {
|
|
72
|
+
if (this.#config.env.serverAddress === '') {
|
|
67
73
|
serverAddress = hostname;
|
|
68
74
|
} else {
|
|
69
|
-
serverAddress = config.env.serverAddress;
|
|
75
|
+
serverAddress = this.#config.env.serverAddress;
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
// when in https with self-signed certificates, we don't want to use
|
|
@@ -74,20 +80,20 @@ class Socket {
|
|
|
74
80
|
// just override serverAddress with hostname in this case
|
|
75
81
|
const localhosts = ['127.0.0.1', 'localhost'];
|
|
76
82
|
|
|
77
|
-
if (config.env.useHttps && localhosts.includes(hostname)) {
|
|
83
|
+
if (this.#config.env.useHttps && localhosts.includes(hostname)) {
|
|
78
84
|
serverAddress = window.location.hostname;
|
|
79
85
|
}
|
|
80
86
|
|
|
81
|
-
if (config.env.useHttps && window.location.hostname !== serverAddress) {
|
|
87
|
+
if (this.#config.env.useHttps && window.location.hostname !== serverAddress) {
|
|
82
88
|
console.warn(`The WebSocket 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}.`);
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
webSocketOptions = [];
|
|
86
92
|
} else {
|
|
87
|
-
if (config.env.serverAddress === '') {
|
|
93
|
+
if (this.#config.env.serverAddress === '') {
|
|
88
94
|
serverAddress = '127.0.0.1';
|
|
89
95
|
} else {
|
|
90
|
-
serverAddress = config.env.serverAddress;
|
|
96
|
+
serverAddress = this.#config.env.serverAddress;
|
|
91
97
|
}
|
|
92
98
|
|
|
93
99
|
webSocketOptions = {
|
|
@@ -97,10 +103,10 @@ class Socket {
|
|
|
97
103
|
};
|
|
98
104
|
}
|
|
99
105
|
|
|
100
|
-
let queryParams = `role=${role}`;
|
|
106
|
+
let queryParams = `role=${this.#role}`;
|
|
101
107
|
|
|
102
|
-
if (config.token) {
|
|
103
|
-
queryParams += `&token=${config.token}`;
|
|
108
|
+
if (this.#config.token) {
|
|
109
|
+
queryParams += `&token=${this.#config.token}`;
|
|
104
110
|
}
|
|
105
111
|
|
|
106
112
|
const url = `${protocol}//${serverAddress}:${port}/${path}?${queryParams}`;
|
|
@@ -112,67 +118,51 @@ class Socket {
|
|
|
112
118
|
const trySocket = async () => {
|
|
113
119
|
const ws = new WebSocket(url, webSocketOptions);
|
|
114
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 whensome 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
|
+
|
|
115
128
|
ws.addEventListener('open', openEvent => {
|
|
116
|
-
// parse incoming messages for pubsub
|
|
117
129
|
this.#socket = ws;
|
|
118
130
|
|
|
119
131
|
this.#socket.addEventListener('message', e => {
|
|
120
132
|
if (e.data === PING_MESSAGE) {
|
|
121
133
|
this.#socket.send(PONG_MESSAGE);
|
|
122
|
-
// do not propagate ping
|
|
123
|
-
return;
|
|
134
|
+
return; // do not propagate ping pong messages
|
|
124
135
|
}
|
|
125
136
|
|
|
137
|
+
// Parse incoming message, dispatch in pubsub system and propagate raw event.
|
|
126
138
|
const [channel, args] = unpackStringMessage(e.data);
|
|
127
139
|
this.#dispatchEvent(channel, ...args);
|
|
140
|
+
this.#dispatchEvent('message', e);
|
|
128
141
|
});
|
|
129
142
|
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
//
|
|
137
|
-
// Also available via the onerror property.
|
|
138
|
-
// `message`:
|
|
139
|
-
// Fired when data is received through a websocket.
|
|
140
|
-
// Also available via the onmessage property.
|
|
141
|
-
// `open`:
|
|
142
|
-
// Fired when a connection with a websocket is opened.
|
|
143
|
-
// Also available via the onopen property.
|
|
144
|
-
[
|
|
145
|
-
'close',
|
|
146
|
-
'error',
|
|
147
|
-
'upgrade',
|
|
148
|
-
'message',
|
|
149
|
-
].forEach(eventName => {
|
|
150
|
-
this.#socket.addEventListener(eventName, e => {
|
|
151
|
-
this.#dispatchEvent(eventName, e);
|
|
152
|
-
});
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
// forward open event
|
|
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
|
|
156
150
|
this.#dispatchEvent('open', openEvent);
|
|
157
|
-
// continue with raw socket
|
|
158
151
|
resolve();
|
|
159
152
|
});
|
|
160
153
|
|
|
161
154
|
// cf. https://github.com/collective-soundworks/soundworks/issues/17
|
|
155
|
+
// cf. https://github.com/collective-soundworks/soundworks/issues/97
|
|
162
156
|
ws.addEventListener('error', e => {
|
|
163
|
-
|
|
164
|
-
ws.terminate();
|
|
165
|
-
} else {
|
|
166
|
-
ws.close();
|
|
167
|
-
}
|
|
157
|
+
ws.terminate ? ws.terminate() : ws.close();
|
|
168
158
|
|
|
169
159
|
if (e.error) {
|
|
170
160
|
const msg = `[Socket Error] code: ${e.error.code}, message: ${e.error.message}`;
|
|
171
161
|
logger.log(msg);
|
|
172
162
|
}
|
|
173
163
|
|
|
174
|
-
// Try reconnect in all cases
|
|
175
|
-
// close event will be propagated and the launcher will restart the process.
|
|
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.
|
|
176
166
|
setTimeout(trySocket, 1000);
|
|
177
167
|
});
|
|
178
168
|
};
|
|
@@ -203,7 +193,6 @@ class Socket {
|
|
|
203
193
|
}
|
|
204
194
|
|
|
205
195
|
/**
|
|
206
|
-
* @param {boolean} binary - Emit to either the string or binary socket.
|
|
207
196
|
* @param {string} channel - Channel name.
|
|
208
197
|
* @param {...*} args - Content of the message.
|
|
209
198
|
*/
|
|
@@ -234,7 +223,7 @@ class Socket {
|
|
|
234
223
|
* @param {string} channel - Channel name.
|
|
235
224
|
* @param {Function} callback - Callback to execute when a message is received,
|
|
236
225
|
* arguments of the callback function will match the arguments sent using the
|
|
237
|
-
* {@link
|
|
226
|
+
* {@link ServerSocket#send} method.
|
|
238
227
|
*/
|
|
239
228
|
addListener(channel, callback) {
|
|
240
229
|
if (!this.#listeners.has(channel)) {
|
|
@@ -276,4 +265,4 @@ class Socket {
|
|
|
276
265
|
}
|
|
277
266
|
}
|
|
278
267
|
|
|
279
|
-
export default
|
|
268
|
+
export default ClientSocket;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import BaseStateManager from '../common/BaseStateManager.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @callback
|
|
4
|
+
* @callback ClientStateManager~ObserveCallback
|
|
5
5
|
* @async
|
|
6
6
|
* @param {String} schemaName - name of the schema
|
|
7
7
|
* @param {Number} stateId - id of the state
|
|
@@ -9,11 +9,11 @@ import BaseStateManager from '../common/BaseStateManager.js';
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* The `
|
|
13
|
-
* to {@link
|
|
14
|
-
* can also track all the {@link
|
|
12
|
+
* The `ClientStateManager` allows to create new {@link SharedState}s, or attach
|
|
13
|
+
* to {@link SharedState}s created by other nodes (clients or server). It
|
|
14
|
+
* can also track all the {@link SharedState}s created by other nodes.
|
|
15
15
|
*
|
|
16
|
-
* An instance of `
|
|
16
|
+
* An instance of `ClientStateManager` is automatically created by the `soundworks.Client`
|
|
17
17
|
* at initialization (cf. {@link client.Client#stateManager}).
|
|
18
18
|
*
|
|
19
19
|
* See {@link client.Client#stateManager}
|
|
@@ -57,11 +57,10 @@ import BaseStateManager from '../common/BaseStateManager.js';
|
|
|
57
57
|
* }, 1000);
|
|
58
58
|
* ```
|
|
59
59
|
*
|
|
60
|
-
* @memberof client
|
|
61
60
|
* @extends BaseStateManager
|
|
62
61
|
* @inheritdoc
|
|
63
62
|
* @hideconstructor
|
|
64
63
|
*/
|
|
65
|
-
class
|
|
64
|
+
class ClientStateManager extends BaseStateManager {}
|
|
66
65
|
|
|
67
|
-
export default
|
|
66
|
+
export default ClientStateManager;
|
package/src/client/index.js
CHANGED
|
@@ -4,47 +4,23 @@
|
|
|
4
4
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
// this must be defined here so that we can import it into the application
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* const config = window.SOUNDWORKS_CONFIG;
|
|
23
|
-
*
|
|
24
|
-
* async function main($container) {
|
|
25
|
-
* try {
|
|
26
|
-
* const client = new Client(config);
|
|
27
|
-
*
|
|
28
|
-
* // client.pluginManager.register(pluginName, pluginFactory, {options}, [dependencies])
|
|
29
|
-
*
|
|
30
|
-
* launcher.register(client, {
|
|
31
|
-
* initScreensContainer: $container,
|
|
32
|
-
* reloadOnVisibilityChange: false,
|
|
33
|
-
* });
|
|
34
|
-
*
|
|
35
|
-
* await client.start();
|
|
36
|
-
*
|
|
37
|
-
* $container.innerText = `client ${client.id} started`);
|
|
38
|
-
*
|
|
39
|
-
* } catch(err) {
|
|
40
|
-
* console.error(err);
|
|
41
|
-
* }
|
|
42
|
-
* }
|
|
43
|
-
*
|
|
44
|
-
* launcher.execute(main);
|
|
45
|
-
* ```
|
|
46
|
-
*
|
|
47
|
-
* @namespace client
|
|
9
|
+
* Configuration object for a client running in a browser runtime.
|
|
10
|
+
*
|
|
11
|
+
* @typedef ClientConfig
|
|
12
|
+
* @type {object}
|
|
13
|
+
* @property {string} role - Role of the client in the application (e.g. 'player', 'controller').
|
|
14
|
+
* @property {object} [app] - Application configuration object.
|
|
15
|
+
* @property {string} [app.name=''] - Name of the application.
|
|
16
|
+
* @property {string} [app.author=''] - Name of the author.
|
|
17
|
+
* @property {object} env - Environment configuration object.
|
|
18
|
+
* @property {boolean} env.useHttps - Define if the websocket should use secure connection.
|
|
19
|
+
* @property {string} env.serverAddress - Address the socket server. Mandatory for
|
|
20
|
+
* node clients. For browser clients, use `window.location.domain` as fallback if empty.
|
|
21
|
+
* @property {number} env.port=8000 - Port of the socket server.
|
|
22
|
+
* @property {string} [env.subpath=''] - If running behind a proxy, path to the application.
|
|
48
23
|
*/
|
|
49
|
-
|
|
24
|
+
|
|
50
25
|
export { default as Client } from './Client.js';
|
|
26
|
+
export { default as ClientContext } from './ClientContext.js';
|