@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.
Files changed (125) hide show
  1. package/.eslintrc +16 -0
  2. package/LICENSE +1 -1
  3. package/README.md +32 -30
  4. package/package.json +57 -26
  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/src/common/BaseSharedState.js +473 -0
  17. package/src/common/BaseStateManager.js +200 -0
  18. package/{common → src/common}/ParameterBag.js +82 -122
  19. package/{common → src/common}/SharedStatePrivate.js +56 -64
  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 +119 -94
  33. package/{server → src/server}/Sockets.js +73 -61
  34. package/{common/SharedStateManagerServer.js → src/server/StateManager.js} +183 -131
  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 -190
  108. package/client/PluginManager.js +0 -267
  109. package/client/Socket.js +0 -338
  110. package/client/index.js +0 -48
  111. package/client.js +0 -28
  112. package/common/SharedState.js +0 -480
  113. package/common/SharedStateManagerClient.js +0 -194
  114. package/common/Signal.js +0 -40
  115. package/common/SignalAll.js +0 -44
  116. package/common/shared-state-utils.js +0 -90
  117. package/common/sockets-encoder-decoder.js +0 -73
  118. package/server/AbstractExperience.js +0 -126
  119. package/server/AbstractPlugin.js +0 -211
  120. package/server/Client.js +0 -70
  121. package/server/PluginManager.js +0 -177
  122. package/server/Server.js +0 -746
  123. package/server/index.js +0 -49
  124. package/server/utils/logger.js +0 -134
  125. package/server.js +0 -28
@@ -1,20 +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 _lodash = _interopRequireDefault(require("lodash.clonedeep"));
11
-
12
- var _sharedStateUtils = require("./shared-state-utils");
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 SharedStateServerManager.
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 _ParameterBag.default(schema, initValues);
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
- } // attach client listeners
40
-
38
+ }
41
39
 
42
- 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) => {
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
- let hookAborted = false; // cf. https://github.com/collective-soundworks/soundworks/issues/45
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]); // if `filterChange` is set to `false` we don't check if the value
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(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
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(`${_sharedStateUtils.UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
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(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
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(`${_sharedStateUtils.UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
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(`${_sharedStateUtils.UPDATE_ABORT}-${this.id}-${remoteId}`, reqId, updates, context);
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
- } // aborted by hook (updates have been overriden to {})
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(`${_sharedStateUtils.DELETE_REQUEST}-${this.id}-${remoteId}`, reqId => {
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(`${_sharedStateUtils.DELETE_RESPONSE}-${this.id}-${remoteId}`, reqId);
166
+ attached.transport.emit(`${DELETE_RESPONSE}-${this.id}-${remoteId}`, reqId);
173
167
  } else {
174
- attached.transport.emit(`${_sharedStateUtils.DELETE_NOTIFICATION}-${this.id}-${remoteId}`);
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(`${_sharedStateUtils.DETACH_REQUEST}-${this.id}-${remoteId}`, reqId => {
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); // delete listeners
190
-
191
-
192
- client.transport.removeAllListeners(`${_sharedStateUtils.UPDATE_REQUEST}-${this.id}-${remoteId}`);
193
- client.transport.removeAllListeners(`${_sharedStateUtils.DELETE_REQUEST}-${this.id}-${remoteId}`);
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
- var _default = SharedStatePrivate;
200
- 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;