@soundworks/core 3.6.0 → 4.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +16 -0
- package/LICENSE +1 -1
- package/README.md +32 -30
- package/package.json +57 -26
- package/src/client/Client.js +391 -0
- package/src/client/Context.js +225 -0
- package/src/client/ContextManager.js +111 -0
- package/src/client/Plugin.js +66 -0
- package/src/client/PluginManager.js +123 -0
- package/src/client/SharedState.js +84 -0
- package/src/client/Socket.js +389 -0
- package/src/client/StateManager.js +67 -0
- package/src/client/index.js +77 -0
- package/src/common/BasePlugin.js +163 -0
- package/src/common/BasePluginManager.js +227 -0
- package/src/common/BaseSharedState.js +473 -0
- package/src/common/BaseStateManager.js +200 -0
- package/{common → src/common}/ParameterBag.js +82 -122
- package/{common → src/common}/SharedStatePrivate.js +56 -64
- package/src/common/constants.js +51 -0
- package/src/common/logger.js +154 -0
- package/src/common/promise-store.js +25 -0
- package/src/common/sockets-utils.js +73 -0
- package/src/common/utils.js +22 -0
- package/src/server/Client.js +50 -0
- package/src/server/Context.js +99 -0
- package/src/server/ContextManager.js +266 -0
- package/src/server/Plugin.js +77 -0
- package/src/server/PluginManager.js +150 -0
- package/src/server/Server.js +849 -0
- package/src/server/SharedState.js +86 -0
- package/{server → src/server}/Socket.js +119 -94
- package/{server → src/server}/Sockets.js +73 -61
- package/{common/SharedStateManagerServer.js → src/server/StateManager.js} +183 -131
- package/src/server/audit-network-latency.worker.js +57 -0
- package/src/server/audit-schema.js +42 -0
- package/src/server/index.js +45 -0
- package/tsconfig.json +21 -0
- package/types/client/Client.d.ts +194 -0
- package/types/client/Client.d.ts.map +1 -0
- package/types/client/Context.d.ts +139 -0
- package/types/client/Context.d.ts.map +1 -0
- package/types/client/ContextManager.d.ts +45 -0
- package/types/client/ContextManager.d.ts.map +1 -0
- package/types/client/Plugin.d.ts +53 -0
- package/types/client/Plugin.d.ts.map +1 -0
- package/types/client/PluginManager.d.ts +96 -0
- package/types/client/PluginManager.d.ts.map +1 -0
- package/types/client/SharedState.d.ts +85 -0
- package/types/client/SharedState.d.ts.map +1 -0
- package/types/client/Socket.d.ts +138 -0
- package/types/client/Socket.d.ts.map +1 -0
- package/types/client/StateManager.d.ts +72 -0
- package/types/client/StateManager.d.ts.map +1 -0
- package/types/client/index.d.ts +11 -0
- package/types/client/index.d.ts.map +1 -0
- package/types/common/BasePlugin.d.ts +145 -0
- package/types/common/BasePlugin.d.ts.map +1 -0
- package/types/common/BasePluginManager.d.ts +88 -0
- package/types/common/BasePluginManager.d.ts.map +1 -0
- package/types/common/BaseSharedState.d.ts +173 -0
- package/types/common/BaseSharedState.d.ts.map +1 -0
- package/types/common/BaseStateManager.d.ts +70 -0
- package/types/common/BaseStateManager.d.ts.map +1 -0
- package/types/common/ParameterBag.d.ts +143 -0
- package/types/common/ParameterBag.d.ts.map +1 -0
- package/types/common/SharedStatePrivate.d.ts +20 -0
- package/types/common/SharedStatePrivate.d.ts.map +1 -0
- package/types/common/constants.d.ts +35 -0
- package/types/common/constants.d.ts.map +1 -0
- package/types/common/logger.d.ts +16 -0
- package/types/common/logger.d.ts.map +1 -0
- package/types/common/promise-store.d.ts +4 -0
- package/types/common/promise-store.d.ts.map +1 -0
- package/types/common/sockets-utils.d.ts +9 -0
- package/types/common/sockets-utils.d.ts.map +1 -0
- package/types/common/utils.d.ts +5 -0
- package/types/common/utils.d.ts.map +1 -0
- package/types/server/Client.d.ts +40 -0
- package/types/server/Client.d.ts.map +1 -0
- package/types/server/Context.d.ts +59 -0
- package/types/server/Context.d.ts.map +1 -0
- package/types/server/ContextManager.d.ts +69 -0
- package/types/server/ContextManager.d.ts.map +1 -0
- package/types/server/Plugin.d.ts +70 -0
- package/types/server/Plugin.d.ts.map +1 -0
- package/types/server/PluginManager.d.ts +91 -0
- package/types/server/PluginManager.d.ts.map +1 -0
- package/types/server/Server.d.ts +223 -0
- package/types/server/Server.d.ts.map +1 -0
- package/types/server/SharedState.d.ts +86 -0
- package/types/server/SharedState.d.ts.map +1 -0
- package/types/server/Socket.d.ts +128 -0
- package/types/server/Socket.d.ts.map +1 -0
- package/types/server/Sockets.d.ts +73 -0
- package/types/server/Sockets.d.ts.map +1 -0
- package/types/server/StateManager.d.ts +190 -0
- package/types/server/StateManager.d.ts.map +1 -0
- package/types/server/audit-network-latency.worker.d.ts +2 -0
- package/types/server/audit-network-latency.worker.d.ts.map +1 -0
- package/types/server/audit-schema.d.ts +27 -0
- package/types/server/audit-schema.d.ts.map +1 -0
- package/types/server/index.d.ts +7 -0
- package/types/server/index.d.ts.map +1 -0
- package/client/AbstractExperience.js +0 -54
- package/client/AbstractPlugin.js +0 -230
- package/client/Client.js +0 -190
- package/client/PluginManager.js +0 -267
- package/client/Socket.js +0 -338
- package/client/index.js +0 -48
- package/client.js +0 -28
- package/common/SharedState.js +0 -480
- package/common/SharedStateManagerClient.js +0 -194
- package/common/Signal.js +0 -40
- package/common/SignalAll.js +0 -44
- package/common/shared-state-utils.js +0 -90
- package/common/sockets-encoder-decoder.js +0 -73
- package/server/AbstractExperience.js +0 -126
- package/server/AbstractPlugin.js +0 -211
- package/server/Client.js +0 -70
- package/server/PluginManager.js +0 -177
- package/server/Server.js +0 -746
- package/server/index.js +0 -49
- package/server/utils/logger.js +0 -134
- package/server.js +0 -28
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
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';
|
|
15
13
|
|
|
16
14
|
/**
|
|
17
|
-
* The "real" state, this instance is kept private by the
|
|
15
|
+
* The "real" state, this instance is kept private by the server.StateManager.
|
|
18
16
|
* It cannot be accessed without a SharedState proxy
|
|
19
17
|
* @private
|
|
20
18
|
*/
|
|
@@ -22,8 +20,9 @@ class SharedStatePrivate {
|
|
|
22
20
|
constructor(id, schemaName, schema, manager, initValues = {}) {
|
|
23
21
|
this.id = id;
|
|
24
22
|
this.schemaName = schemaName;
|
|
23
|
+
|
|
25
24
|
this._manager = manager;
|
|
26
|
-
this._parameters = new
|
|
25
|
+
this._parameters = new ParameterBag(schema, initValues);
|
|
27
26
|
this._attachedClients = new Map(); // other peers interested in watching / controlling the state
|
|
28
27
|
|
|
29
28
|
this._creatorRemoteId = null;
|
|
@@ -36,29 +35,25 @@ class SharedStatePrivate {
|
|
|
36
35
|
if (isOwner) {
|
|
37
36
|
this._creatorRemoteId = remoteId;
|
|
38
37
|
this._creatorId = client.id;
|
|
39
|
-
}
|
|
40
|
-
|
|
38
|
+
}
|
|
41
39
|
|
|
42
|
-
|
|
40
|
+
// attach client listeners
|
|
41
|
+
client.transport.addListener(`${UPDATE_REQUEST}-${this.id}-${remoteId}`, async (reqId, updates, context) => {
|
|
43
42
|
// apply registered hooks
|
|
44
43
|
const hooks = this._manager._hooksBySchemaName.get(this.schemaName);
|
|
45
|
-
|
|
46
44
|
const values = this._parameters.getValues();
|
|
45
|
+
let hookAborted = false;
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
// cf. https://github.com/collective-soundworks/soundworks/issues/45
|
|
50
48
|
for (let hook of hooks.values()) {
|
|
51
49
|
const result = await hook(updates, values, context);
|
|
52
50
|
|
|
53
|
-
if (result === null) {
|
|
54
|
-
// explicit abort if hook returns null
|
|
51
|
+
if (result === null) { // explicit abort if hook returns null
|
|
55
52
|
hookAborted = true;
|
|
56
53
|
break;
|
|
57
|
-
} else if (result === undefined) {
|
|
58
|
-
// implicit continue if hook returns undefined
|
|
54
|
+
} else if (result === undefined) { // implicit continue if hook returns undefined
|
|
59
55
|
continue;
|
|
60
|
-
} else {
|
|
61
|
-
// the hook returned an updates object
|
|
56
|
+
} else { // the hook returned an updates object
|
|
62
57
|
updates = result;
|
|
63
58
|
}
|
|
64
59
|
}
|
|
@@ -76,21 +71,18 @@ class SharedStatePrivate {
|
|
|
76
71
|
// objects as dirty, because if the state is attached locally, we
|
|
77
72
|
// compare the Object instances instead of their values.
|
|
78
73
|
// @note - this should be made more robust but how?
|
|
79
|
-
const [newValue, changed] = this._parameters.set(name, updates[name]);
|
|
80
|
-
// has been changed or not, it is always propagated to client states
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const {
|
|
84
|
-
event,
|
|
85
|
-
filterChange
|
|
86
|
-
} = this._parameters.getSchema(name); // if event type reset internal value to null
|
|
74
|
+
const [newValue, changed] = this._parameters.set(name, updates[name]);
|
|
87
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);
|
|
88
79
|
|
|
80
|
+
// if event type reset internal value to null
|
|
89
81
|
if (event) {
|
|
90
82
|
this._parameters.set(name, null);
|
|
91
83
|
}
|
|
92
84
|
|
|
93
|
-
if (filterChange && changed || !filterChange) {
|
|
85
|
+
if ((filterChange && changed) || !filterChange) {
|
|
94
86
|
filteredUpdates[name] = newValue;
|
|
95
87
|
hasUpdates = true;
|
|
96
88
|
}
|
|
@@ -99,47 +91,51 @@ class SharedStatePrivate {
|
|
|
99
91
|
if (hasUpdates) {
|
|
100
92
|
// send response to requester
|
|
101
93
|
// client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates);
|
|
94
|
+
|
|
102
95
|
// @note: we propagate server-side last, because as the server transport
|
|
103
96
|
// is synchronous it can break ordering if a subscription function makes
|
|
104
97
|
// itself an update in reaction to an update, therefore network messages
|
|
105
98
|
// order would be broken,
|
|
99
|
+
|
|
106
100
|
// we need to handle cases where:
|
|
107
101
|
// client state (client.id: 2) sends a request
|
|
108
102
|
// server attached state (client.id: -1) spot a problem and overrides the value
|
|
109
103
|
// we want the remote client (id: 2) to receive in the right order:
|
|
110
104
|
// * 1. the value it requested,
|
|
111
105
|
// * 2. the value overriden by the server-side attached state (id: -1)
|
|
106
|
+
|
|
112
107
|
// this problem could be solved properly with a reducer system:
|
|
113
108
|
// if (dirty) {
|
|
114
109
|
// -> call (async) reducer
|
|
115
110
|
// -> get values from reducer
|
|
116
111
|
//. -> dispatch to everybody
|
|
117
112
|
// }
|
|
113
|
+
|
|
118
114
|
for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
|
|
119
115
|
// propagate notification to all other attached clients except server
|
|
120
116
|
if (remoteId !== peerRemoteId && peer.id !== -1) {
|
|
121
|
-
peer.transport.emit(`${
|
|
117
|
+
peer.transport.emit(`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
|
|
122
118
|
}
|
|
123
119
|
}
|
|
124
120
|
|
|
125
121
|
if (client.id !== -1) {
|
|
126
|
-
client.transport.emit(`${
|
|
122
|
+
client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
|
|
127
123
|
}
|
|
128
124
|
|
|
129
125
|
for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
|
|
130
126
|
// propagate notification to server
|
|
131
127
|
if (remoteId !== peerRemoteId && peer.id === -1) {
|
|
132
|
-
peer.transport.emit(`${
|
|
128
|
+
peer.transport.emit(`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
|
|
133
129
|
}
|
|
134
130
|
}
|
|
135
131
|
|
|
136
132
|
if (client.id === -1) {
|
|
137
|
-
client.transport.emit(`${
|
|
133
|
+
client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
|
|
138
134
|
}
|
|
139
135
|
} else {
|
|
140
136
|
// propagate back to the requester that the update has been aborted
|
|
141
137
|
// ignore all other attached clients.
|
|
142
|
-
client.transport.emit(`${
|
|
138
|
+
client.transport.emit(`${UPDATE_ABORT}-${this.id}-${remoteId}`, reqId, updates, context);
|
|
143
139
|
}
|
|
144
140
|
} else {
|
|
145
141
|
// retrieve values from inner state (also handle immediate approriately)
|
|
@@ -147,54 +143,50 @@ class SharedStatePrivate {
|
|
|
147
143
|
|
|
148
144
|
for (let name in updates) {
|
|
149
145
|
oldValues[name] = this._parameters.get(name);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
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);
|
|
154
149
|
}
|
|
155
150
|
});
|
|
156
151
|
|
|
157
152
|
if (isOwner) {
|
|
158
153
|
// delete only if creator
|
|
159
|
-
client.transport.addListener(`${
|
|
160
|
-
this._manager._serverStatesById.delete(this.id);
|
|
154
|
+
client.transport.addListener(`${DELETE_REQUEST}-${this.id}-${remoteId}`, (reqId) => {
|
|
155
|
+
this._manager._serverStatesById.delete(this.id);
|
|
156
|
+
// --------------------------------------------------------------------
|
|
161
157
|
// WARNING - MAKE SURE WE DON'T HAVE PROBLEM W/ THAT
|
|
162
158
|
// --------------------------------------------------------------------
|
|
163
159
|
// @todo - propagate server-side last, because if a subscription function sends a
|
|
164
160
|
// message to a client, network messages order are kept coherent
|
|
165
161
|
// this._subscriptions.forEach(func => func(updated));
|
|
166
|
-
|
|
167
|
-
|
|
168
162
|
for (let [remoteId, attached] of this._attachedClients.entries()) {
|
|
169
163
|
this._detachClient(remoteId, attached);
|
|
170
164
|
|
|
171
165
|
if (remoteId === this._creatorRemoteId) {
|
|
172
|
-
attached.transport.emit(`${
|
|
166
|
+
attached.transport.emit(`${DELETE_RESPONSE}-${this.id}-${remoteId}`, reqId);
|
|
173
167
|
} else {
|
|
174
|
-
attached.transport.emit(`${
|
|
168
|
+
attached.transport.emit(`${DELETE_NOTIFICATION}-${this.id}-${remoteId}`);
|
|
175
169
|
}
|
|
176
170
|
}
|
|
177
171
|
});
|
|
178
172
|
} else {
|
|
179
173
|
// detach only if not creator
|
|
180
|
-
client.transport.addListener(`${
|
|
174
|
+
client.transport.addListener(`${DETACH_REQUEST}-${this.id}-${remoteId}`, (reqId) => {
|
|
181
175
|
this._detachClient(remoteId, client);
|
|
182
|
-
|
|
183
|
-
client.transport.emit(`${_sharedStateUtils.DETACH_RESPONSE}-${this.id}-${remoteId}`, reqId);
|
|
176
|
+
client.transport.emit(`${DETACH_RESPONSE}-${this.id}-${remoteId}`, reqId);
|
|
184
177
|
});
|
|
185
178
|
}
|
|
186
179
|
}
|
|
187
180
|
|
|
188
181
|
_detachClient(remoteId, client) {
|
|
189
|
-
this._attachedClients.delete(remoteId);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
client.transport.removeAllListeners(`${
|
|
193
|
-
client.transport.removeAllListeners(`${
|
|
194
|
-
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}`);
|
|
195
187
|
}
|
|
196
|
-
|
|
197
188
|
}
|
|
198
189
|
|
|
199
|
-
|
|
200
|
-
|
|
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;
|