@soundworks/core 3.1.0-beta.0 → 3.1.0-beta.4
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/README.md +1 -0
- package/client/Client.js +1 -1
- package/client/PluginManager.js +4 -3
- package/client/Socket.js +70 -39
- package/common/ParameterBag.js +25 -13
- package/common/SharedState.js +62 -17
- package/common/SharedStateManagerClient.js +6 -0
- package/common/SharedStateManagerServer.js +6 -0
- package/common/SharedStatePrivate.js +96 -63
- package/common/shared-state-utils.js +5 -3
- package/common/sockets-encoder-decoder.js +1 -1
- package/package.json +12 -10
- package/server/Client.js +2 -4
- package/server/PluginManager.js +4 -3
- package/server/Server.js +51 -7
- package/server/utils/Db.js +0 -67
package/README.md
CHANGED
|
@@ -56,6 +56,7 @@ The simplest way to start a new `soundworks` application is using the applicatio
|
|
|
56
56
|
|
|
57
57
|
## Academic Papers
|
|
58
58
|
|
|
59
|
+
- Benjamin Matuszewski. A Web-Based Framework for Distributed Music System Research and Creation. AES - Journal of the Audio Engineering Society Audio-Accoustics-Application, Audio Engineering Society Inc, 2020. <[hal-03033143](https://hal.archives-ouvertes.fr/hal-03033143)>
|
|
59
60
|
- Benjamin Matuszewski. Soundworks - A Framework for Networked Music Systems on the Web - State of Affairs and New Developments. Proceedings of the Web Audio Conference (WAC) 2019, Dec 2019, Trondheim, Norway. <[hal-02387783](https://hal.archives-ouvertes.fr/hal-02387783)>
|
|
60
61
|
- Benjamin Matuszewski, Norbert Schnell, Frédéric Bevilacqua. Interaction Topologies in Mobile-Based Situated Networked Music Systems. Wireless Communications and Mobile Computing, Hindawi Publishing Corporation, 2019, 2019, pp.9142490. ⟨10.1155/2019/9142490⟩. <[hal-02086673](https://hal.archives-ouvertes.fr/hal-02086673)>
|
|
61
62
|
- Norbert Schnell, Sébastien Robaszkiewicz. Soundworks – A playground for artists and developers to create collaborative mobile web performances. `Proceedings of the Web Audio Conference (WAC'15), 2015, Paris, France. <[hal-01580797](https://hal.archives-ouvertes.fr/hal-01580797)>
|
package/client/Client.js
CHANGED
|
@@ -118,7 +118,7 @@ class Client {
|
|
|
118
118
|
// url: '',
|
|
119
119
|
path: 'socket' // pingInterval: 5 * 1000,
|
|
120
120
|
|
|
121
|
-
}, config.env.websockets); // init communications
|
|
121
|
+
}, config.env.websockets); // init socket communications (string and binary)
|
|
122
122
|
|
|
123
123
|
await this.socket.init(this.type, this.config);
|
|
124
124
|
return Promise.resolve();
|
package/client/PluginManager.js
CHANGED
|
@@ -141,7 +141,8 @@ class PluginManager {
|
|
|
141
141
|
|
|
142
142
|
register(name, factory, options = {}, deps = []) {
|
|
143
143
|
if (this._registeredPlugins[name]) {
|
|
144
|
-
|
|
144
|
+
const ctor = factory(_AbstractPlugin.default);
|
|
145
|
+
throw new Error(`[soundworks:core] Plugin "${name}" of type "${ctor.name}" already registered`);
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
const ctor = factory(_AbstractPlugin.default);
|
|
@@ -164,7 +165,7 @@ class PluginManager {
|
|
|
164
165
|
|
|
165
166
|
get(name, _experienceRequired = false) {
|
|
166
167
|
if (!this._registeredPlugins[name]) {
|
|
167
|
-
throw new Error(`Cannot get or require plugin "${name}", plugin is not registered
|
|
168
|
+
throw new Error(`[soundworks:core] Cannot get or require plugin "${name}", plugin is not registered
|
|
168
169
|
> registered plugins are:
|
|
169
170
|
${Object.keys(this._registeredPlugins).map(n => `> - ${n}\n`).join('')}
|
|
170
171
|
`);
|
|
@@ -172,7 +173,7 @@ ${Object.keys(this._registeredPlugins).map(n => `> - ${n}\n`).join('')}
|
|
|
172
173
|
|
|
173
174
|
|
|
174
175
|
if (_experienceRequired && this.signals.start.value === true) {
|
|
175
|
-
throw new Error(`Plugin "${name}" required after pluginManager start`);
|
|
176
|
+
throw new Error(`[soundworks:core] Plugin "${name}" required after pluginManager start`);
|
|
176
177
|
}
|
|
177
178
|
|
|
178
179
|
if (!this._instances[name]) {
|
package/client/Socket.js
CHANGED
|
@@ -53,6 +53,13 @@ class Socket {
|
|
|
53
53
|
|
|
54
54
|
/** @private */
|
|
55
55
|
this.ws = null;
|
|
56
|
+
/**
|
|
57
|
+
* WebSocket instance (binary protocol - binaryType = 'arraybuffer').
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/** @private */
|
|
61
|
+
|
|
62
|
+
this.binaryWs = null;
|
|
56
63
|
this._stringListeners = new Map();
|
|
57
64
|
this._binaryListeners = new Map();
|
|
58
65
|
}
|
|
@@ -67,11 +74,11 @@ class Socket {
|
|
|
67
74
|
/** @private */
|
|
68
75
|
|
|
69
76
|
|
|
70
|
-
init(clientType, config) {
|
|
77
|
+
async init(clientType, config) {
|
|
71
78
|
// unique key that allows to associate the two sockets to the same client.
|
|
72
79
|
// note: the key is only used to pair to two sockets, so its usage is very
|
|
73
80
|
// limited in time therefore a random number should hopefully be sufficient.
|
|
74
|
-
const key = (Math.random() + '').replace(/^0
|
|
81
|
+
const key = (Math.random() + '').replace(/^0\./, ''); // open web sockets
|
|
75
82
|
|
|
76
83
|
let {
|
|
77
84
|
path
|
|
@@ -106,47 +113,71 @@ class Socket {
|
|
|
106
113
|
// ----------------------------------------------------------
|
|
107
114
|
|
|
108
115
|
const stringSocketUrl = `${url}?binary=0&${queryParams}`;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
116
|
+
await new Promise((resolve, reject) => {
|
|
117
|
+
const trySocket = async () => {
|
|
118
|
+
log(`[string socket] trying connection - url: ${stringSocketUrl}`);
|
|
119
|
+
const ws = new _isomorphicWs.default(stringSocketUrl);
|
|
120
|
+
ws.addEventListener('open', connectEvent => {
|
|
121
|
+
// parse incoming messages for pubsub
|
|
122
|
+
this.ws = ws;
|
|
123
|
+
this.ws.addEventListener('message', e => {
|
|
124
|
+
const [channel, args] = (0, _socketsEncoderDecoder.unpackStringMessage)(e.data);
|
|
125
|
+
|
|
126
|
+
this._emit(false, channel, ...args);
|
|
127
|
+
}); // broadcast all `WebSocket` native events
|
|
128
|
+
|
|
129
|
+
['close', 'error', 'upgrade', 'message'].forEach(eventName => {
|
|
130
|
+
this.ws.addEventListener(eventName, e => {
|
|
131
|
+
this._emit(false, eventName, e);
|
|
132
|
+
});
|
|
133
|
+
}); // forward open event
|
|
134
|
+
|
|
135
|
+
this._emit(false, 'open', connectEvent); // continue with raw socket
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
resolve();
|
|
139
|
+
}); // cf. https://github.com/collective-soundworks/soundworks/issues/17
|
|
140
|
+
|
|
141
|
+
ws.addEventListener('error', e => {
|
|
142
|
+
if (e.type === 'error' && e.error.code === 'ECONNREFUSED') {
|
|
143
|
+
console.log('[socket] connection refused - (retry in 1s)');
|
|
144
|
+
setTimeout(trySocket, 1000);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
trySocket();
|
|
150
|
+
}); // // ----------------------------------------------------------
|
|
151
|
+
// // init binary socket
|
|
152
|
+
// // ----------------------------------------------------------
|
|
128
153
|
|
|
129
154
|
const binarySocketUrl = `${url}?binary=1&${queryParams}`;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
155
|
+
await new Promise((resolve, reject) => {
|
|
156
|
+
log(`[binary socket] trying connection - url: ${binarySocketUrl}`);
|
|
157
|
+
const ws = new _isomorphicWs.default(binarySocketUrl);
|
|
158
|
+
ws.binaryType = 'arraybuffer';
|
|
159
|
+
ws.addEventListener('open', connectEvent => {
|
|
160
|
+
// parse incoming messages for pubsub
|
|
161
|
+
this.binaryWs = ws;
|
|
162
|
+
this.binaryWs.addEventListener('message', e => {
|
|
163
|
+
const [channel, data] = (0, _socketsEncoderDecoder.unpackBinaryMessage)(e.data);
|
|
164
|
+
|
|
165
|
+
this._emit(true, channel, data);
|
|
166
|
+
}); // broadcast all `WebSocket` native events
|
|
167
|
+
|
|
168
|
+
['close', 'error', 'upgrade', 'message'].forEach(eventName => {
|
|
169
|
+
this.binaryWs.addEventListener(eventName, e => {
|
|
170
|
+
this._emit(true, eventName, e);
|
|
171
|
+
});
|
|
172
|
+
}); // forward open event
|
|
173
|
+
|
|
174
|
+
this._emit(true, 'open', connectEvent);
|
|
175
|
+
|
|
176
|
+
resolve();
|
|
146
177
|
});
|
|
147
|
-
}); //
|
|
178
|
+
}); // everyone is connected
|
|
148
179
|
|
|
149
|
-
return Promise.
|
|
180
|
+
return Promise.resolve();
|
|
150
181
|
}
|
|
151
182
|
/** @private */
|
|
152
183
|
|
package/common/ParameterBag.js
CHANGED
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.
|
|
6
|
+
exports.types = exports.sharedOptions = exports.default = void 0;
|
|
7
7
|
|
|
8
|
-
var _lodash = _interopRequireDefault(require("lodash.
|
|
8
|
+
var _lodash = _interopRequireDefault(require("lodash.clonedeep"));
|
|
9
9
|
|
|
10
10
|
var _fastDeepEqual = _interopRequireDefault(require("fast-deep-equal"));
|
|
11
11
|
|
|
12
12
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
13
|
|
|
14
14
|
// import types from './types';
|
|
15
|
+
// import shallowClone from 'shallow-clone';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* @typedef {Object} server.SharedStateManagerServer~schema
|
|
@@ -182,6 +183,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
182
183
|
/**
|
|
183
184
|
* @typedef {Object} server.SharedStateManagerServer~schemaAnyDef
|
|
184
185
|
*
|
|
186
|
+
* Note that the `any` type always return a shallow copy of the state internal
|
|
187
|
+
* value. Mutating the returned value will not modify the internal state.
|
|
188
|
+
*
|
|
185
189
|
* @property {String} [type='any'] - Parameter of any type.
|
|
186
190
|
* @property {Mixed} default - Default value of the parameter.
|
|
187
191
|
* @property {Boolean} [nullable=false] - Define if the parameter is nullable. If
|
|
@@ -209,8 +213,8 @@ const sharedOptions = {
|
|
|
209
213
|
event: false,
|
|
210
214
|
// if event=true, nullable=true
|
|
211
215
|
metas: {},
|
|
212
|
-
filterChange: true
|
|
213
|
-
|
|
216
|
+
filterChange: true,
|
|
217
|
+
immediate: false
|
|
214
218
|
};
|
|
215
219
|
exports.sharedOptions = sharedOptions;
|
|
216
220
|
const types = {
|
|
@@ -423,10 +427,17 @@ class ParameterBag {
|
|
|
423
427
|
|
|
424
428
|
|
|
425
429
|
getValues() {
|
|
426
|
-
|
|
430
|
+
let values = {};
|
|
431
|
+
|
|
432
|
+
for (let name in this._values) {
|
|
433
|
+
values[name] = this.get(name);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return values;
|
|
427
437
|
}
|
|
428
438
|
/**
|
|
429
|
-
* Return the value of the given parameter.
|
|
439
|
+
* Return the value of the given parameter. If the parameter is of `any` type,
|
|
440
|
+
* a deep copy is returned.
|
|
430
441
|
*
|
|
431
442
|
* @param {String} name - Name of the parameter.
|
|
432
443
|
* @return {Mixed} - Value of the parameter.
|
|
@@ -438,7 +449,11 @@ class ParameterBag {
|
|
|
438
449
|
throw new ReferenceError(`[stateManager] Cannot get value of undefined parameter "${name}"`);
|
|
439
450
|
}
|
|
440
451
|
|
|
441
|
-
|
|
452
|
+
if (this._schema[name].type === 'any') {
|
|
453
|
+
return (0, _lodash.default)(this._values[name]);
|
|
454
|
+
} else {
|
|
455
|
+
return this._values[name];
|
|
456
|
+
}
|
|
442
457
|
}
|
|
443
458
|
/**
|
|
444
459
|
* Set the value of a parameter. If the value of the parameter is updated
|
|
@@ -473,17 +488,14 @@ class ParameterBag {
|
|
|
473
488
|
|
|
474
489
|
const currentValue = this._values[name];
|
|
475
490
|
const updated = !(0, _fastDeepEqual.default)(currentValue, value);
|
|
476
|
-
this._values[name] = value;
|
|
477
|
-
|
|
478
|
-
if (def.event === true) {
|
|
479
|
-
this._values[name] = null;
|
|
480
|
-
} // return tuple so that the state manager can handle the `filterChange` option
|
|
481
|
-
|
|
491
|
+
this._values[name] = value; // return tuple so that the state manager can handle the `filterChange` option
|
|
482
492
|
|
|
483
493
|
return [value, updated];
|
|
484
494
|
}
|
|
485
495
|
/**
|
|
486
496
|
* Reset a parameter to its initialization values. Reset all parameters if no argument.
|
|
497
|
+
* @note - prefer `state.set(state.getInitValues())`
|
|
498
|
+
* or `state.set(state.getDefaultValues())`
|
|
487
499
|
*
|
|
488
500
|
* @param {String} [name=null] - Name of the parameter to reset.
|
|
489
501
|
*/
|
package/common/SharedState.js
CHANGED
|
@@ -46,20 +46,18 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
46
46
|
this._onDetachCallbacks = new Set();
|
|
47
47
|
this._onDeleteCallbacks = new Set(); // add listener for state updates
|
|
48
48
|
|
|
49
|
-
client.transport.addListener(`${_sharedStateUtils.UPDATE_RESPONSE}-${id}-${this.remoteId}`, (reqId, updates, context) => {
|
|
50
|
-
const updated = this._commit(updates, context, true, true);
|
|
51
|
-
|
|
49
|
+
client.transport.addListener(`${_sharedStateUtils.UPDATE_RESPONSE}-${id}-${this.remoteId}`, async (reqId, updates, context) => {
|
|
50
|
+
const updated = await this._commit(updates, context, true, true);
|
|
52
51
|
(0, _sharedStateUtils.resolveRequest)(reqId, updated);
|
|
53
52
|
}); // retrieve values but do not propagate to subscriptions
|
|
54
53
|
|
|
55
|
-
client.transport.addListener(`${_sharedStateUtils.UPDATE_ABORT}-${id}-${this.remoteId}`, (reqId, updates, context) => {
|
|
56
|
-
const updated = this._commit(updates, context, false, true);
|
|
57
|
-
|
|
54
|
+
client.transport.addListener(`${_sharedStateUtils.UPDATE_ABORT}-${id}-${this.remoteId}`, async (reqId, updates, context) => {
|
|
55
|
+
const updated = await this._commit(updates, context, false, true);
|
|
58
56
|
(0, _sharedStateUtils.resolveRequest)(reqId, updated);
|
|
59
57
|
});
|
|
60
|
-
client.transport.addListener(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${id}-${this.remoteId}`, (updates, context) => {
|
|
58
|
+
client.transport.addListener(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${id}-${this.remoteId}`, async (updates, context) => {
|
|
61
59
|
// cf. https://github.com/collective-soundworks/soundworks/issues/18
|
|
62
|
-
this._commit(updates, context, true, false);
|
|
60
|
+
await this._commit(updates, context, true, false);
|
|
63
61
|
}); // ---------------------------------------------
|
|
64
62
|
// DELETE initiated by creator, or schema deleted
|
|
65
63
|
// ---------------------------------------------
|
|
@@ -145,7 +143,7 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
145
143
|
/** @private */
|
|
146
144
|
|
|
147
145
|
|
|
148
|
-
_commit(updates, context, propagate = true, initiator = false) {
|
|
146
|
+
async _commit(updates, context, propagate = true, initiator = false) {
|
|
149
147
|
const newValues = {};
|
|
150
148
|
const oldValues = {};
|
|
151
149
|
|
|
@@ -154,7 +152,11 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
154
152
|
immediate,
|
|
155
153
|
filterChange,
|
|
156
154
|
event
|
|
157
|
-
} = this._parameters.getSchema(name);
|
|
155
|
+
} = this._parameters.getSchema(name); // @note 20211209 - we have an issue here server-side, because if the value
|
|
156
|
+
// is an object or an array, the reference is shared by everybody, therefore
|
|
157
|
+
// `changed` is always false and the new value is never propagated...
|
|
158
|
+
// FIXED - `state.get` now returns a deep copy when `type` is `any`
|
|
159
|
+
|
|
158
160
|
|
|
159
161
|
const oldValue = this._parameters.get(name);
|
|
160
162
|
|
|
@@ -165,7 +167,7 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
165
167
|
// @note - we don't need to check filterChange here because the value
|
|
166
168
|
// has been updated in parameters on the `set` side so can rely on `changed`
|
|
167
169
|
// to avoid retrigger listeners.
|
|
168
|
-
// If the value has overriden by the server, `changed` will true
|
|
170
|
+
// If the value has been overriden by the server, `changed` will true
|
|
169
171
|
// anyway so it should behave correctly.
|
|
170
172
|
if (!changed || event) {
|
|
171
173
|
continue;
|
|
@@ -177,14 +179,48 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
177
179
|
} // if the `UPDATE_REQUEST` as been aborted by the server, do not propagate
|
|
178
180
|
|
|
179
181
|
|
|
182
|
+
let promises = [];
|
|
183
|
+
|
|
180
184
|
if (propagate && Object.keys(newValues).length > 0) {
|
|
181
|
-
this._subscriptions.forEach(listener =>
|
|
185
|
+
this._subscriptions.forEach(listener => {
|
|
186
|
+
promises.push(listener(newValues, oldValues, context));
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
await Promise.all(promises); // reset events to null after propagation of all listeners
|
|
191
|
+
|
|
192
|
+
for (let name in newValues) {
|
|
193
|
+
const {
|
|
194
|
+
event
|
|
195
|
+
} = this._parameters.getSchema(name);
|
|
196
|
+
|
|
197
|
+
if (event) {
|
|
198
|
+
this._parameters.set(name, null);
|
|
199
|
+
}
|
|
182
200
|
}
|
|
183
201
|
|
|
184
202
|
return newValues;
|
|
185
203
|
}
|
|
186
204
|
/**
|
|
187
|
-
* Updates values of the state.
|
|
205
|
+
* Updates values of the state. Wait for all subscriptions to be resolved
|
|
206
|
+
* before resolving itself, i.e.:
|
|
207
|
+
*
|
|
208
|
+
* ```js
|
|
209
|
+
* const a = await server.stateManager.create('a');
|
|
210
|
+
* let asyncCallbackCalled = false;
|
|
211
|
+
*
|
|
212
|
+
* a.subscribe(updates => {
|
|
213
|
+
* return new Promise(resolve => {
|
|
214
|
+
* setTimeout(() => {
|
|
215
|
+
* asyncCallbackCalled = true;
|
|
216
|
+
* resolve();
|
|
217
|
+
* }, 100);
|
|
218
|
+
* });
|
|
219
|
+
* });
|
|
220
|
+
*
|
|
221
|
+
* await a.set({ bool: true });
|
|
222
|
+
* assert.equal(asyncCallbackCalled, true);
|
|
223
|
+
* ```
|
|
188
224
|
*
|
|
189
225
|
* @async
|
|
190
226
|
* @param {Object} updates - key / value pairs of updates to apply to the state.
|
|
@@ -230,7 +266,7 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
230
266
|
|
|
231
267
|
const [newValue, changed] = this._parameters.set(name, updates[name]);
|
|
232
268
|
|
|
233
|
-
if (changed || filterChange === false) {
|
|
269
|
+
if (changed || filterChange === false || event) {
|
|
234
270
|
immediateOldValues[name] = oldValue;
|
|
235
271
|
immediateNewValues[name] = newValue;
|
|
236
272
|
propagateNow = true;
|
|
@@ -318,7 +354,7 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
318
354
|
* // do something
|
|
319
355
|
* }
|
|
320
356
|
* }
|
|
321
|
-
* }
|
|
357
|
+
* });
|
|
322
358
|
*
|
|
323
359
|
* @see {common.SharedState#set}
|
|
324
360
|
* @see {common.SharedState#subscribe}
|
|
@@ -329,6 +365,8 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
329
365
|
*
|
|
330
366
|
* @param {common.SharedState~subscribeCallback} callback - callback to execute
|
|
331
367
|
* when an update is applied on the state.
|
|
368
|
+
* @param {Boolean} [executeListener=false] - execute the given listener with
|
|
369
|
+
* current state values. (`oldValues` will be set to `{}`, and `context` to `null`)
|
|
332
370
|
*
|
|
333
371
|
* @example
|
|
334
372
|
* state.subscribe(async (newValues, oldValues) => {
|
|
@@ -337,13 +375,20 @@ ${JSON.stringify(initValues, null, 2)}`);
|
|
|
337
375
|
* // do something
|
|
338
376
|
* }
|
|
339
377
|
* }
|
|
340
|
-
* }
|
|
378
|
+
* });
|
|
341
379
|
*/
|
|
342
380
|
|
|
343
381
|
|
|
344
|
-
subscribe(listener) {
|
|
382
|
+
subscribe(listener, executeListener = false) {
|
|
345
383
|
this._subscriptions.add(listener);
|
|
346
384
|
|
|
385
|
+
if (executeListener === true) {
|
|
386
|
+
const currentValues = this.getValues();
|
|
387
|
+
const oldValues = {};
|
|
388
|
+
const context = null;
|
|
389
|
+
listener(currentValues, oldValues, context);
|
|
390
|
+
}
|
|
391
|
+
|
|
347
392
|
return () => {
|
|
348
393
|
this._subscriptions.delete(listener);
|
|
349
394
|
};
|
|
@@ -99,6 +99,12 @@ class SharedStateManagerClient {
|
|
|
99
99
|
list.forEach(([schemaName, stateId, nodeId]) => {
|
|
100
100
|
this._observeListeners.forEach(callback => callback(schemaName, stateId, nodeId));
|
|
101
101
|
});
|
|
102
|
+
}); // ---------------------------------------------
|
|
103
|
+
// Clear cache when schema is deleted
|
|
104
|
+
// ---------------------------------------------
|
|
105
|
+
|
|
106
|
+
this.client.transport.addListener(_sharedStateUtils.DELETE_SCHEMA, schemaName => {
|
|
107
|
+
this._cachedSchemas.delete(schemaName);
|
|
102
108
|
});
|
|
103
109
|
}
|
|
104
110
|
/**
|
|
@@ -273,6 +273,7 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
|
|
|
273
273
|
|
|
274
274
|
|
|
275
275
|
deleteSchema(schemaName) {
|
|
276
|
+
// @note: deleting schema
|
|
276
277
|
for (let [id, state] of this._serverStatesById.entries()) {
|
|
277
278
|
if (state.schemaName === schemaName) {
|
|
278
279
|
for (let [remoteId, attached] of state._attachedClients.entries()) {
|
|
@@ -283,6 +284,11 @@ class SharedStateManagerServer extends _SharedStateManagerClient.default {
|
|
|
283
284
|
|
|
284
285
|
this._serverStatesById.delete(this.id);
|
|
285
286
|
}
|
|
287
|
+
} // clear schema cache of all connected clients
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
for (let client of this._clientByNodeId.values()) {
|
|
291
|
+
client.transport.emit(`${_sharedStateUtils.DELETE_SCHEMA}`, schemaName);
|
|
286
292
|
}
|
|
287
293
|
|
|
288
294
|
this._schemas.delete(schemaName); // delete registered hooks
|
|
@@ -7,6 +7,8 @@ exports.default = void 0;
|
|
|
7
7
|
|
|
8
8
|
var _ParameterBag = _interopRequireDefault(require("./ParameterBag.js"));
|
|
9
9
|
|
|
10
|
+
var _lodash = _interopRequireDefault(require("lodash.clonedeep"));
|
|
11
|
+
|
|
10
12
|
var _sharedStateUtils = require("./shared-state-utils");
|
|
11
13
|
|
|
12
14
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
@@ -41,83 +43,114 @@ class SharedStatePrivate {
|
|
|
41
43
|
// apply registered hooks
|
|
42
44
|
const hooks = this._manager._hooksBySchemaName.get(this.schemaName);
|
|
43
45
|
|
|
44
|
-
const values = this._parameters.getValues();
|
|
46
|
+
const values = this._parameters.getValues();
|
|
45
47
|
|
|
48
|
+
let hookAborted = false; // cf. https://github.com/collective-soundworks/soundworks/issues/45
|
|
46
49
|
|
|
47
50
|
for (let hook of hooks.values()) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// objects as dirty, because if the state is attached locally, we
|
|
61
|
-
// compare the Object instances instead of their values.
|
|
62
|
-
// @note - this should be made more robust but how?
|
|
63
|
-
const [newValue, changed] = this._parameters.set(name, updates[name]); // if `filterChange` is set to `false` we don't check if the value
|
|
64
|
-
// has been changed or not, it is always propagated to client states
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const {
|
|
68
|
-
filterChange
|
|
69
|
-
} = this._parameters.getSchema(name);
|
|
70
|
-
|
|
71
|
-
if (filterChange && changed || !filterChange) {
|
|
72
|
-
filteredUpdates[name] = newValue;
|
|
73
|
-
hasUpdates = true;
|
|
51
|
+
const result = await hook(updates, values, context);
|
|
52
|
+
|
|
53
|
+
if (result === null) {
|
|
54
|
+
// explicit abort if hook returns null
|
|
55
|
+
hookAborted = true;
|
|
56
|
+
break;
|
|
57
|
+
} else if (result === undefined) {
|
|
58
|
+
// implicit continue if hook returns undefined
|
|
59
|
+
continue;
|
|
60
|
+
} else {
|
|
61
|
+
// the hook returned an updates object
|
|
62
|
+
updates = result;
|
|
74
63
|
}
|
|
75
64
|
}
|
|
76
65
|
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
66
|
+
if (hookAborted === false) {
|
|
67
|
+
const filteredUpdates = {};
|
|
68
|
+
let hasUpdates = false;
|
|
69
|
+
|
|
70
|
+
for (let name in updates) {
|
|
71
|
+
// from v3.1.0 - the `filteredUpdates` check is made using 'fast-deep-equal'
|
|
72
|
+
// cf. https://github.com/epoberezkin/fast-deep-equal
|
|
73
|
+
// therefore unchanged objects are not considered changed
|
|
74
|
+
// nor propagated anymore.
|
|
75
|
+
// until v3.0.4 - we checked the `schema[name].type === 'any'`, to always consider
|
|
76
|
+
// objects as dirty, because if the state is attached locally, we
|
|
77
|
+
// compare the Object instances instead of their values.
|
|
78
|
+
// @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
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if (event) {
|
|
90
|
+
this._parameters.set(name, null);
|
|
100
91
|
}
|
|
101
|
-
}
|
|
102
92
|
|
|
103
|
-
|
|
104
|
-
|
|
93
|
+
if (filterChange && changed || !filterChange) {
|
|
94
|
+
filteredUpdates[name] = newValue;
|
|
95
|
+
hasUpdates = true;
|
|
96
|
+
}
|
|
105
97
|
}
|
|
106
98
|
|
|
107
|
-
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
99
|
+
if (hasUpdates) {
|
|
100
|
+
// send response to requester
|
|
101
|
+
// client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates);
|
|
102
|
+
// @note: we propagate server-side last, because as the server transport
|
|
103
|
+
// is synchronous it can break ordering if a subscription function makes
|
|
104
|
+
// itself an update in reaction to an update, therefore network messages
|
|
105
|
+
// order would be broken,
|
|
106
|
+
// we need to handle cases where:
|
|
107
|
+
// client state (client.id: 2) sends a request
|
|
108
|
+
// server attached state (client.id: -1) spot a problem and overrides the value
|
|
109
|
+
// we want the remote client (id: 2) to receive in the right order:
|
|
110
|
+
// * 1. the value it requested,
|
|
111
|
+
// * 2. the value overriden by the server-side attached state (id: -1)
|
|
112
|
+
// this problem could be solved properly with a reducer system:
|
|
113
|
+
// if (dirty) {
|
|
114
|
+
// -> call (async) reducer
|
|
115
|
+
// -> get values from reducer
|
|
116
|
+
//. -> dispatch to everybody
|
|
117
|
+
// }
|
|
118
|
+
for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
|
|
119
|
+
// propagate notification to all other attached clients except server
|
|
120
|
+
if (remoteId !== peerRemoteId && peer.id !== -1) {
|
|
121
|
+
peer.transport.emit(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (client.id !== -1) {
|
|
126
|
+
client.transport.emit(`${_sharedStateUtils.UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
|
|
130
|
+
// propagate notification to server
|
|
131
|
+
if (remoteId !== peerRemoteId && peer.id === -1) {
|
|
132
|
+
peer.transport.emit(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);
|
|
133
|
+
}
|
|
111
134
|
}
|
|
112
|
-
}
|
|
113
135
|
|
|
114
|
-
|
|
115
|
-
|
|
136
|
+
if (client.id === -1) {
|
|
137
|
+
client.transport.emit(`${_sharedStateUtils.UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
// propagate back to the requester that the update has been aborted
|
|
141
|
+
// ignore all other attached clients.
|
|
142
|
+
client.transport.emit(`${_sharedStateUtils.UPDATE_ABORT}-${this.id}-${remoteId}`, reqId, updates, context);
|
|
116
143
|
}
|
|
117
144
|
} else {
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
145
|
+
// retrieve values from inner state (also handle immediate approriately)
|
|
146
|
+
const oldValues = {};
|
|
147
|
+
|
|
148
|
+
for (let name in updates) {
|
|
149
|
+
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);
|
|
121
154
|
}
|
|
122
155
|
});
|
|
123
156
|
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
exports.UPDATE_RESPONSE = exports.UPDATE_REQUEST = exports.UPDATE_NOTIFICATION = exports.UPDATE_ABORT = exports.SERVER_ID = exports.OBSERVE_RESPONSE = exports.OBSERVE_REQUEST = exports.OBSERVE_NOTIFICATION = exports.DETACH_RESPONSE = exports.DETACH_REQUEST = exports.DETACH_ERROR = exports.DELETE_SCHEMA = exports.DELETE_RESPONSE = exports.DELETE_REQUEST = exports.DELETE_NOTIFICATION = exports.DELETE_ERROR = exports.CREATE_RESPONSE = exports.CREATE_REQUEST = exports.CREATE_ERROR = exports.ATTACH_RESPONSE = exports.ATTACH_REQUEST = exports.ATTACH_ERROR = void 0;
|
|
6
7
|
exports.idGenerator = idGenerator;
|
|
7
|
-
exports.storeRequestPromise = storeRequestPromise;
|
|
8
|
-
exports.resolveRequest = resolveRequest;
|
|
9
8
|
exports.rejectRequest = rejectRequest;
|
|
10
|
-
exports.
|
|
9
|
+
exports.resolveRequest = resolveRequest;
|
|
10
|
+
exports.storeRequestPromise = storeRequestPromise;
|
|
11
11
|
// id of the server when owner of a state
|
|
12
12
|
const SERVER_ID = -1;
|
|
13
13
|
exports.SERVER_ID = SERVER_ID;
|
|
@@ -51,6 +51,8 @@ const UPDATE_ABORT = 's:u:ab';
|
|
|
51
51
|
exports.UPDATE_ABORT = UPDATE_ABORT;
|
|
52
52
|
const UPDATE_NOTIFICATION = 's:u:not';
|
|
53
53
|
exports.UPDATE_NOTIFICATION = UPDATE_NOTIFICATION;
|
|
54
|
+
const DELETE_SCHEMA = 's:d:s';
|
|
55
|
+
exports.DELETE_SCHEMA = DELETE_SCHEMA;
|
|
54
56
|
|
|
55
57
|
function* idGenerator() {
|
|
56
58
|
for (let i = 0; true; i++) {
|
|
@@ -4,8 +4,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.packBinaryMessage = packBinaryMessage;
|
|
7
|
-
exports.unpackBinaryMessage = unpackBinaryMessage;
|
|
8
7
|
exports.packStringMessage = packStringMessage;
|
|
8
|
+
exports.unpackBinaryMessage = unpackBinaryMessage;
|
|
9
9
|
exports.unpackStringMessage = unpackStringMessage;
|
|
10
10
|
|
|
11
11
|
require("fast-text-encoding");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soundworks/core",
|
|
3
|
-
"version": "3.1.0-beta.
|
|
3
|
+
"version": "3.1.0-beta.4",
|
|
4
4
|
"description": "full-stack javascript framework for distributed audio visual experiences on the web",
|
|
5
5
|
"authors": [
|
|
6
6
|
"Benjamin Matuszewski",
|
|
@@ -32,36 +32,38 @@
|
|
|
32
32
|
"dev": "npm run build && (npm run dev:client & npm run dev:server & npm run dev:common)"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"chalk": "^
|
|
35
|
+
"chalk": "^4.1.2",
|
|
36
36
|
"columnify": "^1.5.4",
|
|
37
37
|
"compression": "^1.7.1",
|
|
38
38
|
"debug": "^4.1.1",
|
|
39
39
|
"fast-deep-equal": "^3.1.3",
|
|
40
40
|
"fast-text-encoding": "^1.0.0",
|
|
41
41
|
"isomorphic-ws": "^4.0.1",
|
|
42
|
-
"
|
|
43
|
-
"keyv": "^
|
|
44
|
-
"keyv-file": "^0.1.13",
|
|
42
|
+
"keyv": "^4.0.3",
|
|
43
|
+
"keyv-file": "^0.2.0",
|
|
45
44
|
"lodash.clonedeep": "^4.5.0",
|
|
46
45
|
"lodash.merge": "^4.6.2",
|
|
47
46
|
"pem": "^1.12.5",
|
|
48
47
|
"polka": "^0.5.2",
|
|
49
48
|
"serve-static": "^1.13.2",
|
|
50
|
-
"
|
|
49
|
+
"uuid": "^8.3.2",
|
|
51
50
|
"window-or-global": "^1.0.1",
|
|
52
|
-
"ws": "^
|
|
51
|
+
"ws": "^8.2.3"
|
|
53
52
|
},
|
|
54
53
|
"devDependencies": {
|
|
55
54
|
"@babel/cli": "^7.4.4",
|
|
56
55
|
"@babel/core": "^7.4.5",
|
|
57
56
|
"@babel/preset-env": "^7.4.5",
|
|
58
|
-
"braintree-jsdoc-template": "^3.3.0",
|
|
59
57
|
"chai": "^4.2.0",
|
|
60
58
|
"chokidar": "^3.0.1",
|
|
61
|
-
"chokidar-cli": "^
|
|
59
|
+
"chokidar-cli": "^3.0.0",
|
|
62
60
|
"docdash": "^1.2.0",
|
|
63
61
|
"jsdoc": "^3.6.2",
|
|
64
62
|
"markdown-toc": "^1.2.0",
|
|
65
|
-
"mocha": "^
|
|
63
|
+
"mocha": "^9.1.3"
|
|
64
|
+
},
|
|
65
|
+
"optionalDependencies": {
|
|
66
|
+
"bufferutil": "^4.0.5",
|
|
67
|
+
"utf-8-validate": "^5.0.7"
|
|
66
68
|
}
|
|
67
69
|
}
|
package/server/Client.js
CHANGED
|
@@ -5,9 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
8
|
+
var _uuid = require("uuid");
|
|
11
9
|
|
|
12
10
|
function* idGenerator() {
|
|
13
11
|
for (let i = 0; true; i++) {
|
|
@@ -47,7 +45,7 @@ class Client {
|
|
|
47
45
|
* @type {String}
|
|
48
46
|
*/
|
|
49
47
|
|
|
50
|
-
this.uuid = (0,
|
|
48
|
+
this.uuid = (0, _uuid.v4)();
|
|
51
49
|
/**
|
|
52
50
|
* Socket connection with the remote {@link client.Client}.
|
|
53
51
|
* @type {server.Socket}
|
package/server/PluginManager.js
CHANGED
|
@@ -86,7 +86,8 @@ class PluginManager {
|
|
|
86
86
|
|
|
87
87
|
register(name, factory = null, options = {}, deps = []) {
|
|
88
88
|
if (this._registeredPlugins[name]) {
|
|
89
|
-
|
|
89
|
+
const ctor = factory(_AbstractPlugin.default);
|
|
90
|
+
throw new Error(`[soundworks:core] Plugin "${name}" of type "${ctor.name}" already registered`);
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
const ctor = factory(_AbstractPlugin.default);
|
|
@@ -105,7 +106,7 @@ class PluginManager {
|
|
|
105
106
|
|
|
106
107
|
get(name, _experience = null) {
|
|
107
108
|
if (!this._registeredPlugins[name]) {
|
|
108
|
-
throw new Error(`Cannot get or require plugin "${name}", plugin is not registered
|
|
109
|
+
throw new Error(`[soundworks:core] Cannot get or require plugin "${name}", plugin is not registered
|
|
109
110
|
> registered plugins are:
|
|
110
111
|
${Object.keys(this._registeredPlugins).map(n => `> - ${n}\n`).join('')}
|
|
111
112
|
`);
|
|
@@ -113,7 +114,7 @@ ${Object.keys(this._registeredPlugins).map(n => `> - ${n}\n`).join('')}
|
|
|
113
114
|
|
|
114
115
|
|
|
115
116
|
if (_experience && this.signals.start.value === true) {
|
|
116
|
-
throw new Error(`Plugin "${name}" required after pluginManager start`);
|
|
117
|
+
throw new Error(`[soundworks:core] Plugin "${name}" required after pluginManager start`);
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
if (!this._instances[name]) {
|
package/server/Server.js
CHANGED
|
@@ -35,12 +35,17 @@ var _Sockets = _interopRequireDefault(require("./Sockets.js"));
|
|
|
35
35
|
|
|
36
36
|
var _SharedStateManagerServer = _interopRequireDefault(require("../common/SharedStateManagerServer.js"));
|
|
37
37
|
|
|
38
|
-
var _Db = _interopRequireDefault(require("./utils/Db.js"));
|
|
39
|
-
|
|
40
38
|
var _logger = _interopRequireDefault(require("./utils/logger.js"));
|
|
41
39
|
|
|
40
|
+
var _keyv = _interopRequireDefault(require("keyv"));
|
|
41
|
+
|
|
42
|
+
var _keyvFile = _interopRequireDefault(require("keyv-file"));
|
|
43
|
+
|
|
42
44
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
43
45
|
|
|
46
|
+
let _dbNamespaces = new Set();
|
|
47
|
+
|
|
48
|
+
let _dbStore = null;
|
|
44
49
|
/**
|
|
45
50
|
* Server side entry point for a `soundworks` application.
|
|
46
51
|
*
|
|
@@ -58,6 +63,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
58
63
|
* const myExperience = new MyExperience();
|
|
59
64
|
* soundworks.server.start();
|
|
60
65
|
*/
|
|
66
|
+
|
|
61
67
|
class Server {
|
|
62
68
|
constructor() {
|
|
63
69
|
/**
|
|
@@ -98,7 +104,7 @@ class Server {
|
|
|
98
104
|
* @private
|
|
99
105
|
*/
|
|
100
106
|
|
|
101
|
-
this.db =
|
|
107
|
+
this.db = this.createNamespacedDb('core');
|
|
102
108
|
/**
|
|
103
109
|
* The {@link server.Sockets} instance. A small wrapper around
|
|
104
110
|
* [`ws`](https://github.com/websockets/ws) server.
|
|
@@ -320,8 +326,8 @@ class Server {
|
|
|
320
326
|
}
|
|
321
327
|
} else {
|
|
322
328
|
return new Promise(async (resolve, reject) => {
|
|
323
|
-
const key = await this.db.get('
|
|
324
|
-
const cert = await this.db.get('
|
|
329
|
+
const key = await this.db.get('httpsKey');
|
|
330
|
+
const cert = await this.db.get('httpsCert');
|
|
325
331
|
|
|
326
332
|
if (key && cert) {
|
|
327
333
|
this._httpsInfos = {
|
|
@@ -346,8 +352,8 @@ class Server {
|
|
|
346
352
|
key: keys.serviceKey,
|
|
347
353
|
cert: keys.certificate
|
|
348
354
|
};
|
|
349
|
-
await this.db.set('
|
|
350
|
-
await this.db.set('
|
|
355
|
+
await this.db.set('httpsKey', this._httpsInfos.key);
|
|
356
|
+
await this.db.set('httpsCert', this._httpsInfos.cert);
|
|
351
357
|
|
|
352
358
|
const httpsServer = _https.default.createServer(this._httpsInfos);
|
|
353
359
|
|
|
@@ -566,6 +572,44 @@ class Server {
|
|
|
566
572
|
});
|
|
567
573
|
});
|
|
568
574
|
}
|
|
575
|
+
/**
|
|
576
|
+
* Create namespaced databases for core and plugins
|
|
577
|
+
* (kind of experiemental API do not expose in doc for now)
|
|
578
|
+
*
|
|
579
|
+
* @note - introduced in v3.1.0-beta.1
|
|
580
|
+
* @note - used by core and plugin-audio-streams
|
|
581
|
+
* @private
|
|
582
|
+
*/
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
createNamespacedDb(namespace = null) {
|
|
586
|
+
if (namespace === null || !(typeof namespace === 'string')) {
|
|
587
|
+
throw new Error(`[soundworks:core] Invalid namespace for ".createNamespacedDb(namespace)", namespace is mandatory and should be a string`);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (_dbNamespaces.has(namespace)) {
|
|
591
|
+
throw new Error(`[soundworks:core] Invalid namespace for ".createNamespacedDb(namespace)", namespace "${namespace}" already exists`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const dbDirectory = _path.default.join(process.cwd(), '.data');
|
|
595
|
+
|
|
596
|
+
if (!_fs.default.existsSync(dbDirectory)) {
|
|
597
|
+
_fs.default.mkdirSync(dbDirectory);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const filename = _path.default.join(dbDirectory, `soundworks-${namespace}.db`); // at note keyv-file doesn't seems to works
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
const store = new _keyvFile.default({
|
|
604
|
+
filename
|
|
605
|
+
});
|
|
606
|
+
const db = new _keyv.default({
|
|
607
|
+
namespace,
|
|
608
|
+
store
|
|
609
|
+
});
|
|
610
|
+
db.on('error', err => console.log(chalk.red('[soundworks:core] db ${namespace} error:'), err));
|
|
611
|
+
return db;
|
|
612
|
+
}
|
|
569
613
|
|
|
570
614
|
}
|
|
571
615
|
|
package/server/utils/Db.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = void 0;
|
|
7
|
-
|
|
8
|
-
var _keyv = _interopRequireDefault(require("keyv"));
|
|
9
|
-
|
|
10
|
-
var _keyvFile = _interopRequireDefault(require("keyv-file"));
|
|
11
|
-
|
|
12
|
-
var _path = _interopRequireDefault(require("path"));
|
|
13
|
-
|
|
14
|
-
var _fs = _interopRequireDefault(require("fs"));
|
|
15
|
-
|
|
16
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Simple in file key / value database.
|
|
20
|
-
* Do not expose for now, may not be the right way to go...
|
|
21
|
-
*
|
|
22
|
-
* @todo - implement options to change storage solution.
|
|
23
|
-
* cf. https://github.com/lukechilds/keyv
|
|
24
|
-
*
|
|
25
|
-
* @memberof server
|
|
26
|
-
* @private
|
|
27
|
-
*/
|
|
28
|
-
class Db {
|
|
29
|
-
constructor(options = {}) {
|
|
30
|
-
const dbDirectory = _path.default.join(process.cwd(), '.data');
|
|
31
|
-
|
|
32
|
-
if (!_fs.default.existsSync(dbDirectory)) {
|
|
33
|
-
_fs.default.mkdirSync(dbDirectory);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const filename = _path.default.join(dbDirectory, 'soundworks.db');
|
|
37
|
-
|
|
38
|
-
this.keyv = new _keyv.default({
|
|
39
|
-
namespace: 'soundworks',
|
|
40
|
-
store: new _keyvFile.default({
|
|
41
|
-
filename
|
|
42
|
-
})
|
|
43
|
-
});
|
|
44
|
-
this.keyv.on('error', err => console.log('Connection Error', err));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async get(key) {
|
|
48
|
-
const value = await this.keyv.get(key);
|
|
49
|
-
return value;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async set(key, value, expireDelayMs = undefined) {
|
|
53
|
-
await this.keyv.set(key, value, expireDelayMs);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async delete(key) {
|
|
57
|
-
await this.keyv.delete(key);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async clear() {
|
|
61
|
-
await this.keyv.clear();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
var _default = Db;
|
|
67
|
-
exports.default = _default;
|