@soundworks/core 4.0.0-alpha.7 → 5.0.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 (144) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +16 -50
  3. package/package.json +26 -44
  4. package/src/client/Client.js +306 -256
  5. package/src/client/{Context.js → ClientContext.js} +63 -54
  6. package/src/client/ClientContextManager.js +115 -0
  7. package/src/client/{Plugin.js → ClientPlugin.js} +23 -19
  8. package/src/client/{PluginManager.js → ClientPluginManager.js} +43 -40
  9. package/src/client/ClientSocket.js +270 -0
  10. package/src/client/{StateManager.js → ClientStateManager.js} +13 -13
  11. package/src/client/index.js +36 -39
  12. package/src/common/BasePlugin.js +63 -58
  13. package/src/common/BasePluginManager.js +191 -142
  14. package/src/common/BaseStateManager.js +636 -143
  15. package/src/common/BatchedTransport.js +73 -0
  16. package/src/common/ParameterBag.js +90 -93
  17. package/src/common/PromiseStore.js +54 -0
  18. package/src/common/SharedState.js +821 -0
  19. package/src/common/SharedStateCollection.js +492 -0
  20. package/src/common/constants.js +14 -2
  21. package/src/common/logger.js +64 -68
  22. package/src/common/shared-state-types.js +82 -0
  23. package/src/common/sockets-utils.js +0 -64
  24. package/src/common/version.js +2 -0
  25. package/src/server/Server.js +559 -821
  26. package/src/server/ServerClient.js +74 -0
  27. package/src/server/{Context.js → ServerContext.js} +96 -77
  28. package/src/server/{ContextManager.js → ServerContextManager.js} +107 -87
  29. package/src/server/{Plugin.js → ServerPlugin.js} +30 -32
  30. package/src/server/ServerPluginManager.js +177 -0
  31. package/src/server/ServerSocket.js +256 -0
  32. package/src/server/ServerSockets.js +150 -0
  33. package/src/server/ServerStateManager.js +706 -0
  34. package/src/server/SharedStatePrivate.js +277 -0
  35. package/src/server/audit-network-latency.worker.js +19 -4
  36. package/src/server/{audit-schema.js → audit-state-class-description.js} +3 -2
  37. package/src/server/create-http-server.js +127 -0
  38. package/src/server/crypto.js +8 -7
  39. package/src/server/index.js +60 -17
  40. package/types/client/Client.d.ts +95 -70
  41. package/types/client/{Context.d.ts → ClientContext.d.ts} +59 -34
  42. package/types/client/{ContextManager.d.ts → ClientContextManager.d.ts} +19 -22
  43. package/types/client/ClientPlugin.d.ts +51 -0
  44. package/types/client/ClientPluginManager.d.ts +112 -0
  45. package/types/client/ClientSocket.d.ts +76 -0
  46. package/types/client/{StateManager.d.ts → ClientStateManager.d.ts} +18 -21
  47. package/types/client/index.d.ts +33 -6
  48. package/types/common/BasePlugin.d.ts +64 -53
  49. package/types/common/BasePluginManager.d.ts +84 -39
  50. package/types/common/BaseStateManager.d.ts +227 -49
  51. package/types/common/BatchedTransport.d.ts +17 -0
  52. package/types/common/ParameterBag.d.ts +73 -76
  53. package/types/common/PromiseStore.d.ts +11 -0
  54. package/types/common/SharedState.d.ts +291 -0
  55. package/types/common/SharedStateCollection.d.ts +311 -0
  56. package/types/common/constants.d.ts +10 -3
  57. package/types/common/logger.d.ts +8 -6
  58. package/types/common/shared-state-types.d.ts +95 -0
  59. package/types/common/sockets-utils.d.ts +0 -5
  60. package/types/common/version.d.ts +2 -0
  61. package/types/server/Server.d.ts +215 -136
  62. package/types/server/ServerClient.d.ts +45 -0
  63. package/types/server/ServerContext.d.ts +209 -0
  64. package/types/server/ServerContextManager.d.ts +75 -0
  65. package/types/server/ServerPlugin.d.ts +57 -0
  66. package/types/server/ServerPluginManager.d.ts +115 -0
  67. package/types/server/ServerSocket.d.ts +69 -0
  68. package/types/server/ServerSockets.d.ts +42 -0
  69. package/types/server/ServerStateManager.d.ts +284 -0
  70. package/types/server/SharedStatePrivate.d.ts +23 -0
  71. package/types/server/audit-network-latency.worker.d.ts +2 -2
  72. package/types/server/{audit-schema.d.ts → audit-state-class-description.d.ts} +8 -9
  73. package/types/server/create-http-server.d.ts +5 -0
  74. package/types/server/crypto.d.ts +4 -0
  75. package/types/server/index.d.ts +51 -3
  76. package/.eslintrc +0 -15
  77. package/.github/workflows/gh-pages.yml +0 -31
  78. package/.github/workflows/unit-tests.yml +0 -18
  79. package/src/client/ContextManager.js +0 -111
  80. package/src/client/SharedState.js +0 -84
  81. package/src/client/SharedStateCollection.js +0 -35
  82. package/src/client/Socket.js +0 -402
  83. package/src/common/BaseSharedState.js +0 -517
  84. package/src/common/BaseSharedStateCollection.js +0 -179
  85. package/src/common/SharedStatePrivate.js +0 -192
  86. package/src/common/promise-store.js +0 -25
  87. package/src/server/Client.js +0 -55
  88. package/src/server/PluginManager.js +0 -170
  89. package/src/server/SharedState.js +0 -85
  90. package/src/server/SharedStateCollection.js +0 -36
  91. package/src/server/Socket.js +0 -396
  92. package/src/server/Sockets.js +0 -224
  93. package/src/server/StateManager.js +0 -620
  94. package/tsconfig.json +0 -21
  95. package/types/client/Client.d.ts.map +0 -1
  96. package/types/client/Context.d.ts.map +0 -1
  97. package/types/client/ContextManager.d.ts.map +0 -1
  98. package/types/client/Plugin.d.ts +0 -53
  99. package/types/client/Plugin.d.ts.map +0 -1
  100. package/types/client/PluginManager.d.ts +0 -96
  101. package/types/client/PluginManager.d.ts.map +0 -1
  102. package/types/client/SharedState.d.ts +0 -85
  103. package/types/client/SharedState.d.ts.map +0 -1
  104. package/types/client/Socket.d.ts +0 -138
  105. package/types/client/Socket.d.ts.map +0 -1
  106. package/types/client/StateManager.d.ts.map +0 -1
  107. package/types/client/index.d.ts.map +0 -1
  108. package/types/common/BasePlugin.d.ts.map +0 -1
  109. package/types/common/BasePluginManager.d.ts.map +0 -1
  110. package/types/common/BaseSharedState.d.ts +0 -173
  111. package/types/common/BaseSharedState.d.ts.map +0 -1
  112. package/types/common/BaseStateManager.d.ts.map +0 -1
  113. package/types/common/ParameterBag.d.ts.map +0 -1
  114. package/types/common/SharedStatePrivate.d.ts +0 -20
  115. package/types/common/SharedStatePrivate.d.ts.map +0 -1
  116. package/types/common/constants.d.ts.map +0 -1
  117. package/types/common/logger.d.ts.map +0 -1
  118. package/types/common/promise-store.d.ts +0 -4
  119. package/types/common/promise-store.d.ts.map +0 -1
  120. package/types/common/sockets-utils.d.ts.map +0 -1
  121. package/types/common/utils.d.ts +0 -5
  122. package/types/common/utils.d.ts.map +0 -1
  123. package/types/server/Client.d.ts +0 -40
  124. package/types/server/Client.d.ts.map +0 -1
  125. package/types/server/Context.d.ts +0 -59
  126. package/types/server/Context.d.ts.map +0 -1
  127. package/types/server/ContextManager.d.ts +0 -69
  128. package/types/server/ContextManager.d.ts.map +0 -1
  129. package/types/server/Plugin.d.ts +0 -70
  130. package/types/server/Plugin.d.ts.map +0 -1
  131. package/types/server/PluginManager.d.ts +0 -91
  132. package/types/server/PluginManager.d.ts.map +0 -1
  133. package/types/server/Server.d.ts.map +0 -1
  134. package/types/server/SharedState.d.ts +0 -86
  135. package/types/server/SharedState.d.ts.map +0 -1
  136. package/types/server/Socket.d.ts +0 -128
  137. package/types/server/Socket.d.ts.map +0 -1
  138. package/types/server/Sockets.d.ts +0 -73
  139. package/types/server/Sockets.d.ts.map +0 -1
  140. package/types/server/StateManager.d.ts +0 -190
  141. package/types/server/StateManager.d.ts.map +0 -1
  142. package/types/server/audit-network-latency.worker.d.ts.map +0 -1
  143. package/types/server/audit-schema.d.ts.map +0 -1
  144. package/types/server/index.d.ts.map +0 -1
