@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.
Files changed (124) hide show
  1. package/.eslintrc +16 -0
  2. package/LICENSE +1 -1
  3. package/README.md +32 -30
  4. package/package.json +48 -28
  5. package/src/client/Client.js +391 -0
  6. package/src/client/Context.js +225 -0
  7. package/src/client/ContextManager.js +111 -0
  8. package/src/client/Plugin.js +66 -0
  9. package/src/client/PluginManager.js +123 -0
  10. package/src/client/SharedState.js +84 -0
  11. package/src/client/Socket.js +389 -0
  12. package/src/client/StateManager.js +67 -0
  13. package/src/client/index.js +77 -0
  14. package/src/common/BasePlugin.js +163 -0
  15. package/src/common/BasePluginManager.js +227 -0
  16. package/{common/SharedState.js → src/common/BaseSharedState.js} +198 -234
  17. package/src/common/BaseStateManager.js +200 -0
  18. package/{common → src/common}/ParameterBag.js +74 -112
  19. package/{common → src/common}/SharedStatePrivate.js +56 -62
  20. package/src/common/constants.js +51 -0
  21. package/src/common/logger.js +154 -0
  22. package/src/common/promise-store.js +25 -0
  23. package/src/common/sockets-utils.js +73 -0
  24. package/src/common/utils.js +22 -0
  25. package/src/server/Client.js +50 -0
  26. package/src/server/Context.js +99 -0
  27. package/src/server/ContextManager.js +266 -0
  28. package/src/server/Plugin.js +77 -0
  29. package/src/server/PluginManager.js +150 -0
  30. package/src/server/Server.js +849 -0
  31. package/src/server/SharedState.js +86 -0
  32. package/{server → src/server}/Socket.js +114 -88
  33. package/{server → src/server}/Sockets.js +73 -60
  34. package/{common/SharedStateManagerServer.js → src/server/StateManager.js} +181 -129
  35. package/src/server/audit-network-latency.worker.js +57 -0
  36. package/src/server/audit-schema.js +42 -0
  37. package/src/server/index.js +45 -0
  38. package/tsconfig.json +21 -0
  39. package/types/client/Client.d.ts +194 -0
  40. package/types/client/Client.d.ts.map +1 -0
  41. package/types/client/Context.d.ts +139 -0
  42. package/types/client/Context.d.ts.map +1 -0
  43. package/types/client/ContextManager.d.ts +45 -0
  44. package/types/client/ContextManager.d.ts.map +1 -0
  45. package/types/client/Plugin.d.ts +53 -0
  46. package/types/client/Plugin.d.ts.map +1 -0
  47. package/types/client/PluginManager.d.ts +96 -0
  48. package/types/client/PluginManager.d.ts.map +1 -0
  49. package/types/client/SharedState.d.ts +85 -0
  50. package/types/client/SharedState.d.ts.map +1 -0
  51. package/types/client/Socket.d.ts +138 -0
  52. package/types/client/Socket.d.ts.map +1 -0
  53. package/types/client/StateManager.d.ts +72 -0
  54. package/types/client/StateManager.d.ts.map +1 -0
  55. package/types/client/index.d.ts +11 -0
  56. package/types/client/index.d.ts.map +1 -0
  57. package/types/common/BasePlugin.d.ts +145 -0
  58. package/types/common/BasePlugin.d.ts.map +1 -0
  59. package/types/common/BasePluginManager.d.ts +88 -0
  60. package/types/common/BasePluginManager.d.ts.map +1 -0
  61. package/types/common/BaseSharedState.d.ts +173 -0
  62. package/types/common/BaseSharedState.d.ts.map +1 -0
  63. package/types/common/BaseStateManager.d.ts +70 -0
  64. package/types/common/BaseStateManager.d.ts.map +1 -0
  65. package/types/common/ParameterBag.d.ts +143 -0
  66. package/types/common/ParameterBag.d.ts.map +1 -0
  67. package/types/common/SharedStatePrivate.d.ts +20 -0
  68. package/types/common/SharedStatePrivate.d.ts.map +1 -0
  69. package/types/common/constants.d.ts +35 -0
  70. package/types/common/constants.d.ts.map +1 -0
  71. package/types/common/logger.d.ts +16 -0
  72. package/types/common/logger.d.ts.map +1 -0
  73. package/types/common/promise-store.d.ts +4 -0
  74. package/types/common/promise-store.d.ts.map +1 -0
  75. package/types/common/sockets-utils.d.ts +9 -0
  76. package/types/common/sockets-utils.d.ts.map +1 -0
  77. package/types/common/utils.d.ts +5 -0
  78. package/types/common/utils.d.ts.map +1 -0
  79. package/types/server/Client.d.ts +40 -0
  80. package/types/server/Client.d.ts.map +1 -0
  81. package/types/server/Context.d.ts +59 -0
  82. package/types/server/Context.d.ts.map +1 -0
  83. package/types/server/ContextManager.d.ts +69 -0
  84. package/types/server/ContextManager.d.ts.map +1 -0
  85. package/types/server/Plugin.d.ts +70 -0
  86. package/types/server/Plugin.d.ts.map +1 -0
  87. package/types/server/PluginManager.d.ts +91 -0
  88. package/types/server/PluginManager.d.ts.map +1 -0
  89. package/types/server/Server.d.ts +223 -0
  90. package/types/server/Server.d.ts.map +1 -0
  91. package/types/server/SharedState.d.ts +86 -0
  92. package/types/server/SharedState.d.ts.map +1 -0
  93. package/types/server/Socket.d.ts +128 -0
  94. package/types/server/Socket.d.ts.map +1 -0
  95. package/types/server/Sockets.d.ts +73 -0
  96. package/types/server/Sockets.d.ts.map +1 -0
  97. package/types/server/StateManager.d.ts +190 -0
  98. package/types/server/StateManager.d.ts.map +1 -0
  99. package/types/server/audit-network-latency.worker.d.ts +2 -0
  100. package/types/server/audit-network-latency.worker.d.ts.map +1 -0
  101. package/types/server/audit-schema.d.ts +27 -0
  102. package/types/server/audit-schema.d.ts.map +1 -0
  103. package/types/server/index.d.ts +7 -0
  104. package/types/server/index.d.ts.map +1 -0
  105. package/client/AbstractExperience.js +0 -54
  106. package/client/AbstractPlugin.js +0 -230
  107. package/client/Client.js +0 -193
  108. package/client/PluginManager.js +0 -279
  109. package/client/Socket.js +0 -346
  110. package/client/index.js +0 -48
  111. package/client.js +0 -28
  112. package/common/SharedStateManagerClient.js +0 -194
  113. package/common/Signal.js +0 -40
  114. package/common/SignalAll.js +0 -44
  115. package/common/logger.js +0 -162
  116. package/common/shared-state-utils.js +0 -89
  117. package/common/sockets-encoder-decoder.js +0 -73
  118. package/server/AbstractExperience.js +0 -126
  119. package/server/AbstractPlugin.js +0 -214
  120. package/server/Client.js +0 -70
  121. package/server/PluginManager.js +0 -273
  122. package/server/Server.js +0 -785
  123. package/server/index.js +0 -49
  124. package/server.js +0 -28
