@soundworks/core 4.0.0-alpha.5 → 4.0.0-alpha.7
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/.github/workflows/gh-pages.yml +31 -0
- package/.github/workflows/unit-tests.yml +18 -0
- package/package.json +4 -5
- package/src/client/Client.js +8 -1
- package/src/client/PluginManager.js +1 -1
- package/src/client/SharedStateCollection.js +35 -0
- package/src/client/Socket.js +16 -8
- package/src/common/BasePlugin.js +30 -20
- package/src/common/BasePluginManager.js +30 -14
- package/src/common/BaseSharedState.js +48 -13
- package/src/common/BaseSharedStateCollection.js +179 -0
- package/src/common/BaseStateManager.js +70 -16
- package/src/common/ParameterBag.js +63 -16
- package/src/server/Client.js +6 -0
- package/src/server/PluginManager.js +1 -1
- package/src/server/Server.js +183 -17
- package/src/server/SharedStateCollection.js +36 -0
- package/src/server/Sockets.js +25 -4
- package/src/server/StateManager.js +4 -2
- package/src/server/crypto.js +38 -0
- package/.github/workflows/main.yml +0 -15
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import BaseSharedStateCollection from '../common/BaseSharedStateCollection.js';
|
|
2
|
+
|
|
3
|
+
// this file exists only for documentation purposesSharedStateCollection.js
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @callback server.SharedStateCollection~onUpdateCallback
|
|
7
|
+
* @param {server.SharedState} state - State that triggered the update.
|
|
8
|
+
* @param {Object} newValues - Key / value pairs of the updates that have been
|
|
9
|
+
* applied to the state.
|
|
10
|
+
* @param {Object} oldValues - Key / value pairs of the updated params before
|
|
11
|
+
* the updates has been applied to the state.
|
|
12
|
+
* @param {Mixed} [context=null] - Optionnal context object that has been passed
|
|
13
|
+
* with the values updates in the `set` call.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The `SharedStateCollection` interface represent a collection of all states
|
|
18
|
+
* created from a given schema name on the network, at the execption of the ones
|
|
19
|
+
* created by the current node.
|
|
20
|
+
*
|
|
21
|
+
* ```
|
|
22
|
+
* const collection = await server.stateManager.getCollection('my-schema');
|
|
23
|
+
* const allValues = collection.getValues();
|
|
24
|
+
* collection.onUpdate((state, newValues, oldValues, context) => {
|
|
25
|
+
* // do something
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
* @memberof server
|
|
29
|
+
* @extends BaseSharedStateCollection
|
|
30
|
+
* @inheritdoc
|
|
31
|
+
* @hideconstructor
|
|
32
|
+
*/
|
|
33
|
+
class SharedStateCollection extends BaseSharedStateCollection {}
|
|
34
|
+
|
|
35
|
+
export default SharedStateCollection;
|
|
36
|
+
|
package/src/server/Sockets.js
CHANGED
|
@@ -2,9 +2,9 @@ import { Worker } from 'node:worker_threads';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
|
|
5
|
+
import querystring from 'querystring';
|
|
5
6
|
import { WebSocketServer } from 'ws';
|
|
6
7
|
import WebSocket from 'ws';
|
|
7
|
-
import querystring from 'querystring';
|
|
8
8
|
|
|
9
9
|
import Socket from './Socket.js';
|
|
10
10
|
|
|
@@ -69,13 +69,13 @@ class Sockets {
|
|
|
69
69
|
|
|
70
70
|
// init ws server
|
|
71
71
|
this._wss = new WebSocketServer({
|
|
72
|
-
|
|
72
|
+
noServer: true,
|
|
73
73
|
path: `/${config.path}`, // @note - update according to existing config files (aka cosima-apps)
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
this._wss.on('connection', (ws, req) => {
|
|
77
77
|
const queryString = querystring.decode(req.url.split('?')[1]);
|
|
78
|
-
const { role, key } = queryString;
|
|
78
|
+
const { role, key, token } = queryString;
|
|
79
79
|
const binary = !!(parseInt(queryString.binary));
|
|
80
80
|
|
|
81
81
|
if (binary) {
|
|
@@ -96,8 +96,29 @@ class Sockets {
|
|
|
96
96
|
socket.addToRoom('*');
|
|
97
97
|
socket.addToRoom(role);
|
|
98
98
|
|
|
99
|
-
onConnectionCallback(role, socket);
|
|
99
|
+
onConnectionCallback(role, socket, token);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// check if client can connect
|
|
104
|
+
server.httpServer.on('upgrade', async (req, socket, head) => {
|
|
105
|
+
const queryString = querystring.decode(req.url.split('?')[1]);
|
|
106
|
+
|
|
107
|
+
const { role, token } = queryString;
|
|
108
|
+
|
|
109
|
+
if (server.isProtected(role)) {
|
|
110
|
+
// we don't have any ip in the upgrade request, so we just check the
|
|
111
|
+
// connection token is pending
|
|
112
|
+
const allowed = server.isValidConnectionToken(token);
|
|
113
|
+
|
|
114
|
+
if (!allowed) {
|
|
115
|
+
socket.destroy('not allowed');
|
|
116
|
+
}
|
|
100
117
|
}
|
|
118
|
+
|
|
119
|
+
this._wss.handleUpgrade(req, socket, head, (ws) => {
|
|
120
|
+
this._wss.emit('connection', ws, req);
|
|
121
|
+
});
|
|
101
122
|
});
|
|
102
123
|
}
|
|
103
124
|
|
|
@@ -359,7 +359,9 @@ class StateManager extends BaseStateManager {
|
|
|
359
359
|
client.transport.emit(CREATE_RESPONSE, reqId, stateId, remoteId, schemaName, sendedSchema, currentValues);
|
|
360
360
|
|
|
361
361
|
this._observers.forEach(observer => {
|
|
362
|
-
observer.
|
|
362
|
+
if (observer.id !== nodeId) {
|
|
363
|
+
observer.transport.emit(OBSERVE_NOTIFICATION, schemaName, stateId, nodeId);
|
|
364
|
+
}
|
|
363
365
|
});
|
|
364
366
|
} catch (err) {
|
|
365
367
|
client.transport.emit(CREATE_ERROR, reqId, err.message);
|
|
@@ -428,7 +430,7 @@ class StateManager extends BaseStateManager {
|
|
|
428
430
|
const { schemaName, id, _creatorId } = state;
|
|
429
431
|
// only track application states
|
|
430
432
|
// (e.g. do not propagate infos about audit state)
|
|
431
|
-
if (!PRIVATE_STATES.includes(schemaName)) {
|
|
433
|
+
if (!PRIVATE_STATES.includes(schemaName) && _creatorId !== client.id) {
|
|
432
434
|
statesInfos.push([schemaName, id, _creatorId]);
|
|
433
435
|
}
|
|
434
436
|
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
|
|
4
|
+
// adapted from https://dev.to/jobizil/encrypt-and-decrypt-data-in-nodejs-using-aes-256-cbc-2l6d
|
|
5
|
+
// generate a new key and init vector each time the server restart, we don't need
|
|
6
|
+
// any persistency here
|
|
7
|
+
const secretKey = uuidv4();
|
|
8
|
+
const secretIv = uuidv4();
|
|
9
|
+
const encryptionMethod = 'aes-256-cbc';
|
|
10
|
+
|
|
11
|
+
const key = crypto
|
|
12
|
+
.createHash('sha512')
|
|
13
|
+
.update(secretKey)
|
|
14
|
+
.digest('hex')
|
|
15
|
+
.substring(0, 32);
|
|
16
|
+
|
|
17
|
+
const encryptionIV = crypto
|
|
18
|
+
.createHash('sha512')
|
|
19
|
+
.update(secretIv)
|
|
20
|
+
.digest('hex')
|
|
21
|
+
.substring(0, 16);
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
export function encryptData(obj) {
|
|
25
|
+
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')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function decryptData(encryptedData) {
|
|
33
|
+
const buff = Buffer.from(encryptedData, 'base64')
|
|
34
|
+
const decipher = crypto.createDecipheriv(encryptionMethod, key, encryptionIV)
|
|
35
|
+
|
|
36
|
+
const decrypted = `${decipher.update(buff.toString('utf8'), 'hex', 'utf8')}${decipher.final('utf8')}`
|
|
37
|
+
return JSON.parse(decrypted);
|
|
38
|
+
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
name: "Unit Tests"
|
|
2
|
-
on: push
|
|
3
|
-
|
|
4
|
-
jobs:
|
|
5
|
-
test:
|
|
6
|
-
runs-on: ubuntu-latest
|
|
7
|
-
steps:
|
|
8
|
-
- name: Checkout
|
|
9
|
-
uses: actions/checkout@v3.3.0
|
|
10
|
-
- name: 'Install Node'
|
|
11
|
-
uses: actions/setup-node@v3.6.0
|
|
12
|
-
- name: Install dependencies
|
|
13
|
-
run: npm install
|
|
14
|
-
- name: "Run Test"
|
|
15
|
-
run: npm test -- tests/**.spec.js
|