@@ -0,0 +1,277 @@
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
+ import {
14
+ kServerStateManagerDeletePrivateState,
15
+ kServerStateManagerGetUpdateHooks,
16
+ } from '../server/ServerStateManager.js';
17
+
18
+ /**
19
+ * Filter update object according to filter list.
20
+ * Return identity is filter is null
21
+ * @param {object} updates
22
+ * @param {array|null} filter
23
+ * @private
24
+ */
25
+ function filterUpdates(updates, filter) {
26
+ if (filter === null) {
27
+ return updates;
28
+ }
29
+
30
+ return filter.reduce((acc, key) => {
31
+ if (key in updates) {
32
+ acc[key] = updates[key];
33
+ }
34
+ return acc;
35
+ }, {});
36
+ }
37
+
38
+ export const kSharedStatePrivateAttachClient = Symbol('soundworks:shared-state-private-attach-client');
39
+ export const kSharedStatePrivateDetachClient = Symbol('soundworks:shared-state-private-detach-client');
40
+ export const kSharedStatePrivateGetValues = Symbol('soundworks:shared-state-private-get-values');
41
+
42
+ /**
43
+ * The "real" state, this instance is kept private by the server.StateManager.
44
+ * It can only be accessed through a SharedState proxy.
45
+ *
46
+ * @private
47
+ */
48
+ class SharedStatePrivate {
49
+ #id = null;
50
+ #className = null;
51
+ #manager = null;
52
+ #parameters = null;
53
+ #creatorId = null;
54
+ #creatorInstanceId = null;
55
+ #attachedClients = new Map();
56
+
57
+ constructor(manager, className, classDefinition, id, initValues = {}) {
58
+ this.#manager = manager;
59
+ this.#className = className;
60
+ this.#id = id;
61
+ // This can throw but will be catch in ServerStateManager CREATE_REQUEST handler
62
+ this.#parameters = new ParameterBag(classDefinition, initValues);
63
+ }
64
+
65
+ get id() {
66
+ return this.#id;
67
+ }
68
+
69
+ get className() {
70
+ return this.#className;
71
+ }
72
+
73
+ get creatorId() {
74
+ return this.#creatorId;
75
+ }
76
+
77
+ get creatorInstanceId() {
78
+ return this.#creatorInstanceId;
79
+ }
80
+
81
+ get attachedClients() {
82
+ return this.#attachedClients;
83
+ }
84
+
85
+ get parameters() {
86
+ return this.#parameters;
87
+ }
88
+
89
+ [kSharedStatePrivateGetValues]() {
90
+ return this.#parameters.getValues();
91
+ }
92
+
93
+ [kSharedStatePrivateAttachClient](instanceId, client, isOwner, filter) {
94
+ const clientInfos = { client, isOwner, filter };
95
+ this.#attachedClients.set(instanceId, clientInfos);
96
+
97
+ if (isOwner) {
98
+ this.#creatorId = client.id;
99
+ this.#creatorInstanceId = instanceId;
100
+ }
101
+
102
+ // attach client listeners
103
+ client.transport.addListener(`${UPDATE_REQUEST}-${this.id}-${instanceId}`, async (reqId, updates) => {
104
+ // apply registered hooks
105
+ const hooks = this.#manager[kServerStateManagerGetUpdateHooks](this.className);
106
+ const values = this.#parameters.getValues();
107
+ let hookAborted = false;
108
+
109
+ // cf. https://github.com/collective-soundworks/soundworks/issues/45
110
+ for (let hook of hooks.values()) {
111
+ const result = await hook(updates, values);
112
+
113
+ if (result === null) { // explicit abort if hook returns null
114
+ hookAborted = true;
115
+ break;
116
+ } else if (result === undefined) { // implicit continue if hook returns undefined
117
+ continue;
118
+ } else { // the hook returned an updates object
119
+ updates = result;
120
+ }
121
+ }
122
+
123
+ if (hookAborted === false) {
124
+ let acknowledgedUpdates = {};
125
+ let hasUpdates = false;
126
+
127
+ for (let name in updates) {
128
+ const [newValue, changed] = this.#parameters.set(name, updates[name]);
129
+ // if `filterChange` is set to `false` we don't check if the value
130
+ // has been changed or not, it is always propagated to client states
131
+ const { event, filterChange } = this.#parameters.getDescription(name);
132
+
133
+ // if event type reset internal value to null
134
+ if (event) {
135
+ this.#parameters.set(name, null);
136
+ }
137
+
138
+ if ((filterChange && changed) || !filterChange) {
139
+ acknowledgedUpdates[name] = newValue;
140
+ hasUpdates = true;
141
+ }
142
+ }
143
+
144
+ if (hasUpdates) {
145
+ // Normal case
146
+ //
147
+ // We need to handle cases where:
148
+ // - client state (client.id: 2) sends a request
149
+ // - server attached state (client.id: -1) spot a problem and overrides the value
150
+ // We want the remote client (id: 2) to receive in the right order:
151
+ // * 1. the value it requested,
152
+ // * 2. the value overridden by the server-side attached state (id: -1)
153
+ //
154
+ // This problem is now better solved with the the updateHook system,
155
+ // nevertheless we don't want to introduce inconsistencies here
156
+ //
157
+ // Then we propagate server-side last, because as the server transport
158
+ // is synchronous it can break ordering if a subscription function makes
159
+ // itself an update in reaction to an update. Propagating to server last
160
+ // allows to maintain network messages order consistent.
161
+ //
162
+ // @note - instanceId correspond to unique remote state id
163
+
164
+ // propagate RESPONSE to the client that originates the request if not the server
165
+ if (client.id !== -1) {
166
+ // no need to filter updates on requested, is blocked on client-side
167
+ client.transport.emit(
168
+ `${UPDATE_RESPONSE}-${this.id}-${instanceId}`,
169
+ reqId,
170
+ acknowledgedUpdates,
171
+ );
172
+ }
173
+
174
+ // propagate NOTIFICATION to all attached clients except server
175
+ for (let [peerInstanceId, clientInfos] of this.#attachedClients) {
176
+ const peer = clientInfos.client;
177
+
178
+ if (instanceId !== peerInstanceId && peer.id !== -1) {
179
+ const filter = clientInfos.filter;
180
+ const filteredUpdates = filterUpdates(acknowledgedUpdates, filter);
181
+
182
+ // propagate only if there something left after applying the filter
183
+ if (Object.keys(filteredUpdates).length > 0) {
184
+ peer.transport.emit(
185
+ `${UPDATE_NOTIFICATION}-${this.id}-${peerInstanceId}`,
186
+ filteredUpdates,
187
+ );
188
+ }
189
+ }
190
+ }
191
+
192
+ // propagate RESPONSE to server if it is the requester
193
+ if (client.id === -1) {
194
+ // no need to filter updates on requested, is blocked on client-side
195
+ client.transport.emit(
196
+ `${UPDATE_RESPONSE}-${this.id}-${instanceId}`,
197
+ reqId,
198
+ acknowledgedUpdates,
199
+ );
200
+ }
201
+
202
+ // propagate NOTIFICATION to other state attached on the server
203
+ for (let [peerInstanceId, clientInfos] of this.#attachedClients) {
204
+ const peer = clientInfos.client;
205
+
206
+ if (instanceId !== peerInstanceId && peer.id === -1) {
207
+ const filter = clientInfos.filter;
208
+ const filteredUpdates = filterUpdates(acknowledgedUpdates, filter);
209
+
210
+ if (Object.keys(filteredUpdates).length > 0) {
211
+ peer.transport.emit(
212
+ `${UPDATE_NOTIFICATION}-${this.id}-${peerInstanceId}`,
213
+ filteredUpdates,
214
+ );
215
+ }
216
+ }
217
+ }
218
+ } else {
219
+ // propagate back to the requester that the update has been aborted
220
+ // ignore all other attached clients.
221
+ client.transport.emit(`${UPDATE_ABORT}-${this.id}-${instanceId}`, reqId, updates);
222
+ }
223
+ } else {
224
+ // retrieve values from inner state (also handle immediate appropriately)
225
+ const oldValues = {};
226
+
227
+ for (let name in updates) {
228
+ oldValues[name] = this.#parameters.get(name);
229
+ }
230
+ // aborted by hook (updates have been overridden to {})
231
+ client.transport.emit(`${UPDATE_ABORT}-${this.id}-${instanceId}`, reqId, oldValues);
232
+ }
233
+ });
234
+
235
+ if (isOwner) {
236
+ // delete only if creator
237
+ client.transport.addListener(`${DELETE_REQUEST}-${this.id}-${instanceId}`, async reqId => {
238
+ // make sure hooks have been called when `delete()` fulfills
239
+ await this.#manager[kServerStateManagerDeletePrivateState](this);
240
+ // --------------------------------------------------------------------
241
+ // WARNING - MAKE SURE WE DON'T HAVE PROBLEM W/ THAT
242
+ // --------------------------------------------------------------------
243
+ // @todo - propagate server-side last, because if a subscription function sends a
244
+ // message to a client, network messages order are kept coherent
245
+ // this._subscriptions.forEach(func => func(updated));
246
+ for (let [instanceId, clientInfos] of this.#attachedClients) {
247
+ const attached = clientInfos.client;
248
+ this[kSharedStatePrivateDetachClient](instanceId, attached);
249
+
250
+ if (instanceId === this.#creatorInstanceId) {
251
+ attached.transport.emit(`${DELETE_RESPONSE}-${this.id}-${instanceId}`, reqId);
252
+ } else {
253
+ attached.transport.emit(`${DELETE_NOTIFICATION}-${this.id}-${instanceId}`);
254
+ }
255
+ }
256
+ });
257
+ } else {
258
+ // detach only if not creator
259
+ client.transport.addListener(`${DETACH_REQUEST}-${this.id}-${instanceId}`, (reqId) => {
260
+ this[kSharedStatePrivateDetachClient](instanceId, client);
261
+ client.transport.emit(`${DETACH_RESPONSE}-${this.id}-${instanceId}`, reqId);
262
+ });
263
+ }
264
+ }
265
+
266
+ [kSharedStatePrivateDetachClient](instanceId, client) {
267
+ this.#attachedClients.delete(instanceId);
268
+ // delete listeners
269
+ client.transport.removeAllListeners(`${UPDATE_REQUEST}-${this.id}-${instanceId}`);
270
+ client.transport.removeAllListeners(`${DELETE_REQUEST}-${this.id}-${instanceId}`);
271
+ client.transport.removeAllListeners(`${DETACH_REQUEST}-${this.id}-${instanceId}`);
272
+ }
273
+ }
274
+
275
+ export default SharedStatePrivate;
276
+
277
+
@@ -1,6 +1,10 @@
1
- import { parentPort } from 'node:worker_threads';
2
-
3
- import { getTime } from '@ircam/sc-gettime';
1
+ // @note - We use this common js syntax so that the server can be bundled to cjs
2
+ // which allows to build a server that can be accepted by Max `node.script`
3
+ //
4
+ // Should move back to regular esm module once Max has fixed its loader
5
+ export default `
6
+ const { parentPort } = require('node:worker_threads');
7
+ const { hrtime } = require('node:process');
4
8
 
5
9
  let stack = [];
6
10
  let averageLatencyWindow = 5;
@@ -9,7 +13,17 @@ let averageLatencyPeriod = 2;
9
13
  let intervalId = null;
10
14
  let meanLatency = 0;
11
15
 
12
- parentPort.on('message', msg => {
16
+ // workaround that sc-utils is pure ecmascript module
17
+ // 2024/09/06 - Just copy getTime implementation so that we don't even need the node_modules
18
+ const start = hrtime.bigint();
19
+
20
+ function getTime() {
21
+ const now = hrtime.bigint();
22
+ const delta = now - start;
23
+ return Number(delta) * 1e-9;
24
+ }
25
+
26
+ parentPort.on('message', async msg => {
13
27
  switch (msg.type) {
14
28
  case 'config': {
15
29
  averageLatencyWindow = msg.value.averageLatencyWindow;
@@ -55,3 +69,4 @@ function computeAverageLatency() {
55
69
  });
56
70
  }
57
71
  }
72
+ `;
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Internal schema used to audit the application.
2
+ * Internal shared state class used to audit the application.
3
+ * @private
3
4
  */