@@ -1,18 +1,18 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
-
8
- var _ParameterBag = _interopRequireDefault(require("./ParameterBag.js"));
9
-
10
- var _sharedStateUtils = require("./shared-state-utils");
11
-
12
- 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';
13
13
 
14
14
  /**
15
- * The "real" state, this instance is kept private by the SharedStateServerManager.
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 _ParameterBag.default(schema, initValues);
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
- } // attach client listeners
38
-
38
+ }
39
39
 
40
- client.transport.addListener(`${_sharedStateUtils.UPDATE_REQUEST}-${this.id}-${remoteId}`, async (reqId, updates, context) => {
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
- let hookAborted = false; // cf. https://github.com/collective-soundworks/soundworks/issues/45
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]); // if `filterChange` is set to `false` we don't check if the value
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(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
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(`${_sharedStateUtils.UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
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(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
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(`${_sharedStateUtils.UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
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(`${_sharedStateUtils.UPDATE_ABORT}-${this.id}-${remoteId}`, reqId, updates, context);
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
- } // aborted by hook (updates have been overriden to {})
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(`${_sharedStateUtils.DELETE_REQUEST}-${this.id}-${remoteId}`, reqId => {
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(`${_sharedStateUtils.DELETE_RESPONSE}-${this.id}-${remoteId}`, reqId);
166
+ attached.transport.emit(`${DELETE_RESPONSE}-${this.id}-${remoteId}`, reqId);
171
167
  } else {
172
- attached.transport.emit(`${_sharedStateUtils.DELETE_NOTIFICATION}-${this.id}-${remoteId}`);
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(`${_sharedStateUtils.DETACH_REQUEST}-${this.id}-${remoteId}`, reqId => {
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); // delete listeners
188
-
189
-
190
- client.transport.removeAllListeners(`${_sharedStateUtils.UPDATE_REQUEST}-${this.id}-${remoteId}`);
191
- client.transport.removeAllListeners(`${_sharedStateUtils.DELETE_REQUEST}-${this.id}-${remoteId}`);
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
- var _default = SharedStatePrivate;
198
- exports.default = _default;
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;