@soundworks/core 3.7.0 → 4.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +16 -0
- package/LICENSE +1 -1
- package/README.md +32 -30
- package/package.json +48 -28
- package/src/client/Client.js +391 -0
- package/src/client/Context.js +225 -0
- package/src/client/ContextManager.js +111 -0
- package/src/client/Plugin.js +66 -0
- package/src/client/PluginManager.js +123 -0
- package/src/client/SharedState.js +84 -0
- package/src/client/Socket.js +389 -0
- package/src/client/StateManager.js +67 -0
- package/src/client/index.js +77 -0
- package/src/common/BasePlugin.js +163 -0
- package/src/common/BasePluginManager.js +227 -0
- package/{common/SharedState.js → src/common/BaseSharedState.js} +198 -234
- package/src/common/BaseStateManager.js +200 -0
- package/{common → src/common}/ParameterBag.js +74 -112
- package/{common → src/common}/SharedStatePrivate.js +56 -62
- package/src/common/constants.js +51 -0
- package/src/common/logger.js +154 -0
- package/src/common/promise-store.js +25 -0
- package/src/common/sockets-utils.js +73 -0
- package/src/common/utils.js +22 -0
- package/src/server/Client.js +50 -0
- package/src/server/Context.js +99 -0
- package/src/server/ContextManager.js +266 -0
- package/src/server/Plugin.js +77 -0
- package/src/server/PluginManager.js +150 -0
- package/src/server/Server.js +849 -0
- package/src/server/SharedState.js +86 -0
- package/{server → src/server}/Socket.js +114 -88
- package/{server → src/server}/Sockets.js +73 -60
- package/{common/SharedStateManagerServer.js → src/server/StateManager.js} +181 -129
- package/src/server/audit-network-latency.worker.js +57 -0
- package/src/server/audit-schema.js +42 -0
- package/src/server/index.js +45 -0
- package/tsconfig.json +21 -0
- package/types/client/Client.d.ts +194 -0
- package/types/client/Client.d.ts.map +1 -0
- package/types/client/Context.d.ts +139 -0
- package/types/client/Context.d.ts.map +1 -0
- package/types/client/ContextManager.d.ts +45 -0
- package/types/client/ContextManager.d.ts.map +1 -0
- package/types/client/Plugin.d.ts +53 -0
- package/types/client/Plugin.d.ts.map +1 -0
- package/types/client/PluginManager.d.ts +96 -0
- package/types/client/PluginManager.d.ts.map +1 -0
- package/types/client/SharedState.d.ts +85 -0
- package/types/client/SharedState.d.ts.map +1 -0
- package/types/client/Socket.d.ts +138 -0
- package/types/client/Socket.d.ts.map +1 -0
- package/types/client/StateManager.d.ts +72 -0
- package/types/client/StateManager.d.ts.map +1 -0
- package/types/client/index.d.ts +11 -0
- package/types/client/index.d.ts.map +1 -0
- package/types/common/BasePlugin.d.ts +145 -0
- package/types/common/BasePlugin.d.ts.map +1 -0
- package/types/common/BasePluginManager.d.ts +88 -0
- package/types/common/BasePluginManager.d.ts.map +1 -0
- package/types/common/BaseSharedState.d.ts +173 -0
- package/types/common/BaseSharedState.d.ts.map +1 -0
- package/types/common/BaseStateManager.d.ts +70 -0
- package/types/common/BaseStateManager.d.ts.map +1 -0
- package/types/common/ParameterBag.d.ts +143 -0
- package/types/common/ParameterBag.d.ts.map +1 -0
- package/types/common/SharedStatePrivate.d.ts +20 -0
- package/types/common/SharedStatePrivate.d.ts.map +1 -0
- package/types/common/constants.d.ts +35 -0
- package/types/common/constants.d.ts.map +1 -0
- package/types/common/logger.d.ts +16 -0
- package/types/common/logger.d.ts.map +1 -0
- package/types/common/promise-store.d.ts +4 -0
- package/types/common/promise-store.d.ts.map +1 -0
- package/types/common/sockets-utils.d.ts +9 -0
- package/types/common/sockets-utils.d.ts.map +1 -0
- package/types/common/utils.d.ts +5 -0
- package/types/common/utils.d.ts.map +1 -0
- package/types/server/Client.d.ts +40 -0
- package/types/server/Client.d.ts.map +1 -0
- package/types/server/Context.d.ts +59 -0
- package/types/server/Context.d.ts.map +1 -0
- package/types/server/ContextManager.d.ts +69 -0
- package/types/server/ContextManager.d.ts.map +1 -0
- package/types/server/Plugin.d.ts +70 -0
- package/types/server/Plugin.d.ts.map +1 -0
- package/types/server/PluginManager.d.ts +91 -0
- package/types/server/PluginManager.d.ts.map +1 -0
- package/types/server/Server.d.ts +223 -0
- package/types/server/Server.d.ts.map +1 -0
- package/types/server/SharedState.d.ts +86 -0
- package/types/server/SharedState.d.ts.map +1 -0
- package/types/server/Socket.d.ts +128 -0
- package/types/server/Socket.d.ts.map +1 -0
- package/types/server/Sockets.d.ts +73 -0
- package/types/server/Sockets.d.ts.map +1 -0
- package/types/server/StateManager.d.ts +190 -0
- package/types/server/StateManager.d.ts.map +1 -0
- package/types/server/audit-network-latency.worker.d.ts +2 -0
- package/types/server/audit-network-latency.worker.d.ts.map +1 -0
- package/types/server/audit-schema.d.ts +27 -0
- package/types/server/audit-schema.d.ts.map +1 -0
- package/types/server/index.d.ts +7 -0
- package/types/server/index.d.ts.map +1 -0
- package/client/AbstractExperience.js +0 -54
- package/client/AbstractPlugin.js +0 -230
- package/client/Client.js +0 -193
- package/client/PluginManager.js +0 -279
- package/client/Socket.js +0 -346
- package/client/index.js +0 -48
- package/client.js +0 -28
- package/common/SharedStateManagerClient.js +0 -194
- package/common/Signal.js +0 -40
- package/common/SignalAll.js +0 -44
- package/common/logger.js +0 -162
- package/common/shared-state-utils.js +0 -89
- package/common/sockets-encoder-decoder.js +0 -73
- package/server/AbstractExperience.js +0 -126
- package/server/AbstractPlugin.js +0 -214
- package/server/Client.js +0 -70
- package/server/PluginManager.js +0 -273
- package/server/Server.js +0 -785
- package/server/index.js +0 -49
- package/server.js +0 -28
package/server/Server.js
DELETED
|
@@ -1,785 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = void 0;
|
|
7
|
-
|
|
8
|
-
var _fs = _interopRequireDefault(require("fs"));
|
|
9
|
-
|
|
10
|
-
var _http = _interopRequireDefault(require("http"));
|
|
11
|
-
|
|
12
|
-
var _https = _interopRequireDefault(require("https"));
|
|
13
|
-
|
|
14
|
-
var _path = _interopRequireDefault(require("path"));
|
|
15
|
-
|
|
16
|
-
var _os = _interopRequireDefault(require("os"));
|
|
17
|
-
|
|
18
|
-
var _crypto = require("crypto");
|
|
19
|
-
|
|
20
|
-
var _chalk = _interopRequireDefault(require("chalk"));
|
|
21
|
-
|
|
22
|
-
var _compression = _interopRequireDefault(require("compression"));
|
|
23
|
-
|
|
24
|
-
var _keyv = _interopRequireDefault(require("keyv"));
|
|
25
|
-
|
|
26
|
-
var _keyvFile = _interopRequireDefault(require("keyv-file"));
|
|
27
|
-
|
|
28
|
-
var _lodash = _interopRequireDefault(require("lodash.merge"));
|
|
29
|
-
|
|
30
|
-
var _pem = _interopRequireDefault(require("pem"));
|
|
31
|
-
|
|
32
|
-
var _polka = _interopRequireDefault(require("polka"));
|
|
33
|
-
|
|
34
|
-
var _Client = _interopRequireDefault(require("./Client.js"));
|
|
35
|
-
|
|
36
|
-
var _PluginManager = _interopRequireDefault(require("./PluginManager.js"));
|
|
37
|
-
|
|
38
|
-
var _Sockets = _interopRequireDefault(require("./Sockets.js"));
|
|
39
|
-
|
|
40
|
-
var _SharedStateManagerServer = _interopRequireDefault(require("../common/SharedStateManagerServer.js"));
|
|
41
|
-
|
|
42
|
-
var _logger = _interopRequireDefault(require("../common/logger.js"));
|
|
43
|
-
|
|
44
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
45
|
-
|
|
46
|
-
let _dbNamespaces = new Set();
|
|
47
|
-
/**
|
|
48
|
-
* Server side entry point for a `soundworks` application.
|
|
49
|
-
*
|
|
50
|
-
* This object hosts configuration informations, as well as methods to
|
|
51
|
-
* initialize and start the application. It is also responsible for creating
|
|
52
|
-
* the static file (http) server as well as the socket server.
|
|
53
|
-
*
|
|
54
|
-
* @memberof server
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* import * as soundworks from 'soundworks/server';
|
|
58
|
-
* import MyExperience from './MyExperience';
|
|
59
|
-
*
|
|
60
|
-
* soundworks.server.init(config);
|
|
61
|
-
* const myExperience = new MyExperience();
|
|
62
|
-
* soundworks.server.start();
|
|
63
|
-
*/
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
class Server {
|
|
67
|
-
constructor() {
|
|
68
|
-
/**
|
|
69
|
-
* Configuration informations.
|
|
70
|
-
* Defaults to:
|
|
71
|
-
* ```
|
|
72
|
-
* {
|
|
73
|
-
* env: {
|
|
74
|
-
* type: 'development',
|
|
75
|
-
* port: 8000,
|
|
76
|
-
* subfolder: '',
|
|
77
|
-
* useHttps: false,
|
|
78
|
-
* },
|
|
79
|
-
* app: {
|
|
80
|
-
* name: 'soundworks',
|
|
81
|
-
* },
|
|
82
|
-
* }
|
|
83
|
-
* ```
|
|
84
|
-
*/
|
|
85
|
-
this.config = {};
|
|
86
|
-
/**
|
|
87
|
-
* Router. Internally use polka.
|
|
88
|
-
* (cf. {@link https://github.com/lukeed/polka})
|
|
89
|
-
*/
|
|
90
|
-
|
|
91
|
-
this.router = (0, _polka.default)(); // compression (must be set before serve-static)
|
|
92
|
-
|
|
93
|
-
this.router.use((0, _compression.default)());
|
|
94
|
-
/**
|
|
95
|
-
* Http(s) server instance. The node `http` or `https` module instance
|
|
96
|
-
* (cf. {@link https://nodejs.org/api/http.html})
|
|
97
|
-
*/
|
|
98
|
-
|
|
99
|
-
this.httpServer = null;
|
|
100
|
-
/**
|
|
101
|
-
* Key / value storage with Promise based Map API
|
|
102
|
-
* basically a wrapper around kvey (cf. {@link https://github.com/lukechilds/keyv})
|
|
103
|
-
* @private
|
|
104
|
-
*/
|
|
105
|
-
|
|
106
|
-
this.db = this.createNamespacedDb('core');
|
|
107
|
-
/**
|
|
108
|
-
* The {@link server.Sockets} instance. A small wrapper around
|
|
109
|
-
* [`ws`](https://github.com/websockets/ws) server.
|
|
110
|
-
* @see {@link server.Sockets}
|
|
111
|
-
* @type {server.Sockets}
|
|
112
|
-
*/
|
|
113
|
-
|
|
114
|
-
this.sockets = new _Sockets.default();
|
|
115
|
-
/**
|
|
116
|
-
* The {@link server.PluginManager} instance.
|
|
117
|
-
* @see {@link server.PluginManager}
|
|
118
|
-
* @type {server.PluginManager}
|
|
119
|
-
*/
|
|
120
|
-
|
|
121
|
-
this.pluginManager = new _PluginManager.default(this);
|
|
122
|
-
/**
|
|
123
|
-
* The {@link server.SharedStateManagerServer} instance.
|
|
124
|
-
* @see {@link server.SharedStateManagerServer}
|
|
125
|
-
* @type {server.SharedStateManagerServer}
|
|
126
|
-
*/
|
|
127
|
-
|
|
128
|
-
this.stateManager = new _SharedStateManagerServer.default();
|
|
129
|
-
/**
|
|
130
|
-
* Template engine that should implement a `compile` method.
|
|
131
|
-
* @type {Object}
|
|
132
|
-
*/
|
|
133
|
-
|
|
134
|
-
this.templateEngine = null;
|
|
135
|
-
/**
|
|
136
|
-
* Path to the directory containing the templates.
|
|
137
|
-
* Any filename corresponding to a registered browser client type will be used
|
|
138
|
-
* in priority, if not present fallback to `default`, i.e `${clientType}.tmpl`
|
|
139
|
-
* fallbacks to `default.tmpl`. Template files should have the `.tmpl` extension.
|
|
140
|
-
* @param {String}
|
|
141
|
-
*/
|
|
142
|
-
|
|
143
|
-
this.templateDirectory = null;
|
|
144
|
-
/**
|
|
145
|
-
* If https is required, will contain informations about the certificates
|
|
146
|
-
* (self-signed, validity dates, etc.)
|
|
147
|
-
*/
|
|
148
|
-
|
|
149
|
-
this.httpsInfos = null;
|
|
150
|
-
/**
|
|
151
|
-
* Required activities that must be started. Only used in Experience
|
|
152
|
-
* and Plugin - do not expose.
|
|
153
|
-
* @private
|
|
154
|
-
*/
|
|
155
|
-
|
|
156
|
-
this.activities = new Set();
|
|
157
|
-
/**
|
|
158
|
-
* Mapping between a `clientType` and its related activities.
|
|
159
|
-
* @private
|
|
160
|
-
*/
|
|
161
|
-
|
|
162
|
-
this._clientTypeActivitiesMap = {};
|
|
163
|
-
/**
|
|
164
|
-
* Optionnal routing defined for each client.
|
|
165
|
-
* @type {Object}
|
|
166
|
-
* @private
|
|
167
|
-
*/
|
|
168
|
-
|
|
169
|
-
this._routes = {};
|
|
170
|
-
/** @private */
|
|
171
|
-
|
|
172
|
-
this._htmlTemplateConfig = {
|
|
173
|
-
engine: null,
|
|
174
|
-
directory: null
|
|
175
|
-
};
|
|
176
|
-
/** @private */
|
|
177
|
-
|
|
178
|
-
this._listeners = new Map();
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
*
|
|
182
|
-
* Method to be called before `start` in the initialization lifecycle of the
|
|
183
|
-
* soundworks server.
|
|
184
|
-
*
|
|
185
|
-
* @param {Object} config
|
|
186
|
-
* @param {String} [config.defaultClient='player'] - Client that can access
|
|
187
|
-
* the application at its root url.
|
|
188
|
-
* @param {String} [config.env='development']
|
|
189
|
-
* @param {String} [config.port=8000] - Port on which the http(s) server will
|
|
190
|
-
* listen
|
|
191
|
-
* @param {String} [config.subpath] - If the application runs behind a
|
|
192
|
-
* proxy server (e.g. https://my-domain.com/my-app/`), path to the
|
|
193
|
-
* application root (i.e. 'my-app')
|
|
194
|
-
* @param {Boolean} [config.useHttps=false] - Define wheter to use or not an
|
|
195
|
-
* an https server.
|
|
196
|
-
* @param {Object} [config.httpsInfos=null] - if `useHttps` is `true`, object
|
|
197
|
-
* that give the path to `cert` and `key` files (`{ cert, key }`). If `null`
|
|
198
|
-
* an auto generated certificate will be generated, be aware that browsers
|
|
199
|
-
* will consider the application as not safe in the case.
|
|
200
|
-
*
|
|
201
|
-
* @param {Function} clientConfigFunction - function that filters / defines
|
|
202
|
-
* the configuration object that will be sent to a connecting client.
|
|
203
|
-
*
|
|
204
|
-
* @example
|
|
205
|
-
* // defaults to
|
|
206
|
-
* await server.init(
|
|
207
|
-
* {
|
|
208
|
-
* env: {
|
|
209
|
-
* type: 'development',
|
|
210
|
-
* port: 8000,
|
|
211
|
-
* subpath: '',
|
|
212
|
-
* useHttps: false,
|
|
213
|
-
* },
|
|
214
|
-
* app: {
|
|
215
|
-
* name: 'soundworks',
|
|
216
|
-
* author: 'someone'
|
|
217
|
-
* }
|
|
218
|
-
* },
|
|
219
|
-
* (clientType, serverConfig, httpRequest) => {
|
|
220
|
-
* return { clientType, ...serverConfig };
|
|
221
|
-
* }
|
|
222
|
-
* );
|
|
223
|
-
*/
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
async init(config, clientConfigFunction = (clientType, _serverConfig, _httpRequest) => ({
|
|
227
|
-
clientType
|
|
228
|
-
})) {
|
|
229
|
-
const defaultConfig = {
|
|
230
|
-
env: {
|
|
231
|
-
type: 'development',
|
|
232
|
-
port: 8000,
|
|
233
|
-
subpath: '',
|
|
234
|
-
websockets: {
|
|
235
|
-
path: 'socket',
|
|
236
|
-
pingInterval: 5000
|
|
237
|
-
},
|
|
238
|
-
useHttps: false,
|
|
239
|
-
crossOriginIsolated: true,
|
|
240
|
-
verbose: true
|
|
241
|
-
},
|
|
242
|
-
app: {
|
|
243
|
-
name: 'soundworks'
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
this.config = (0, _lodash.default)({}, defaultConfig, config);
|
|
247
|
-
|
|
248
|
-
_logger.default.configure(this.config.env.verbose); // @note: do not remove
|
|
249
|
-
// backward compatibility w/ assetsDomain and `soundworks-template`
|
|
250
|
-
// cf. https://github.com/collective-soundworks/soundworks/issues/35
|
|
251
|
-
// see also '@soundworks/plugin-audio-buffer-loader' (could be updated more
|
|
252
|
-
// easily as the `assetsDomain` entry was not documented)
|
|
253
|
-
// - we need to handle 3 cases:
|
|
254
|
-
// + old config style / old template
|
|
255
|
-
// + new config style / old template
|
|
256
|
-
// + new template
|
|
257
|
-
// @note: keep `websocket.path` as defined should be enough
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (!this.config.env.assetsDomain && this.config.env.subpath !== undefined) {
|
|
261
|
-
const subpath = this.config.env.subpath.replace(/^\//, '').replace(/\/$/, '');
|
|
262
|
-
|
|
263
|
-
if (subpath) {
|
|
264
|
-
this.config.env.assetsDomain = `/${subpath}/`;
|
|
265
|
-
} else {
|
|
266
|
-
this.config.env.assetsDomain = `/`;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
this._clientConfigFunction = clientConfigFunction; // basic http authentication
|
|
271
|
-
|
|
272
|
-
if (this.config.env.auth) {
|
|
273
|
-
this.router.use((req, res, next) => {
|
|
274
|
-
const isProtected = this.config.env.auth.clients.map(type => req.path.endsWith(`/${type}`)).reduce((acc, value) => acc || value, false);
|
|
275
|
-
|
|
276
|
-
if (isProtected) {
|
|
277
|
-
// authentication middleware
|
|
278
|
-
const auth = this.config.env.auth; // parse login and password from headers
|
|
279
|
-
|
|
280
|
-
const b64auth = (req.headers.authorization || '').split(' ')[1] || '';
|
|
281
|
-
const [login, password] = Buffer.from(b64auth, 'base64').toString().split(':'); // verify login and password are set and correct
|
|
282
|
-
|
|
283
|
-
if (login && password && login === auth.login && password === auth.password) {
|
|
284
|
-
// -> access granted...
|
|
285
|
-
return next();
|
|
286
|
-
} // -> access denied...
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
res.writeHead(401, {
|
|
290
|
-
'WWW-Authenticate': 'Basic',
|
|
291
|
-
'Content-Type': 'text/plain'
|
|
292
|
-
});
|
|
293
|
-
res.end('Authentication required.');
|
|
294
|
-
} else {
|
|
295
|
-
// route not protected
|
|
296
|
-
return next();
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
await this._emit('inited');
|
|
302
|
-
return Promise.resolve();
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Method to be called when `init` step is done in the initialization
|
|
306
|
-
* lifecycle of the soundworks server. Basically initialize plugins,
|
|
307
|
-
* define the routing and start the http-server.
|
|
308
|
-
*/
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
async start() {
|
|
312
|
-
// ------------------------------------------------------------
|
|
313
|
-
// init acitvities
|
|
314
|
-
// ------------------------------------------------------------
|
|
315
|
-
// we should at least have one activity registered for each client here
|
|
316
|
-
// i.e. the Experience so it's ok
|
|
317
|
-
this.activities.forEach(activity => {
|
|
318
|
-
activity.clientTypes.forEach(clientType => {
|
|
319
|
-
if (!(clientType in this.config.app.clients)) {
|
|
320
|
-
throw new Error(`[soundworks:core] no config found for client type ${clientType}`);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (!this._clientTypeActivitiesMap[clientType]) {
|
|
324
|
-
this._clientTypeActivitiesMap[clientType] = new Set();
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
this._clientTypeActivitiesMap[clientType].add(activity);
|
|
328
|
-
});
|
|
329
|
-
}); // start http server
|
|
330
|
-
|
|
331
|
-
const useHttps = this.config.env.useHttps || false;
|
|
332
|
-
return Promise.resolve() // ------------------------------------------------------------
|
|
333
|
-
// create HTTP(S) SERVER
|
|
334
|
-
// ------------------------------------------------------------
|
|
335
|
-
.then(async () => {
|
|
336
|
-
// create http server
|
|
337
|
-
if (!useHttps) {
|
|
338
|
-
const httpServer = _http.default.createServer();
|
|
339
|
-
|
|
340
|
-
return Promise.resolve(httpServer);
|
|
341
|
-
} else {
|
|
342
|
-
const httpsInfos = this.config.env.httpsInfos;
|
|
343
|
-
|
|
344
|
-
if (httpsInfos.key && httpsInfos.cert) {
|
|
345
|
-
// use given certificate
|
|
346
|
-
try {
|
|
347
|
-
const key = _fs.default.readFileSync(httpsInfos.key);
|
|
348
|
-
|
|
349
|
-
const cert = _fs.default.readFileSync(httpsInfos.cert); // this fails with self-signed certificates for whatever reason...
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const x509 = new _crypto.X509Certificate(cert);
|
|
353
|
-
this.httpsInfos = {
|
|
354
|
-
selfSigned: false,
|
|
355
|
-
CN: x509.subject.split('=')[1],
|
|
356
|
-
altNames: x509.subjectAltName.split(',').map(e => e.trim().split(':')[1]),
|
|
357
|
-
validFrom: x509.validFrom,
|
|
358
|
-
validTo: x509.validTo
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
const httpsServer = _https.default.createServer({
|
|
362
|
-
key,
|
|
363
|
-
cert
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
return Promise.resolve(httpsServer);
|
|
367
|
-
} catch (err) {
|
|
368
|
-
_logger.default.error(`
|
|
369
|
-
Invalid certificate files, please check your:
|
|
370
|
-
- key file: ${httpsInfos.key}
|
|
371
|
-
- cert file: ${httpsInfos.cert}
|
|
372
|
-
`);
|
|
373
|
-
|
|
374
|
-
throw err;
|
|
375
|
-
}
|
|
376
|
-
} else {
|
|
377
|
-
const key = await this.db.get('httpsKey');
|
|
378
|
-
const cert = await this.db.get('httpsCert');
|
|
379
|
-
|
|
380
|
-
if (key && cert) {
|
|
381
|
-
this.httpsInfos = {
|
|
382
|
-
selfSigned: true
|
|
383
|
-
};
|
|
384
|
-
|
|
385
|
-
const httpsServer = _https.default.createServer({
|
|
386
|
-
key,
|
|
387
|
-
cert
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
return Promise.resolve(httpsServer);
|
|
391
|
-
} else {
|
|
392
|
-
return new Promise(resolve => {
|
|
393
|
-
// generate certificate on the fly (for development purposes)
|
|
394
|
-
_pem.default.createCertificate({
|
|
395
|
-
days: 1,
|
|
396
|
-
selfSigned: true
|
|
397
|
-
}, async (err, keys) => {
|
|
398
|
-
if (err) {
|
|
399
|
-
_logger.default.error(err.stack);
|
|
400
|
-
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const key = keys.serviceKey;
|
|
405
|
-
const cert = keys.certificate;
|
|
406
|
-
this.httpsInfos = {
|
|
407
|
-
selfSigned: true
|
|
408
|
-
}; // we store the generated cert so that we don't have to re-accept
|
|
409
|
-
// the cert each time the server restarts
|
|
410
|
-
|
|
411
|
-
await this.db.set('httpsKey', key);
|
|
412
|
-
await this.db.set('httpsCert', cert);
|
|
413
|
-
|
|
414
|
-
const httpsServer = _https.default.createServer({
|
|
415
|
-
key,
|
|
416
|
-
cert
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
resolve(httpsServer);
|
|
420
|
-
});
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}).then(httpServer => {
|
|
426
|
-
this.httpServer = httpServer;
|
|
427
|
-
this.router.server = httpServer;
|
|
428
|
-
return Promise.resolve();
|
|
429
|
-
}).then(() => {
|
|
430
|
-
let nodeOnly = true; // do not throw if no browser clients are defined, very usefull for
|
|
431
|
-
// cleaning tests in particular
|
|
432
|
-
|
|
433
|
-
for (let clientType in this.config.app.clients) {
|
|
434
|
-
if (this.config.app.clients[clientType].target === 'browser') {
|
|
435
|
-
nodeOnly = false;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
if (!nodeOnly) {
|
|
440
|
-
if (this.templateEngine === null) {
|
|
441
|
-
throw new Error('Undefined "server.templateEngine": please provide a valid template engine');
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
if (this.templateDirectory === null) {
|
|
445
|
-
throw new Error('Undefined "server.templateDirectory": please provide a valid template directory');
|
|
446
|
-
}
|
|
447
|
-
} // ------------------------------------------------------------
|
|
448
|
-
// INIT ROUTING
|
|
449
|
-
// ------------------------------------------------------------
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
_logger.default.title(`configured clients and routing`);
|
|
453
|
-
|
|
454
|
-
const routes = [];
|
|
455
|
-
let defaultClientType = null;
|
|
456
|
-
|
|
457
|
-
for (let clientType in this.config.app.clients) {
|
|
458
|
-
if (this.config.app.clients[clientType].default === true) {
|
|
459
|
-
defaultClientType = clientType;
|
|
460
|
-
}
|
|
461
|
-
} // we must open default route last
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
for (let clientType in this._clientTypeActivitiesMap) {
|
|
465
|
-
// do nothing if client type is not registered in config
|
|
466
|
-
if (!(clientType in this.config.app.clients)) {
|
|
467
|
-
continue;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
if (clientType !== defaultClientType) {
|
|
471
|
-
const clientTarget = this.config.app.clients[clientType].target;
|
|
472
|
-
|
|
473
|
-
const path = this._openClientRoute(clientType, clientTarget, this.router);
|
|
474
|
-
|
|
475
|
-
routes.push({
|
|
476
|
-
clientType,
|
|
477
|
-
path
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
} // open default route last
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
for (let clientType in this._clientTypeActivitiesMap) {
|
|
484
|
-
// do nothing if client type is not registered in config
|
|
485
|
-
if (!(clientType in this.config.app.clients)) {
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
if (clientType === defaultClientType) {
|
|
490
|
-
const clientTarget = this.config.app.clients[clientType].target;
|
|
491
|
-
|
|
492
|
-
const path = this._openClientRoute(clientType, clientTarget, this.router, true);
|
|
493
|
-
|
|
494
|
-
routes.unshift({
|
|
495
|
-
clientType,
|
|
496
|
-
path
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
_logger.default.clientConfigAndRouting(routes, this.config);
|
|
502
|
-
|
|
503
|
-
return Promise.resolve();
|
|
504
|
-
}).then(() => {
|
|
505
|
-
// ------------------------------------------------------------
|
|
506
|
-
// START SOCKET SERVER
|
|
507
|
-
// ------------------------------------------------------------
|
|
508
|
-
this.sockets.start(this.httpServer, this.config.env.websockets, (clientType, socket) => this._onSocketConnection(clientType, socket));
|
|
509
|
-
return Promise.resolve();
|
|
510
|
-
}).then(async () => {
|
|
511
|
-
// ------------------------------------------------------------
|
|
512
|
-
// START SERVICE MANAGER
|
|
513
|
-
// ------------------------------------------------------------
|
|
514
|
-
return this.pluginManager.start();
|
|
515
|
-
}).then(() => {
|
|
516
|
-
// ------------------------------------------------------------
|
|
517
|
-
// START HTTP SERVER
|
|
518
|
-
// ------------------------------------------------------------
|
|
519
|
-
return new Promise(resolve => {
|
|
520
|
-
const port = this.config.env.port;
|
|
521
|
-
const useHttps = this.config.env.useHttps || false;
|
|
522
|
-
const protocol = useHttps ? 'https' : 'http';
|
|
523
|
-
|
|
524
|
-
const ifaces = _os.default.networkInterfaces();
|
|
525
|
-
|
|
526
|
-
this.router.listen(port, async () => {
|
|
527
|
-
_logger.default.title(`${protocol} server listening on`);
|
|
528
|
-
|
|
529
|
-
Object.keys(ifaces).forEach(dev => {
|
|
530
|
-
ifaces[dev].forEach(details => {
|
|
531
|
-
if (details.family === 'IPv4') {
|
|
532
|
-
_logger.default.ip(protocol, details.address, port);
|
|
533
|
-
}
|
|
534
|
-
});
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
if (this.httpsInfos !== null) {
|
|
538
|
-
_logger.default.title(`https certificates infos`);
|
|
539
|
-
|
|
540
|
-
if (this.httpsInfos.selfSigned) {
|
|
541
|
-
_logger.default.log(` self-signed: ${this.httpsInfos.selfSigned ? 'true' : 'false'}`);
|
|
542
|
-
} else {
|
|
543
|
-
_logger.default.log(` valid from: ${this.httpsInfos.validFrom}`);
|
|
544
|
-
|
|
545
|
-
_logger.default.log(` valid to: ${this.httpsInfos.validTo}`);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
await this._emit('started');
|
|
550
|
-
resolve();
|
|
551
|
-
});
|
|
552
|
-
});
|
|
553
|
-
});
|
|
554
|
-
} // @todo - handle gracefull close of the server
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
async stop() {
|
|
558
|
-
this.sockets.terminate();
|
|
559
|
-
this.httpServer.close();
|
|
560
|
-
await this._emit('stopped');
|
|
561
|
-
return Promise.resolve();
|
|
562
|
-
}
|
|
563
|
-
/**
|
|
564
|
-
* Open the route for the given client.
|
|
565
|
-
* @private
|
|
566
|
-
*/
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
_openClientRoute(clientType, target, router, isDefault = false) {
|
|
570
|
-
// only browser targets need a route
|
|
571
|
-
if (target === 'node') {
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
let route = '/';
|
|
576
|
-
|
|
577
|
-
if (!isDefault) {
|
|
578
|
-
route += `${clientType}`;
|
|
579
|
-
} // @todo - define what is this... looks completely not used and not usable...
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
if (this._routes[clientType]) {
|
|
583
|
-
route += this._routes[clientType];
|
|
584
|
-
} // define template filename: `${clientType}.html` or `default.html`
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const templateDirectory = this.templateDirectory;
|
|
588
|
-
|
|
589
|
-
const clientTmpl = _path.default.join(templateDirectory, `${clientType}.tmpl`);
|
|
590
|
-
|
|
591
|
-
const defaultTmpl = _path.default.join(templateDirectory, `default.tmpl`); // make it sync
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
let template;
|
|
595
|
-
|
|
596
|
-
try {
|
|
597
|
-
const stats = _fs.default.statSync(clientTmpl);
|
|
598
|
-
|
|
599
|
-
template = stats.isFile() ? clientTmpl : defaultTmpl;
|
|
600
|
-
} catch (err) {
|
|
601
|
-
template = defaultTmpl;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
let tmplString;
|
|
605
|
-
|
|
606
|
-
try {
|
|
607
|
-
tmplString = _fs.default.readFileSync(template, 'utf8');
|
|
608
|
-
} catch (err) {
|
|
609
|
-
throw new Error(`[@soundworks/core] html template file "${template}" not found`);
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
const tmpl = this.templateEngine.compile(tmplString); // http request
|
|
613
|
-
|
|
614
|
-
router.get(route, (req, res) => {
|
|
615
|
-
const data = this._clientConfigFunction(clientType, this.config, req); // @note: do not remove
|
|
616
|
-
// backward compatibility w/ assetsDomain and `soundworks-template`
|
|
617
|
-
// cf. https://github.com/collective-soundworks/soundworks/issues/35
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (this.config.env.subpath && !data.env.subpath) {
|
|
621
|
-
data.env.subpath = this.config.env.subpath;
|
|
622
|
-
} // cors / coop / coep headers for `crossOriginIsolated pages, enables
|
|
623
|
-
// sharedArrayBuffers and high precision timers
|
|
624
|
-
// this is the default behavior
|
|
625
|
-
// cf. https://web.dev/why-coop-coep/
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if (this.config.env.crossOriginIsolated) {
|
|
629
|
-
res.writeHead(200, {
|
|
630
|
-
'Cross-Origin-Resource-Policy': 'same-origin',
|
|
631
|
-
'Cross-Origin-Embedder-Policy': 'require-corp',
|
|
632
|
-
'Cross-Origin-Opener-Policy': 'same-origin'
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
const appIndex = tmpl(data);
|
|
637
|
-
res.end(appIndex);
|
|
638
|
-
}); // return route infos for logging on server start
|
|
639
|
-
|
|
640
|
-
return route;
|
|
641
|
-
}
|
|
642
|
-
/**
|
|
643
|
-
* Socket connection callback.
|
|
644
|
-
* @private
|
|
645
|
-
*/
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
_onSocketConnection(clientType, socket) {
|
|
649
|
-
const client = new _Client.default(clientType, socket);
|
|
650
|
-
const activities = this._clientTypeActivitiesMap[clientType];
|
|
651
|
-
|
|
652
|
-
if (activities == undefined) {
|
|
653
|
-
_logger.default.error(`[soundworks:core] No activity registered for "${clientType}", make sure an Experience has been created for this client`);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
socket.addListener('close', () => {
|
|
657
|
-
// clean sockets
|
|
658
|
-
socket.terminate(); // remove client from activities
|
|
659
|
-
|
|
660
|
-
if (activities) {
|
|
661
|
-
activities.forEach(activity => activity.disconnect(client));
|
|
662
|
-
} // destroy client
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
client.destroy();
|
|
666
|
-
});
|
|
667
|
-
socket.addListener('s:client:handshake', data => {
|
|
668
|
-
// client should throw error if no activity registered server-side (cf. #16)
|
|
669
|
-
if (activities === undefined) {
|
|
670
|
-
const err = {
|
|
671
|
-
type: 'no-activity'
|
|
672
|
-
};
|
|
673
|
-
socket.send('s:client:error', err);
|
|
674
|
-
return;
|
|
675
|
-
} // check coherence between client-side and server-side plugin requirements
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
const clientRequiredPlugins = data.requiredPlugins || [];
|
|
679
|
-
const serverRequiredPlugins = this.pluginManager.getRequiredPlugins(clientType);
|
|
680
|
-
const missingPlugins = [];
|
|
681
|
-
clientRequiredPlugins.forEach(pluginId => {
|
|
682
|
-
if (serverRequiredPlugins.indexOf(pluginId) === -1) {
|
|
683
|
-
missingPlugins.push(pluginId);
|
|
684
|
-
}
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
if (missingPlugins.length > 0) {
|
|
688
|
-
const err = {
|
|
689
|
-
type: 'plugins',
|
|
690
|
-
data: missingPlugins
|
|
691
|
-
};
|
|
692
|
-
socket.send('s:client:error', err);
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
activities.forEach(activity => activity.connect(client));
|
|
697
|
-
const {
|
|
698
|
-
id,
|
|
699
|
-
uuid
|
|
700
|
-
} = client;
|
|
701
|
-
socket.send('s:client:start', {
|
|
702
|
-
id,
|
|
703
|
-
uuid
|
|
704
|
-
});
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
/**
|
|
708
|
-
* Create namespaced databases for core and plugins
|
|
709
|
-
* (kind of experiemental API do not expose in doc for now)
|
|
710
|
-
*
|
|
711
|
-
* @note - introduced in v3.1.0-beta.1
|
|
712
|
-
* @note - used by core and plugin-audio-streams
|
|
713
|
-
* @private
|
|
714
|
-
*/
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
createNamespacedDb(namespace = null) {
|
|
718
|
-
if (namespace === null || !(typeof namespace === 'string')) {
|
|
719
|
-
throw new Error(`[soundworks:core] Invalid namespace for ".createNamespacedDb(namespace)", namespace is mandatory and should be a string`);
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
if (_dbNamespaces.has(namespace)) {
|
|
723
|
-
throw new Error(`[soundworks:core] Invalid namespace for ".createNamespacedDb(namespace)", namespace "${namespace}" already exists`);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
const dbDirectory = _path.default.join(process.cwd(), '.data');
|
|
727
|
-
|
|
728
|
-
if (!_fs.default.existsSync(dbDirectory)) {
|
|
729
|
-
_fs.default.mkdirSync(dbDirectory);
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
const filename = _path.default.join(dbDirectory, `soundworks-${namespace}.db`); // at note keyv-file doesn't seems to works
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const store = new _keyvFile.default({
|
|
736
|
-
filename
|
|
737
|
-
});
|
|
738
|
-
const db = new _keyv.default({
|
|
739
|
-
namespace,
|
|
740
|
-
store
|
|
741
|
-
});
|
|
742
|
-
db.on('error', err => _logger.default.error('[soundworks:core] db ${namespace} error: ${err}'));
|
|
743
|
-
return db;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
addListener(channel, callback) {
|
|
747
|
-
if (!this._listeners.has(channel)) {
|
|
748
|
-
const listeners = new Set();
|
|
749
|
-
|
|
750
|
-
this._listeners.set(channel, listeners);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
const listeners = this._listeners.get(channel);
|
|
754
|
-
|
|
755
|
-
listeners.add(callback);
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
removeListener(channel, callback) {
|
|
759
|
-
if (this._listeners.has(channel)) {
|
|
760
|
-
const listeners = this._listeners.get(channel);
|
|
761
|
-
|
|
762
|
-
listeners.delete(callback);
|
|
763
|
-
|
|
764
|
-
if (listeners.size === 0) {
|
|
765
|
-
this._listeners.delete(channel);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
/** @private */
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
async _emit(channel) {
|
|
773
|
-
if (this._listeners.has(channel)) {
|
|
774
|
-
const listeners = this._listeners.get(channel);
|
|
775
|
-
|
|
776
|
-
for (let callback of listeners) {
|
|
777
|
-
await callback();
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
var _default = Server;
|
|
785
|
-
exports.default = _default;
|