4
5
  export default {
5
6
  /**
@@ -17,7 +18,7 @@ export default {
17
18
  },
18
19
 
19
20
  /**
20
- * Average latency in seconds computed from ping/pong.
21
+ * Average latency in seconds computed from ping/pong information.
21
22
  */
22
23
  averageNetworkLatency: {
23
24
  type: 'float',
@@ -0,0 +1,127 @@
1
+ import http from 'node:http';
2
+ import https from 'node:https';
3
+ import fs from 'node:fs';
4
+ import {
5
+ X509Certificate,
6
+ createPrivateKey,
7
+ } from 'node:crypto';
8
+
9
+ import pem from 'pem';
10
+ import equal from 'fast-deep-equal';
11
+
12
+ import logger from '../common/logger.js';
13
+
14
+ /**
15
+ * @private
16
+ * @return {HttpServer|HttpsServer}
17
+ */
18
+ export async function createHttpServer(server) {
19
+ const serverConfig = server.config;
20
+
21
+ if (serverConfig.env.useHttps !== true) {
22
+ return http.createServer();
23
+ }
24
+
25
+ // https is more tricky
26
+ const httpsInfos = serverConfig.env.httpsInfos;
27
+ let useSelfSigned = false;
28
+
29
+ if (!httpsInfos || equal(httpsInfos, { cert: null, key: null })) {
30
+ useSelfSigned = true;
31
+ }
32
+
33
+ let httpsCertsInfos = null;
34
+ let httpsServer = null;
35
+
36
+ if (!useSelfSigned) {
37
+ try {
38
+ // existence of file is checked in constructor
39
+ let cert = fs.readFileSync(httpsInfos.cert);
40
+ let key = fs.readFileSync(httpsInfos.key);
41
+
42
+ let x509 = null;
43
+ // this fails with self-signed certificates for whatever reason...
44
+ try {
45
+ x509 = new X509Certificate(cert);
46
+ } catch {
47
+ throw new TypeError(`Cannot create https.Server: Invalid https cert file`);
48
+ }
49
+
50
+ try {
51
+ const keyObj = createPrivateKey(key);
52
+
53
+ if (!x509.checkPrivateKey(keyObj)) {
54
+ throw new TypeError(`Cannot create https.Server: Invalid https key file`);
55
+ }
56
+ } catch {
57
+ throw new TypeError(`Cannot create https.Server: Invalid https key file`);
58
+ }
59
+
60
+ // check is certificate is still valid
61
+ const now = Date.now();
62
+ const certExpire = Date.parse(x509.validTo);
63
+ const isValid = now < certExpire;
64
+
65
+ const diff = certExpire - now;
66
+ const daysRemaining = Math.round(diff / 1000 / 60 / 60 / 24);
67
+
68
+ httpsCertsInfos = {
69
+ selfSigned: false,
70
+ CN: x509.subject.split('=')[1],
71
+ altNames: x509.subjectAltName.split(',').map(e => e.trim().split(':')[1]),
72
+ validFrom: x509.validFrom,
73
+ validTo: x509.validTo,
74
+ isValid: isValid,
75
+ daysRemaining: daysRemaining,
76
+ };
77
+
78
+ httpsServer = https.createServer({ key, cert });
79
+ } catch (err) {
80
+ logger.error(`
81
+ Invalid certificate files, please check your:
82
+ - key file: ${httpsInfos.key}
83
+ - cert file: ${httpsInfos.cert}
84
+ `);
85
+
86
+ throw err;
87
+ }
88
+ } else {
89
+ // generate self signed certs or reused already existing ones
90
+ let cert = await server.db.get('httpsCert');
91
+ let key = await server.db.get('httpsKey');
92
+
93
+ if (!cert || !key) {
94
+ try {
95
+ const result = await new Promise((resolve, reject) => {
96
+ pem.createCertificate({ days: 1, selfSigned: true }, async (err, keys) => {
97
+ if (err) {
98
+ reject(err);
99
+ return;
100
+ }
101
+
102
+ resolve({
103
+ cert: keys.certificate,
104
+ key: keys.serviceKey,
105
+ });
106
+ });
107
+ });
108
+
109
+ cert = result.cert;
110
+ key = result.key;
111
+ // store the generated certs to reuse on next start
112
+ await server.db.set('httpsCert', cert);
113
+ await server.db.set('httpsKey', key);
114
+ } catch (err) {
115
+ logger.error(err.stack);
116
+ throw err;
117
+ }
118
+ }
119
+
120
+ httpsCertsInfos = { selfSigned: true };
121
+ httpsServer = https.createServer({ cert, key });
122
+ }
123
+
124
+ logger.httpsCertsInfos(httpsCertsInfos);
125
+
126
+ return httpsServer;
127
+ }
@@ -21,18 +21,19 @@ const encryptionIV = crypto
21
21
  .substring(0, 16);
22
22
 
23
23
 
24
+ /** @private */
24
25
  export function encryptData(obj) {
25
26
  const data = JSON.stringify(obj);
26
- const cipher = crypto.createCipheriv(encryptionMethod, key, encryptionIV)
27
- return Buffer.from(
28
- cipher.update(data, 'utf8', 'hex') + cipher.final('hex')
29
- ).toString('base64')
27
+ const cipher = crypto.createCipheriv(encryptionMethod, key, encryptionIV);
28
+
29
+ return Buffer.from(cipher.update(data, 'utf8', 'hex') + cipher.final('hex')).toString('base64');
30
30
  }
31
31
 
32
+ /** @private */
32
33
  export function decryptData(encryptedData) {
33
- const buff = Buffer.from(encryptedData, 'base64')
34
- const decipher = crypto.createDecipheriv(encryptionMethod, key, encryptionIV)
34
+ const buff = Buffer.from(encryptedData, 'base64');
35
+ const decipher = crypto.createDecipheriv(encryptionMethod, key, encryptionIV);
35
36
 
36
- const decrypted = `${decipher.update(buff.toString('utf8'), 'hex', 'utf8')}${decipher.final('utf8')}`
37
+ const decrypted = `${decipher.update(buff.toString('utf8'), 'hex', 'utf8')}${decipher.final('utf8')}`;
37
38
  return JSON.parse(decrypted);
38
39
  }
@@ -1,25 +1,68 @@
1
1
  /**
2
- * Server-side part of the *soundworks* framework.
3
- *
4
- * ```
5
- * import '@soundworks/helpers/polyfills.js';
6
- * import { Server } from '@soundworks/core/server.js';
7
- * import { loadConfig } from '../utils/load-config.js';
2
+ * @license
3
+ * Copyright (c) 2014-present IRCAM – Centre Pompidou (France, Paris)
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ */
6
+
7
+ // these types must be defined here, so that they can be imported into the application
8
+
9
+ /**
10
+ * The role of the client in the application.
8
11
  *
9
- * // - General documentation: https://soundworks.dev/
10
- * // - API documentation: https://soundworks.dev/api
11
- * // - Issue Tracker: https://github.com/collective-soundworks/soundworks/issues
12
- * // - Wizard & Tools: `npx soundworks`
12
+ * For browser client, this information is used to create the URL endpoint.
13
13
  *
14
- * const config = loadConfig(process.env.ENV, import.meta.url);
14
+ * @typedef ClientRole
15
+ * @type {string}
16
+ */
17
+ /**
18
+ * @typedef ClientDescription
19
+ * @type {object}
20
+ * @property {'browser'|'node'} runtime - Runtime on which the client is aimed at running
21
+ * @property {boolean} [default=false] - For browser client, define is the client should be
22
+ * accessible at the default / root HTTP endpoint
23
+ */
24
+ /**
25
+ * Application configuration object.
15
26
  *
16
- * const server = new Server(config);
17
- * server.setDefaultTemplateConfig();
27
+ * @typedef ServerAppConfig
28
+ * @type {object}
29
+ * @property {object<ClientRole, ClientDescription>} clients - Definition of the application clients.
30
+ * @property {string} [name=''] - Name of the application.
31
+ * @property {string} [author=''] - Name of the author.
32
+ */
33
+ /**
34
+ * Environment configuration object.
18
35
  *
19
- * await server.start();
20
- * ```
36
+ * @typedef ServerEnvConfig
37
+ * @type {objecy}
38
+ * @property {'development'|'production'} type - If set to 'production', will enforce
39
+ * some security feature (e.g. blocking access to sensitive APIs, etc.) based on
40
+ * the `auth`.
41
+ * @property {number} port - Port on which the server is listening.
42
+ * @property {string} serverAddress - Domain name or IP of the server.
43
+ * Mandatory when node clients are declared
44
+ * @property {boolean} useHttps - Define is the server run in http or in https.
45
+ * @property {object} [httpsInfos=null] - Path to cert files ( cert, key } for https.
46
+ * If not given and `useHttps` is `true` self certified certificates will be created.
47
+ * @property {string} [httpsInfos.cert=null] - Path to cert file.
48
+ * @property {string} [httpsInfos.key=null] - Path to key file.
49
+ * @property {object} [auth=null] - Configuration object for authenticating clients
50
+ * using simple HTTP authentication.
51
+ * @property {array.<ClientRole>} [auth.client=[]] - List of client role that must authenticate.
52
+ * @property {login} [auth.login=''] - Login
53
+ * @property {login} [auth.password=''] - Password
54
+ * @property {string} [env.baseUrl=''] - If running behind a proxy, base URL of the application.
55
+ */
56
+ /**
57
+ * Configuration object for the server.
21
58
  *
22
- * @namespace server
59
+ * @typedef ServerConfig
60
+ * @type {object}
61
+ * @property {AppConfig} app - Application configuration object.
62
+ * @property {ServerEnvConfig} env - Environment configuration object.
23
63
  */
64
+
24
65
  export { default as Server } from './Server.js';
25
- export { default as Context } from './Context.js';
66
+ export { default as ServerPlugin } from './ServerPlugin.js';
67
+ export { default as ServerContext } from './ServerContext.js';
68
+ export { default as version } from '../common/version.js';