@soundworks/core 3.7.0 → 4.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +16 -0
- package/LICENSE +1 -1
- package/README.md +32 -30
- package/package.json +48 -28
- package/src/client/Client.js +391 -0
- package/src/client/Context.js +225 -0
- package/src/client/ContextManager.js +111 -0
- package/src/client/Plugin.js +66 -0
- package/src/client/PluginManager.js +123 -0
- package/src/client/SharedState.js +84 -0
- package/src/client/Socket.js +389 -0
- package/src/client/StateManager.js +67 -0
- package/src/client/index.js +77 -0
- package/src/common/BasePlugin.js +163 -0
- package/src/common/BasePluginManager.js +227 -0
- package/{common/SharedState.js → src/common/BaseSharedState.js} +198 -234
- package/src/common/BaseStateManager.js +200 -0
- package/{common → src/common}/ParameterBag.js +74 -112
- package/{common → src/common}/SharedStatePrivate.js +56 -62
- package/src/common/constants.js +51 -0
- package/src/common/logger.js +154 -0
- package/src/common/promise-store.js +25 -0
- package/src/common/sockets-utils.js +73 -0
- package/src/common/utils.js +22 -0
- package/src/server/Client.js +50 -0
- package/src/server/Context.js +99 -0
- package/src/server/ContextManager.js +266 -0
- package/src/server/Plugin.js +77 -0
- package/src/server/PluginManager.js +150 -0
- package/src/server/Server.js +849 -0
- package/src/server/SharedState.js +86 -0
- package/{server → src/server}/Socket.js +114 -88
- package/{server → src/server}/Sockets.js +73 -60
- package/{common/SharedStateManagerServer.js → src/server/StateManager.js} +181 -129
- package/src/server/audit-network-latency.worker.js +57 -0
- package/src/server/audit-schema.js +42 -0
- package/src/server/index.js +45 -0
- package/tsconfig.json +21 -0
- package/types/client/Client.d.ts +194 -0
- package/types/client/Client.d.ts.map +1 -0
- package/types/client/Context.d.ts +139 -0
- package/types/client/Context.d.ts.map +1 -0
- package/types/client/ContextManager.d.ts +45 -0
- package/types/client/ContextManager.d.ts.map +1 -0
- package/types/client/Plugin.d.ts +53 -0
- package/types/client/Plugin.d.ts.map +1 -0
- package/types/client/PluginManager.d.ts +96 -0
- package/types/client/PluginManager.d.ts.map +1 -0
- package/types/client/SharedState.d.ts +85 -0
- package/types/client/SharedState.d.ts.map +1 -0
- package/types/client/Socket.d.ts +138 -0
- package/types/client/Socket.d.ts.map +1 -0
- package/types/client/StateManager.d.ts +72 -0
- package/types/client/StateManager.d.ts.map +1 -0
- package/types/client/index.d.ts +11 -0
- package/types/client/index.d.ts.map +1 -0
- package/types/common/BasePlugin.d.ts +145 -0
- package/types/common/BasePlugin.d.ts.map +1 -0
- package/types/common/BasePluginManager.d.ts +88 -0
- package/types/common/BasePluginManager.d.ts.map +1 -0
- package/types/common/BaseSharedState.d.ts +173 -0
- package/types/common/BaseSharedState.d.ts.map +1 -0
- package/types/common/BaseStateManager.d.ts +70 -0
- package/types/common/BaseStateManager.d.ts.map +1 -0
- package/types/common/ParameterBag.d.ts +143 -0
- package/types/common/ParameterBag.d.ts.map +1 -0
- package/types/common/SharedStatePrivate.d.ts +20 -0
- package/types/common/SharedStatePrivate.d.ts.map +1 -0
- package/types/common/constants.d.ts +35 -0
- package/types/common/constants.d.ts.map +1 -0
- package/types/common/logger.d.ts +16 -0
- package/types/common/logger.d.ts.map +1 -0
- package/types/common/promise-store.d.ts +4 -0
- package/types/common/promise-store.d.ts.map +1 -0
- package/types/common/sockets-utils.d.ts +9 -0
- package/types/common/sockets-utils.d.ts.map +1 -0
- package/types/common/utils.d.ts +5 -0
- package/types/common/utils.d.ts.map +1 -0
- package/types/server/Client.d.ts +40 -0
- package/types/server/Client.d.ts.map +1 -0
- package/types/server/Context.d.ts +59 -0
- package/types/server/Context.d.ts.map +1 -0
- package/types/server/ContextManager.d.ts +69 -0
- package/types/server/ContextManager.d.ts.map +1 -0
- package/types/server/Plugin.d.ts +70 -0
- package/types/server/Plugin.d.ts.map +1 -0
- package/types/server/PluginManager.d.ts +91 -0
- package/types/server/PluginManager.d.ts.map +1 -0
- package/types/server/Server.d.ts +223 -0
- package/types/server/Server.d.ts.map +1 -0
- package/types/server/SharedState.d.ts +86 -0
- package/types/server/SharedState.d.ts.map +1 -0
- package/types/server/Socket.d.ts +128 -0
- package/types/server/Socket.d.ts.map +1 -0
- package/types/server/Sockets.d.ts +73 -0
- package/types/server/Sockets.d.ts.map +1 -0
- package/types/server/StateManager.d.ts +190 -0
- package/types/server/StateManager.d.ts.map +1 -0
- package/types/server/audit-network-latency.worker.d.ts +2 -0
- package/types/server/audit-network-latency.worker.d.ts.map +1 -0
- package/types/server/audit-schema.d.ts +27 -0
- package/types/server/audit-schema.d.ts.map +1 -0
- package/types/server/index.d.ts +7 -0
- package/types/server/index.d.ts.map +1 -0
- package/client/AbstractExperience.js +0 -54
- package/client/AbstractPlugin.js +0 -230
- package/client/Client.js +0 -193
- package/client/PluginManager.js +0 -279
- package/client/Socket.js +0 -346
- package/client/index.js +0 -48
- package/client.js +0 -28
- package/common/SharedStateManagerClient.js +0 -194
- package/common/Signal.js +0 -40
- package/common/SignalAll.js +0 -44
- package/common/logger.js +0 -162
- package/common/shared-state-utils.js +0 -89
- package/common/sockets-encoder-decoder.js +0 -73
- package/server/AbstractExperience.js +0 -126
- package/server/AbstractPlugin.js +0 -214
- package/server/Client.js +0 -70
- package/server/PluginManager.js +0 -273
- package/server/Server.js +0 -785
- package/server/index.js +0 -49
- package/server.js +0 -28
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import ParameterBag from '../common/ParameterBag.js';
|
|
2
|
+
import {
|
|
3
|
+
DELETE_REQUEST,
|
|
4
|
+
DELETE_RESPONSE,
|
|
5
|
+
DELETE_NOTIFICATION,
|
|
6
|
+
DETACH_REQUEST,
|
|
7
|
+
DETACH_RESPONSE,
|
|
8
|
+
UPDATE_REQUEST,
|
|
9
|
+
UPDATE_RESPONSE,
|
|
10
|
+
UPDATE_ABORT,
|
|
11
|
+
UPDATE_NOTIFICATION,
|
|
12
|
+
} from '../common/constants.js';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* The "real" state, this instance is kept private by the
|
|
15
|
+
* The "real" state, this instance is kept private by the server.StateManager.
|
|
16
16
|
* It cannot be accessed without a SharedState proxy
|
|
17
17
|
* @private
|
|
18
18
|
*/
|
|
@@ -20,8 +20,9 @@ class SharedStatePrivate {
|
|
|
20
20
|
constructor(id, schemaName, schema, manager, initValues = {}) {
|
|
21
21
|
this.id = id;
|
|
22
22
|
this.schemaName = schemaName;
|
|
23
|
+
|
|
23
24
|
this._manager = manager;
|
|
24
|
-
this._parameters = new
|
|
25
|
+
this._parameters = new ParameterBag(schema, initValues);
|
|
25
26
|
this._attachedClients = new Map(); // other peers interested in watching / controlling the state
|
|
26
27
|
|
|
27
28
|
this._creatorRemoteId = null;
|
|
@@ -34,29 +35,25 @@ class SharedStatePrivate {
|
|
|
34
35
|
if (isOwner) {
|
|
35
36
|
this._creatorRemoteId = remoteId;
|
|
36
37
|
this._creatorId = client.id;
|
|
37
|
-
}
|
|
38
|
-
|
|
38
|
+
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
// attach client listeners
|
|
41
|
+
client.transport.addListener(`${UPDATE_REQUEST}-${this.id}-${remoteId}`, async (reqId, updates, context) => {
|
|
41
42
|
// apply registered hooks
|
|
42
43
|
const hooks = this._manager._hooksBySchemaName.get(this.schemaName);
|
|
43
|
-
|
|
44
44
|
const values = this._parameters.getValues();
|
|
45
|
+
let hookAborted = false;
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
// cf. https://github.com/collective-soundworks/soundworks/issues/45
|
|
48
48
|
for (let hook of hooks.values()) {
|
|
49
49
|
const result = await hook(updates, values, context);
|
|
50
50
|
|
|
51
|
-
if (result === null) {
|
|
52
|
-
// explicit abort if hook returns null
|
|
51
|
+
if (result === null) { // explicit abort if hook returns null
|
|
53
52
|
hookAborted = true;
|
|
54
53
|
break;
|
|
55
|
-
} else if (result === undefined) {
|
|
56
|
-
// implicit continue if hook returns undefined
|
|
54
|
+
} else if (result === undefined) { // implicit continue if hook returns undefined
|
|
57
55
|
continue;
|
|
58
|
-
} else {
|
|
59
|
-
// the hook returned an updates object
|
|
56
|
+
} else { // the hook returned an updates object
|
|
60
57
|
updates = result;
|
|
61
58
|
}
|
|
62
59
|
}
|
|
@@ -74,21 +71,18 @@ class SharedStatePrivate {
|
|
|
74
71
|
// objects as dirty, because if the state is attached locally, we
|
|
75
72
|
// compare the Object instances instead of their values.
|
|
76
73
|
// @note - this should be made more robust but how?
|
|
77
|
-
const [newValue, changed] = this._parameters.set(name, updates[name]);
|
|
78
|
-
// has been changed or not, it is always propagated to client states
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const {
|
|
82
|
-
event,
|
|
83
|
-
filterChange
|
|
84
|
-
} = this._parameters.getSchema(name); // if event type reset internal value to null
|
|
74
|
+
const [newValue, changed] = this._parameters.set(name, updates[name]);
|
|
85
75
|
|
|
76
|
+
// if `filterChange` is set to `false` we don't check if the value
|
|
77
|
+
// has been changed or not, it is always propagated to client states
|
|
78
|
+
const { event, filterChange } = this._parameters.getSchema(name);
|
|
86
79
|
|
|
80
|
+
// if event type reset internal value to null
|
|
87
81
|
if (event) {
|
|
88
82
|
this._parameters.set(name, null);
|
|
89
83
|
}
|
|
90
84
|
|
|
91
|
-
if (filterChange && changed || !filterChange) {
|
|
85
|
+
if ((filterChange && changed) || !filterChange) {
|
|
92
86
|
filteredUpdates[name] = newValue;
|
|
93
87
|
hasUpdates = true;
|
|
94
88
|
}
|
|
@@ -97,47 +91,51 @@ class SharedStatePrivate {
|
|
|
97
91
|
if (hasUpdates) {
|
|
98
92
|
// send response to requester
|
|
99
93
|
// client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates);
|
|
94
|
+
|
|
100
95
|
// @note: we propagate server-side last, because as the server transport
|
|
101
96
|
// is synchronous it can break ordering if a subscription function makes
|
|
102
97
|
// itself an update in reaction to an update, therefore network messages
|
|
103
98
|
// order would be broken,
|
|
99
|
+
|
|
104
100
|
// we need to handle cases where:
|
|
105
101
|
// client state (client.id: 2) sends a request
|
|
106
102
|
// server attached state (client.id: -1) spot a problem and overrides the value
|
|
107
103
|
// we want the remote client (id: 2) to receive in the right order:
|
|
108
104
|
// * 1. the value it requested,
|
|
109
105
|
// * 2. the value overriden by the server-side attached state (id: -1)
|
|
106
|
+
|
|
110
107
|
// this problem could be solved properly with a reducer system:
|
|
111
108
|
// if (dirty) {
|
|
112
109
|
// -> call (async) reducer
|
|
113
110
|
// -> get values from reducer
|
|
114
111
|
//. -> dispatch to everybody
|
|
115
112
|
// }
|
|
113
|
+
|
|
116
114
|
for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
|
|
117
115
|
// propagate notification to all other attached clients except server
|
|
118
116
|
if (remoteId !== peerRemoteId && peer.id !== -1) {
|
|
119
|
-
peer.transport.emit(`${
|
|
117
|
+
peer.transport.emit(`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
|
|
120
118
|
}
|
|
121
119
|
}
|
|
122
120
|
|
|
123
121
|
if (client.id !== -1) {
|
|
124
|
-
client.transport.emit(`${
|
|
122
|
+
client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
|
|
125
123
|
}
|
|
126
124
|
|
|
127
125
|
for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
|
|
128
126
|
// propagate notification to server
|
|
129
127
|
if (remoteId !== peerRemoteId && peer.id === -1) {
|
|
130
|
-
peer.transport.emit(`${
|
|
128
|
+
peer.transport.emit(`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
|
|
131
129
|
}
|
|
132
130
|
}
|
|
133
131
|
|
|
134
132
|
if (client.id === -1) {
|
|
135
|
-
client.transport.emit(`${
|
|
133
|
+
client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
|
|
136
134
|
}
|
|
137
135
|
} else {
|
|
138
136
|
// propagate back to the requester that the update has been aborted
|
|
139
137
|
// ignore all other attached clients.
|
|
140
|
-
client.transport.emit(`${
|
|
138
|
+
client.transport.emit(`${UPDATE_ABORT}-${this.id}-${remoteId}`, reqId, updates, context);
|
|
141
139
|
}
|
|
142
140
|
} else {
|
|
143
141
|
// retrieve values from inner state (also handle immediate approriately)
|
|
@@ -145,54 +143,50 @@ class SharedStatePrivate {
|
|
|
145
143
|
|
|
146
144
|
for (let name in updates) {
|
|
147
145
|
oldValues[name] = this._parameters.get(name);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
client.transport.emit(`${_sharedStateUtils.UPDATE_ABORT}-${this.id}-${remoteId}`, reqId, oldValues, context);
|
|
146
|
+
}
|
|
147
|
+
// aborted by hook (updates have been overriden to {})
|
|
148
|
+
client.transport.emit(`${UPDATE_ABORT}-${this.id}-${remoteId}`, reqId, oldValues, context);
|
|
152
149
|
}
|
|
153
150
|
});
|
|
154
151
|
|
|
155
152
|
if (isOwner) {
|
|
156
153
|
// delete only if creator
|
|
157
|
-
client.transport.addListener(`${
|
|
158
|
-
this._manager._serverStatesById.delete(this.id);
|
|
154
|
+
client.transport.addListener(`${DELETE_REQUEST}-${this.id}-${remoteId}`, (reqId) => {
|
|
155
|
+
this._manager._serverStatesById.delete(this.id);
|
|
156
|
+
// --------------------------------------------------------------------
|
|
159
157
|
// WARNING - MAKE SURE WE DON'T HAVE PROBLEM W/ THAT
|
|
160
158
|
// --------------------------------------------------------------------
|
|
161
159
|
// @todo - propagate server-side last, because if a subscription function sends a
|
|
162
160
|
// message to a client, network messages order are kept coherent
|
|
163
161
|
// this._subscriptions.forEach(func => func(updated));
|
|
164
|
-
|
|
165
|
-
|
|
166
162
|
for (let [remoteId, attached] of this._attachedClients.entries()) {
|
|
167
163
|
this._detachClient(remoteId, attached);
|
|
168
164
|
|
|
169
165
|
if (remoteId === this._creatorRemoteId) {
|
|
170
|
-
attached.transport.emit(`${
|
|
166
|
+
attached.transport.emit(`${DELETE_RESPONSE}-${this.id}-${remoteId}`, reqId);
|
|
171
167
|
} else {
|
|
172
|
-
attached.transport.emit(`${
|
|
168
|
+
attached.transport.emit(`${DELETE_NOTIFICATION}-${this.id}-${remoteId}`);
|
|
173
169
|
}
|
|
174
170
|
}
|
|
175
171
|
});
|
|
176
172
|
} else {
|
|
177
173
|
// detach only if not creator
|
|
178
|
-
client.transport.addListener(`${
|
|
174
|
+
client.transport.addListener(`${DETACH_REQUEST}-${this.id}-${remoteId}`, (reqId) => {
|
|
179
175
|
this._detachClient(remoteId, client);
|
|
180
|
-
|
|
181
|
-
client.transport.emit(`${_sharedStateUtils.DETACH_RESPONSE}-${this.id}-${remoteId}`, reqId);
|
|
176
|
+
client.transport.emit(`${DETACH_RESPONSE}-${this.id}-${remoteId}`, reqId);
|
|
182
177
|
});
|
|
183
178
|
}
|
|
184
179
|
}
|
|
185
180
|
|
|
186
181
|
_detachClient(remoteId, client) {
|
|
187
|
-
this._attachedClients.delete(remoteId);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
client.transport.removeAllListeners(`${
|
|
191
|
-
client.transport.removeAllListeners(`${
|
|
192
|
-
client.transport.removeAllListeners(`${_sharedStateUtils.DETACH_REQUEST}-${this.id}-${remoteId}`);
|
|
182
|
+
this._attachedClients.delete(remoteId);
|
|
183
|
+
// delete listeners
|
|
184
|
+
client.transport.removeAllListeners(`${UPDATE_REQUEST}-${this.id}-${remoteId}`);
|
|
185
|
+
client.transport.removeAllListeners(`${DELETE_REQUEST}-${this.id}-${remoteId}`);
|
|
186
|
+
client.transport.removeAllListeners(`${DETACH_REQUEST}-${this.id}-${remoteId}`);
|
|
193
187
|
}
|
|
194
|
-
|
|
195
188
|
}
|
|
196
189
|
|
|
197
|
-
|
|
198
|
-
|
|
190
|
+
export default SharedStatePrivate;
|
|
191
|
+
|
|
192
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// id of the server when owner of a state
|
|
2
|
+
export const SERVER_ID = -1;
|
|
3
|
+
|
|
4
|
+
// shared states channels
|
|
5
|
+
export const CREATE_REQUEST = 's:c:req';
|
|
6
|
+
export const CREATE_RESPONSE = 's:c:res';
|
|
7
|
+
export const CREATE_ERROR = 's:c:err';
|
|
8
|
+
|
|
9
|
+
export const DELETE_REQUEST = 's:dl:req';
|
|
10
|
+
export const DELETE_RESPONSE = 's:dl:res';
|
|
11
|
+
export const DELETE_ERROR = 's:dl:err';
|
|
12
|
+
export const DELETE_NOTIFICATION = 's:dl:not';
|
|
13
|
+
|
|
14
|
+
export const ATTACH_REQUEST = 's:a:req';
|
|
15
|
+
export const ATTACH_RESPONSE = 's:a:res';
|
|
16
|
+
export const ATTACH_ERROR = 's:a:err';
|
|
17
|
+
|
|
18
|
+
export const DETACH_REQUEST = 's:dt:req';
|
|
19
|
+
export const DETACH_RESPONSE = 's:dt:res';
|
|
20
|
+
export const DETACH_ERROR = 's:dt:err';
|
|
21
|
+
|
|
22
|
+
export const OBSERVE_REQUEST = 's:o:req';
|
|
23
|
+
export const OBSERVE_RESPONSE = 's:o:res';
|
|
24
|
+
export const OBSERVE_NOTIFICATION = 's:o:not';
|
|
25
|
+
|
|
26
|
+
export const UNOBSERVE_NOTIFICATION = 's:uo:not';
|
|
27
|
+
|
|
28
|
+
export const UPDATE_REQUEST = 's:u:req';
|
|
29
|
+
export const UPDATE_RESPONSE = 's:u:res';
|
|
30
|
+
export const UPDATE_ABORT = 's:u:ab';
|
|
31
|
+
export const UPDATE_NOTIFICATION = 's:u:not';
|
|
32
|
+
|
|
33
|
+
export const DELETE_SCHEMA = 's:d:s';
|
|
34
|
+
|
|
35
|
+
// context channels
|
|
36
|
+
export const CONTEXT_ENTER_REQUEST = 'c:en:req';
|
|
37
|
+
export const CONTEXT_ENTER_RESPONSE = 'c:en:res';
|
|
38
|
+
export const CONTEXT_ENTER_ERROR = 'c:en:err';
|
|
39
|
+
|
|
40
|
+
export const CONTEXT_EXIT_REQUEST = 'c:ex:req';
|
|
41
|
+
export const CONTEXT_EXIT_RESPONSE = 'c:ex:res';
|
|
42
|
+
export const CONTEXT_EXIT_ERROR = 'c:ex:err';
|
|
43
|
+
|
|
44
|
+
// client handshake
|
|
45
|
+
export const CLIENT_HANDSHAKE_REQUEST = 'cl:h:req';
|
|
46
|
+
export const CLIENT_HANDSHAKE_RESPONSE = 'cl:h:res';
|
|
47
|
+
export const CLIENT_HANDSHAKE_ERROR = 'cl:h:err';
|
|
48
|
+
|
|
49
|
+
// audit state schema name
|
|
50
|
+
export const AUDIT_STATE_NAME = 's:c:audit';
|
|
51
|
+
export const PRIVATE_STATES = [AUDIT_STATE_NAME];
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import columnify from 'columnify';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @private
|
|
6
|
+
*/
|
|
7
|
+
const logger = {
|
|
8
|
+
verbose: true,
|
|
9
|
+
|
|
10
|
+
configure(verbose) {
|
|
11
|
+
this.verbose = verbose;
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
title(msg) {
|
|
15
|
+
if (!this.verbose) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
console.log(chalk.cyan(`+ ${msg}`));
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
clientConfigAndRouting(routes, config) {
|
|
24
|
+
if (!this.verbose) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
const clientsConfig = config.app.clients;
|
|
30
|
+
const serverAddress = config.env.serverAddress;
|
|
31
|
+
const auth = config.env.auth;
|
|
32
|
+
const table = [];
|
|
33
|
+
|
|
34
|
+
for (let role in clientsConfig) {
|
|
35
|
+
const client = clientsConfig[role];
|
|
36
|
+
|
|
37
|
+
if (client.target === 'node') {
|
|
38
|
+
const line = {
|
|
39
|
+
role: `> ${role}`,
|
|
40
|
+
target: chalk.red(client.target),
|
|
41
|
+
path: `serverAddress: ${chalk.green(serverAddress)}`,
|
|
42
|
+
default: undefined,
|
|
43
|
+
auth: undefined,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
table.push(line);
|
|
47
|
+
} else if (client.target === 'browser') {
|
|
48
|
+
const line = {
|
|
49
|
+
role: `> ${role}`,
|
|
50
|
+
target: chalk.red(client.target),
|
|
51
|
+
path: routes.find(r => r.role === role) ?
|
|
52
|
+
chalk.green(routes.find(r => r.role === role).path) :
|
|
53
|
+
chalk.red('no route defined'),
|
|
54
|
+
default: (client.default ? 'x' : undefined),
|
|
55
|
+
auth: auth && auth.clients.indexOf(role) !== -1 ? 'x' : undefined,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
table.push(line);
|
|
59
|
+
} else {
|
|
60
|
+
console.log(`@warning: no target defined for client ${role}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(``);
|
|
65
|
+
console.log(columnify(table, {
|
|
66
|
+
showHeaders: true,
|
|
67
|
+
minWidth: 6,
|
|
68
|
+
columnSplitter: ' | ',
|
|
69
|
+
config: {
|
|
70
|
+
default: { align: 'center' },
|
|
71
|
+
auth: { align: 'center' },
|
|
72
|
+
},
|
|
73
|
+
}));
|
|
74
|
+
console.log(``);
|
|
75
|
+
|
|
76
|
+
// check if a route is defined but not in config
|
|
77
|
+
if (clientsConfig) {
|
|
78
|
+
const configClientTypes = Object.keys(clientsConfig);
|
|
79
|
+
routes.forEach(r => {
|
|
80
|
+
if (configClientTypes.indexOf(r.role) === -1) {
|
|
81
|
+
console.log(`@warning: no client config found for route ${r.role}`);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
console.log(`@warning: no config found for clients`);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
ip(protocol, address, port) {
|
|
90
|
+
if (!this.verbose) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log(` ${protocol}://${address}:${chalk.green(port)}`);
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
pluginStart(name) {
|
|
98
|
+
if (!this.verbose) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(` ${name} ${chalk.yellow('start...')}`);
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
pluginStarted(name) {
|
|
106
|
+
if (!this.verbose) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log(` ${name} ${chalk.cyan('started')}`);
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
pluginReady(name) {
|
|
114
|
+
if (!this.verbose) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log(` ${name} ${chalk.green('ready')}`);
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
pluginErrored(name) {
|
|
122
|
+
if (!this.verbose) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log(` ${name} ${chalk.red('errors')}`);
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
log(msg) {
|
|
130
|
+
if (!this.verbose) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log(msg);
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
warn(msg) {
|
|
138
|
+
if (!this.verbose) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.warn(chalk.yellow(msg));
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
error(msg) {
|
|
146
|
+
if (!this.verbose) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.error(chalk.red(msg));
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export default logger;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { idGenerator } from './utils.js';
|
|
2
|
+
|
|
3
|
+
const generateRequestId = idGenerator();
|
|
4
|
+
const requestPromises = new Map();
|
|
5
|
+
|
|
6
|
+
export function storeRequestPromise(resolve, reject) {
|
|
7
|
+
const reqId = generateRequestId.next().value;
|
|
8
|
+
requestPromises.set(reqId, { resolve, reject });
|
|
9
|
+
|
|
10
|
+
return reqId;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function resolveRequest(reqId, data) {
|
|
14
|
+
const { resolve } = requestPromises.get(reqId);
|
|
15
|
+
requestPromises.delete(reqId);
|
|
16
|
+
|
|
17
|
+
resolve(data);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function rejectRequest(reqId, data) {
|
|
21
|
+
const { reject } = requestPromises.get(reqId);
|
|
22
|
+
requestPromises.delete(reqId);
|
|
23
|
+
|
|
24
|
+
reject(data);
|
|
25
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import 'fast-text-encoding';
|
|
2
|
+
import root from 'window-or-global';
|
|
3
|
+
|
|
4
|
+
const encoder = new root.TextEncoder('utf-8');
|
|
5
|
+
const decoder = new root.TextDecoder('utf-8');
|
|
6
|
+
|
|
7
|
+
const types = [
|
|
8
|
+
'Int8Array',
|
|
9
|
+
'Uint8Array',
|
|
10
|
+
'Uint8ClampedArray',
|
|
11
|
+
'Int16Array',
|
|
12
|
+
'Uint16Array',
|
|
13
|
+
'Int32Array',
|
|
14
|
+
'Uint32Array',
|
|
15
|
+
'Float32Array',
|
|
16
|
+
'Float64Array',
|
|
17
|
+
'BigInt64Array',
|
|
18
|
+
'BigUint64Array',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// @note - probably some room for optimizations
|
|
22
|
+
|
|
23
|
+
/** @private */
|
|
24
|
+
export function packBinaryMessage(channel, data) {
|
|
25
|
+
const channelBuffer = encoder.encode(channel);
|
|
26
|
+
const channelSize = channelBuffer.byteLength;
|
|
27
|
+
const startOffset = Math.ceil((channelSize + 3) / data.BYTES_PER_ELEMENT) * data.BYTES_PER_ELEMENT;
|
|
28
|
+
const bufferSize = startOffset + data.byteLength;
|
|
29
|
+
const typeName = data.constructor.name;
|
|
30
|
+
const typeIndex = types.indexOf(typeName);
|
|
31
|
+
|
|
32
|
+
if (typeIndex === -1) {
|
|
33
|
+
throw new Error(`Invalid TypedArray type: ${typeName}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const view = new Uint8Array(bufferSize);
|
|
37
|
+
view[0] = channelSize;
|
|
38
|
+
view[1] = typeIndex;
|
|
39
|
+
view[2] = startOffset;
|
|
40
|
+
|
|
41
|
+
view.set(channelBuffer, 3, channelSize);
|
|
42
|
+
view.set(new Uint8Array(data.buffer), startOffset, data.byteLength);
|
|
43
|
+
|
|
44
|
+
return view.buffer;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @private */
|
|
48
|
+
export function unpackBinaryMessage(buffer /* arraybuffer */) {
|
|
49
|
+
const infos = new Uint8Array(buffer, 0, 3);
|
|
50
|
+
const channelSize = infos[0];
|
|
51
|
+
const typeIndex = infos[1];
|
|
52
|
+
const startOffset = infos[2];
|
|
53
|
+
|
|
54
|
+
// need to slice as the library recreates a UInt8Array from the whole buffer
|
|
55
|
+
// @todo - see if this copy could be avoided (probably needs a pull request)
|
|
56
|
+
const channelBuffer = new Uint8Array(buffer.slice(3, 3 + channelSize));
|
|
57
|
+
const channel = decoder.decode(channelBuffer);
|
|
58
|
+
const type = types[typeIndex];
|
|
59
|
+
// slice (copy) the underlying ArrayBuffer to create a clean TypedArray
|
|
60
|
+
const data = new root[type](buffer.slice(startOffset));
|
|
61
|
+
|
|
62
|
+
return [channel, data];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** @private */
|
|
66
|
+
export function packStringMessage(channel, ...args) {
|
|
67
|
+
return JSON.stringify([channel, args]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** @private */
|
|
71
|
+
export function unpackStringMessage(data) {
|
|
72
|
+
return JSON.parse(data);
|
|
73
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// https://stackoverflow.com/questions/17575790/environment-detection-node-js-or-browser
|
|
2
|
+
export const isBrowser = new Function('try {return this===window;}catch(e){ return false;}');
|
|
3
|
+
|
|
4
|
+
export function* idGenerator() {
|
|
5
|
+
for (let i = 0; true; i++) {
|
|
6
|
+
if (i === Number.MAX_SAFE_INTEGER) {
|
|
7
|
+
i = 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
yield i;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isString(val) {
|
|
15
|
+
return (typeof val === 'string' || val instanceof String);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function isFunction(func) {
|
|
19
|
+
return Object.prototype.toString.call(func) == '[object Function]' ||
|
|
20
|
+
Object.prototype.toString.call(func) == '[object AsyncFunction]';
|
|
21
|
+
}
|
|
22
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { v4 as uuid } from 'uuid';
|
|
2
|
+
|
|
3
|
+
import { idGenerator } from '../common/utils.js';
|
|
4
|
+
|
|
5
|
+
const generateId = idGenerator();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Server-side representation of a `soundworks` client.
|
|
9
|
+
*
|
|
10
|
+
* @memberof server
|
|
11
|
+
* @hideconstructor
|
|
12
|
+
* @see {@link client.Client}
|
|
13
|
+
*/
|
|
14
|
+
class Client {
|
|
15
|
+
/**
|
|
16
|
+
* @param {String} role - Role of the client
|
|
17
|
+
* @param {server.Socket} socket - Socket connection with the client
|
|
18
|
+
*/
|
|
19
|
+
constructor(role, socket) {
|
|
20
|
+
/**
|
|
21
|
+
* Client role, as specified in client side config {@link client.Client}.
|
|
22
|
+
*
|
|
23
|
+
* @type {String}
|
|
24
|
+
*/
|
|
25
|
+
this.role = role;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Session Id (incremented positive number).
|
|
29
|
+
*
|
|
30
|
+
* @type {Number}
|
|
31
|
+
*/
|
|
32
|
+
this.id = generateId.next().value;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Unique session Id (uuidv4).
|
|
36
|
+
*
|
|
37
|
+
* @type {String}
|
|
38
|
+
*/
|
|
39
|
+
this.uuid = uuid();
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Socket connection with the remote client {@link client.Client}.
|
|
43
|
+
*
|
|
44
|
+
* @type {server.Socket}
|
|
45
|
+
*/
|
|
46
|
+
this.socket = socket;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default Client;
|