Socket
Socket
Sign inDemoInstall

@soundworks/core

Package Overview
Dependencies
17
Maintainers
2
Versions
60
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 4.0.0-alpha.14 to 4.0.0-alpha.15

CHANGELOG.md

40

package.json
{
"name": "@soundworks/core",
"version": "4.0.0-alpha.14",
"version": "4.0.0-alpha.15",
"description": "Open-source creative coding framework for distributed applications based on Web technologies",

@@ -46,16 +46,14 @@ "authors": [

"scripts": {
"doc": "rm -Rf docs && jsdoc -c .jsdoc.json --verbose && cp -R assets docs/",
"doc": "rm -Rf docs && jsdoc -c .jsdoc.json --verbose && cp -R misc/assets docs/",
"lint": "npx eslint src",
"test": "mocha",
"toc": "markdown-toc -i README.md",
"test": "npx mocha",
"test:all": "npx mocha tests/*/*.spec.js",
"types": "rm -Rf types && tsc",
"preversion": "npm run toc && git commit -am 'docs: build' --allow-empty"
"postversion": "node ./misc/scripts/check-changelog.js"
},
"dependencies": {
"@ircam/sc-gettime": "^1.0.1",
"@ircam/sc-utils": "^1.1.1",
"@ircam/sc-utils": "^1.3.2",
"chalk": "^5.3.0",
"columnify": "^1.6.0",
"compression": "^1.7.1",
"cross-fetch": "^3.1.8",
"express": "^4.18.1",

@@ -65,29 +63,25 @@ "fast-deep-equal": "^3.1.3",

"isomorphic-ws": "^5.0.0",
"keyv": "^4.2.2",
"keyv-file": "^0.2.0",
"lodash.clonedeep": "^4.5.0",
"lodash.merge": "^4.6.2",
"keyv": "^4.5.4",
"keyv-file": "^0.3.0",
"lodash": "^4.17.21",
"pem": "^1.14.8",
"template-literal": "^1.0.4",
"uuid": "^9.0.0",
"window-or-global": "^1.0.1",
"ws": "^8.12.0"
"uuid": "^9.0.1",
"ws": "^8.15.0"
},
"devDependencies": {
"@ircam/eslint-config": "^1.3.0",
"chai": "^4.3.6",
"chai": "^4.3.10",
"chai-shallow-deep-equal": "^1.4.6",
"commitizen": "^4.2.5",
"cz-conventional-changelog": "^3.3.0",
"docdash": "^2.0.1",
"docdash": "^2.0.2",
"dotenv": "^16.3.1",
"eslint": "^8.44.0",
"eslint-plugin-jsdoc": "^46.4.3",
"generate-changelog": "^1.8.0",
"eslint": "^8.55.0",
"eslint-plugin-jsdoc": "^46.9.0",
"jsdoc": "^4.0.0",
"markdown-toc": "^1.2.0",
"mocha": "^10.2.0",
"puppeteer": "^20.7.4",
"puppeteer": "^21.6.0",
"tcp-ping": "^0.1.1",
"typescript": "^5.1.6"
"typescript": "^5.3.3"
},

@@ -94,0 +88,0 @@ "optionalDependencies": {

@@ -6,3 +6,3 @@ # soundworks

![soundworks-logo](./assets/logo-200x200.png)
![soundworks-logo](./misc/assets/logo-200x200.png)

@@ -13,17 +13,2 @@ Open-source creative coding framework for distributed applications based on Web technologies.

*__WARNING: The version 4 of `@soundworks/core` is under heavy development.__*
## Getting Started
The best and most simple way to start using **soundworks** is to use the `@soundworks/create` wizard.
```sh
npx @soundworks/create@latest
```
![./assets/soundworks-create-min.gif](./assets/soundworks-create-min.gif)
See [https://soundworks.dev/tutorials/getting-started.html](https://soundworks.dev/tutorials/getting-started.html) for more informations on the wizard and how to start with **soundworks**.
<!--
## Documentation

@@ -33,22 +18,18 @@

- API: [https://soundworks.dev/api](https://soundworks.dev/api)
-->
## API
## Getting started
The API is not publicly published for now. To access the API documentation locally, just clone this repository, go to the v4 branch and launch some http server in the docs directory. For example, using the [serve](https://www.npmjs.com/package/serve) package:
The best and most simple way to start using **soundworks** is to use the `@soundworks/create` wizard.
```sh
git clone https://github.com/collective-soundworks/soundworks.git
cd soundworks
git checkout v4
serve docs
npx @soundworks/create@latest
```
Feel welcome to open an [issue](https://github.com/collective-soundworks/soundworks/issues) or a PR if you find any inconsistency, error or missing information in the documentation.
![soundworks-create](./misc/assets/soundworks-create-min.gif)
## Share with Us
See [https://soundworks.dev/tutorials/getting-started.html](https://soundworks.dev/tutorials/getting-started.html) for more informations on the wizard and how to start using **soundworks**.
If you made an application using **soundworks** please let us know here: https://github.com/collective-soundworks/soundworks/discussions/61
## Misc
## TypeScript Support
### TypeScript support

@@ -59,3 +40,3 @@ Basic TypeScript support will be proposed in a (hopefully) near future.

## Install
### Manual install

@@ -68,2 +49,6 @@ Note that the `@soundworks/core` package is automatically installed when creating an application using the `@soundworks/create` wizard, so most of the time you should not have to install this package manually. See [https://soundworks.dev/guides/getting-started.html](https://soundworks.dev/guides/getting-started.html) for more informations on the **soundworks** wizard.

## Share with Us
If you made an application using **soundworks** please let us know here: https://github.com/collective-soundworks/soundworks/discussions/61
## Credits

@@ -70,0 +55,0 @@

import Client from './Client.js';
import PromiseStore from '../common/PromiseStore.js';
import {

@@ -10,7 +11,2 @@ CONTEXT_ENTER_REQUEST,

} from '../common/constants.js';
import {
storeRequestPromise,
resolveRequest,
rejectRequest,
} from '../common/promise-store.js';

@@ -85,2 +81,5 @@ /**

/** @private */
this._promiseStore = new PromiseStore(this.constructor.name);
this.client.socket.addListener(CONTEXT_ENTER_RESPONSE, (reqId, contextName) => {

@@ -91,3 +90,3 @@ if (contextName !== this.name) {

resolveRequest(reqId);
this._promiseStore.resolve(reqId);
});

@@ -100,3 +99,3 @@

rejectRequest(reqId, msg);
this._promiseStore.reject(reqId, msg);
});

@@ -109,3 +108,3 @@

resolveRequest(reqId);
this._promiseStore.resolve(reqId);
});

@@ -118,3 +117,3 @@

rejectRequest(reqId, msg);
this._promiseStore.reject(reqId, msg);
});

@@ -215,3 +214,3 @@

await new Promise((resolve, reject) => {
const reqId = storeRequestPromise(resolve, reject);
const reqId = this._promiseStore.add(resolve, reject, 'enter-context');
this.client.socket.send(CONTEXT_ENTER_REQUEST, reqId, this.name);

@@ -250,3 +249,3 @@ });

await new Promise((resolve, reject) => {
const reqId = storeRequestPromise(resolve, reject);
const reqId = this._promiseStore.add(resolve, reject, 'exit-context');
this.client.socket.send(CONTEXT_EXIT_REQUEST, reqId, this.name);

@@ -253,0 +252,0 @@ });

@@ -18,5 +18,8 @@ import BaseSharedStateCollection from '../common/BaseSharedStateCollection.js';

* The `SharedStateCollection` interface represent a collection of all states
* created from a given schema name on the network, at the execption of the ones
* created by the current node.
* created from a given schema name on the network.
*
* It can optionnaly exclude the states created by the current node.
*
* See {@link client.StateManager#getCollection} for factory method API
*
* ```

@@ -23,0 +26,0 @@ * const collection = await client.stateManager.getCollection('my-schema');

import { isBrowser } from '@ircam/sc-utils';
import fetch from 'cross-fetch';
import WebSocket from 'isomorphic-ws';

@@ -4,0 +3,0 @@

@@ -1,2 +0,2 @@

import merge from 'lodash.merge';
import merge from 'lodash/merge.js';

@@ -3,0 +3,0 @@ /**

@@ -117,3 +117,3 @@ import { isPlainObject, isString } from '@ircam/sc-utils';

// instanciate all plugins
for (let [id, instance] of this._instances.entries()) {
for (let [_id, instance] of this._instances.entries()) {
instance.onStateChange(_values => this._propagateStateChange(instance));

@@ -120,0 +120,0 @@ }

import { isPlainObject } from '@ircam/sc-utils';
import ParameterBag from './ParameterBag.js';
import PromiseStore from './PromiseStore.js';
import {

@@ -16,7 +17,2 @@ DELETE_REQUEST,

} from './constants.js';
import {
storeRequestPromise,
resolveRequest,
rejectRequest,
} from './promise-store.js';

@@ -37,4 +33,9 @@ class BaseSharedState {

this._manager = manager;
/**
* true is the state has been detached or deleted
* @private
*/
this._detached = false;
/* @private */
this._promiseStore = new PromiseStore(this.constructor.name);

@@ -61,3 +62,3 @@ try {

const updated = await this._commit(updates, context, true, true);
resolveRequest(reqId, updated);
this._promiseStore.resolve(reqId, updated);
});

@@ -68,3 +69,3 @@

const updated = await this._commit(updates, context, false, true);
resolveRequest(reqId, updated);
this._promiseStore.resolve(reqId, updated);
});

@@ -108,3 +109,3 @@

await callback();
} catch(err) {
} catch (err) {
console.error(err.message);

@@ -120,3 +121,5 @@ }

this._clearDetach();
this._onDetachCallbacks.clear();
this._onDeleteCallbacks.clear();
this._promiseStore.flush();
});

@@ -141,8 +144,10 @@

this._clearDetach();
resolveRequest(reqId, this);
this._onDetachCallbacks.clear();
this._onDeleteCallbacks.clear();
this._promiseStore.resolve(reqId, this);
this._promiseStore.flush();
});
this._client.transport.addListener(`${DELETE_ERROR}-${this.id}`, (reqId, msg) => {
rejectRequest(reqId, msg);
this._promiseStore.reject(reqId, msg);
});

@@ -162,4 +167,6 @@

this._clearDetach();
resolveRequest(reqId, this);
this._onDetachCallbacks.clear();
this._onDeleteCallbacks.clear();
this._promiseStore.resolve(reqId, this);
this._promiseStore.flush();
});

@@ -169,3 +176,6 @@

this._client.transport.addListener(`${DETACH_ERROR}-${this.id}`, (reqId, msg) => {
rejectRequest(reqId, msg);
this._onDetachCallbacks.clear();
this._onDeleteCallbacks.clear();
this._promiseStore.reject(reqId, msg);
this._promiseStore.flush();
});

@@ -213,9 +223,2 @@ }

/** @private */
_clearDetach() {
this._onDetachCallbacks.clear();
this._onDeleteCallbacks.clear();
this._detached = true;
}
/** @private */
_clearTransport() {

@@ -338,3 +341,3 @@ // remove listeners

// on the node requesting the update
const _ = this._parameters.coerceValue(name, updates[name]);
this._parameters.coerceValue(name, updates[name]);

@@ -372,3 +375,3 @@ // @note: general idea...

return new Promise((resolve, reject) => {
const reqId = storeRequestPromise(resolve, reject);
const reqId = this._promiseStore.add(resolve, reject, 'update-request');
this._client.transport.emit(`${UPDATE_REQUEST}-${this.id}-${this.remoteId}`, reqId, updates, context);

@@ -492,2 +495,3 @@ });

this._detached = true; // mark detached early
this._onUpdateCallbacks.clear();

@@ -497,3 +501,3 @@

return new Promise((resolve, reject) => {
const reqId = storeRequestPromise(resolve, reject);
const reqId = this._promiseStore.add(resolve, reject, 'delete-request');
this._client.transport.emit(`${DELETE_REQUEST}-${this.id}-${this.remoteId}`, reqId);

@@ -503,3 +507,3 @@ });

return new Promise((resolve, reject) => {
const reqId = storeRequestPromise(resolve, reject);
const reqId = this._promiseStore.add(resolve, reject, 'detach-request');
this._client.transport.emit(`${DETACH_REQUEST}-${this.id}-${this.remoteId}`, reqId);

@@ -506,0 +510,0 @@ });

@@ -5,6 +5,9 @@ /**

class BaseSharedStateCollection {
constructor(stateManager, schemaName) {
constructor(stateManager, schemaName, options = {}) {
this._stateManager = stateManager;
this._schemaName = schemaName;
this._options = Object.assign({ excludeLocal: false }, options);
this._states = [];
this._onUpdateCallbacks = new Set();

@@ -17,3 +20,3 @@ this._onAttachCallbacks = new Set();

async _init() {
this._unobserve = await this._stateManager.observe(this._schemaName, async (schemaName, stateId, nodeId) => {
this._unobserve = await this._stateManager.observe(this._schemaName, async (schemaName, stateId) => {
const state = await this._stateManager.attach(schemaName, stateId);

@@ -37,7 +40,7 @@

this._onAttachCallbacks.forEach(callback => callback(state));
});
}, this._options);
}
/**
* Size of the collection
* Size of the collection, alias `size`
* @type {number}

@@ -50,2 +53,10 @@ */

/**
* Size of the collection, , alias `length`
* @type {number}
*/
get size() {
return this._states.length;
}
/**
* Detach from the collection, i.e. detach all underlying shared states.

@@ -56,7 +67,7 @@ * @type {number}

this._unobserve();
this._onUpdateCallbacks.clear();
const promises = Array.from(this._states).map(state => state.detach());
const promises = this._states.map(state => state.detach());
await Promise.all(promises);
this._onUpdateCallbacks.clear();
this._onDetachCallbacks.clear();

@@ -220,4 +231,4 @@ }

}
}
}
},
};
}

@@ -224,0 +235,0 @@ }

@@ -1,4 +0,5 @@

import { isString, isFunction } from '@ircam/sc-utils';
import { isString, isFunction, isPlainObject } from '@ircam/sc-utils';
import SharedState from './BaseSharedState.js';
import SharedStateCollection from './BaseSharedStateCollection.js';
import PromiseStore from './PromiseStore.js';
import {

@@ -13,2 +14,3 @@ CREATE_REQUEST,

OBSERVE_RESPONSE,
OBSERVE_ERROR,
OBSERVE_NOTIFICATION,

@@ -18,7 +20,2 @@ UNOBSERVE_NOTIFICATION,

} from './constants.js';
import {
storeRequestPromise,
resolveRequest,
rejectRequest,
} from './promise-store.js';

@@ -37,7 +34,10 @@ /**

this._statesById = new Map();
this._observeListeners = new Map(); // Map <callback, filterSchemaName>
this._cachedSchemas = new Map();
this._observeRequestCallbacks = new Map();
this._statesById = new Map(); // <id, state>
this._cachedSchemas = new Map(); // <shemaName, definition>
this._observeListeners = new Set(); // Set <[observedSchemaName, callback, options]>
this._observeRequestCallbacks = new Map(); // Map <reqId, [observedSchemaName, callback, options]>
this._promiseStore = new PromiseStore();
// ---------------------------------------------

@@ -59,7 +59,7 @@ // CREATE

resolveRequest(reqId, state);
this._promiseStore.resolve(reqId, state);
});
this.client.transport.addListener(CREATE_ERROR, (reqId, msg) => {
rejectRequest(reqId, msg);
this._promiseStore.reject(reqId, msg);
});

@@ -84,7 +84,7 @@

resolveRequest(reqId, state);
this._promiseStore.resolve(reqId, state);
});
this.client.transport.addListener(ATTACH_ERROR, (reqId, msg) => {
rejectRequest(reqId, msg);
this._promiseStore.reject(reqId, msg);
});

@@ -98,7 +98,14 @@

// we don't call another callback that may have been registered earlier.
const [callback, filterSchemaName] = this._observeRequestCallbacks.get(reqId);
const observeInfos = this._observeRequestCallbacks.get(reqId);
const [observedSchemaName, callback, options] = observeInfos;
// move observeInfos from `_observeRequestCallbacks` to `_observeListeners`
// to guarantee order of execution, @see not in `.observe`
this._observeRequestCallbacks.delete(reqId);
this._observeListeners.add(observeInfos);
const promises = list.map(([schemaName, stateId, nodeId]) => {
if (filterSchemaName === '*' || filterSchemaName === schemaName) {
const filter = this._filterObserve(observedSchemaName, schemaName, nodeId, options);
if (!filter) {
return callback(schemaName, stateId, nodeId);

@@ -113,4 +120,5 @@ } else {

const unsubscribe = () => {
this._observeListeners.delete(callback);
// no more listeners, we can stop receiving notification from the server
this._observeListeners.delete(observeInfos);
// no more listeners, we can stop receiving notifications from the server
if (this._observeListeners.size === 0) {

@@ -121,8 +129,17 @@ this.client.transport.emit(UNOBSERVE_NOTIFICATION);

resolveRequest(reqId, unsubscribe);
this._promiseStore.resolve(reqId, unsubscribe);
});
// Observe error can occur if observed schema name does not exists
this.client.transport.addListener(OBSERVE_ERROR, (reqId, msg) => {
this._observeRequestCallbacks.delete(reqId);
this._promiseStore.reject(reqId, msg);
});
this.client.transport.addListener(OBSERVE_NOTIFICATION, (schemaName, stateId, nodeId) => {
this._observeListeners.forEach((filterSchemaName, callback) => {
if (filterSchemaName === '*' || filterSchemaName === schemaName) {
this._observeListeners.forEach(observeInfos => {
const [observedSchemaName, callback, options] = observeInfos;
const filter = this._filterObserve(observedSchemaName, schemaName, nodeId, options);
if (!filter) {
callback(schemaName, stateId, nodeId);

@@ -141,2 +158,17 @@ }

/** @private */
_filterObserve(observedSchemaName, schemaName, creatorId, options) {
let filter = true;
// schema name filter filer
if (observedSchemaName === null || observedSchemaName === schemaName) {
filter = false;
}
// filter state created by client if excludeLocal is true
if (options.excludeLocal === true && creatorId === this.client.id) {
filter = true;
}
return filter;
}
/**

@@ -156,3 +188,3 @@ * Create a `SharedState` instance from a registered schema.

return new Promise((resolve, reject) => {
const reqId = storeRequestPromise(resolve, reject);
const reqId = this._promiseStore.add(resolve, reject, 'create-create');
const requireSchema = this._cachedSchemas.has(schemaName) ? false : true;

@@ -180,3 +212,3 @@ this.client.transport.emit(CREATE_REQUEST, reqId, schemaName, requireSchema, initValues);

// @todo - add a timeout
const reqId = storeRequestPromise(resolve, reject);
const reqId = this._promiseStore.add(resolve, reject, 'attach-request');
const requireSchema = this._cachedSchemas.has(schemaName) ? false : true;

@@ -194,11 +226,22 @@ this.client.transport.emit(ATTACH_REQUEST, reqId, schemaName, stateId, requireSchema);

* Notes:
* - The states that are created by the same node are not propagated through
* the observe callback.
* - The order of execution is not guaranted, i.e. an state attached in the
* `observe` callback could be created before the `async create` method resolves.
* - The order of execution is not guaranted between nodes, i.e. an state attached
* in the `observe` callback could be created before the `async create` method resolves.
* - Filtering, i.e. `observedSchemaName` and `options.excludeLocal` are handled
* on the node side, the server just notify all state creation activity and
* the node executes the given callbacks according to the different filter rules.
* Such strategy allows to share the observe notifications between all observers.
*
* @param {String} [schemaName] - optionnal schema name to filter the observed
* Alternative signatures:
* - .observe(callback)
* - .observe(schemaName, callback)
* - .observe(callback, options)
* - .observe(schemaName, callback, options)
*
* @param {string} [schemaName] - optionnal schema name to filter the observed
* states.
* @param {server.StateManager~ObserveCallback|client.StateManager~ObserveCallback}
* callback - Function to be called when a new state is created on the network.
* @param {object} options - Options.
* @param {boolean} [options.excludeLocal = false] - If set to true, exclude states
* created locallly, i.e. by the same node, from the collection.
* @returns {Promise<Function>} - Returns a Promise that resolves when the given

@@ -208,3 +251,3 @@ * callback as been executed on each existing states. The promise value is a

* @example
* client.stateManager.observe(async (schemaName, stateId, nodeId) => {
* client.stateManager.observe(async (schemaName, stateId) => {
* if (schemaName === 'something') {

@@ -220,21 +263,74 @@ * const state = await this.client.stateManager.attach(schemaName, stateId);

async observe(...args) {
let filterSchemaName;
const defaultOptions = {
excludeLocal: false,
};
let observedSchemaName;
let callback;
let options;
if (args.length === 1) {
filterSchemaName = '*';
callback = args[0];
switch (args.length) {
case 1: {
// variation: .observe(callback)
if (!isFunction(args[0])) {
throw new TypeError(`[stateManager] Invalid arguments, argument 1 should be a function"`);
}
if (!isFunction(callback)) {
throw new Error(`[stateManager] Invalid arguments, valid signatures are "stateManager.observe(callback)" or "stateManager.observe(schemaName, callback)"`);
observedSchemaName = null;
callback = args[0];
options = defaultOptions;
break;
}
} else if (args.length === 2) {
filterSchemaName = args[0];
callback = args[1];
case 2: {
// variation: .observe(schemaName, callback)
if (isString(args[0])) {
if (!isFunction(args[1])) {
throw new TypeError(`[stateManager] Invalid arguments, argument 2 should be a function"`);
}
if (!isString(filterSchemaName) || !isFunction(callback)) {
throw new Error(`[stateManager] Invalid arguments, valid signatures are "stateManager.observe(callback)" or "stateManager.observe(schemaName, callback)"`);
observedSchemaName = args[0];
callback = args[1];
options = defaultOptions;
// variation: .observe(callback, options) API
} else if (isFunction(args[0])) {
if (!isPlainObject(args[1])) {
throw new TypeError(`[stateManager] Invalid arguments, argument 2 should be an object"`);
}
observedSchemaName = null;
callback = args[0];
options = Object.assign(defaultOptions, args[1]);
} else {
throw new Error(`[stateManager] Invalid signature, refer to the StateManager.observe documentation"`);
}
break;
}
} else {
throw new Error(`[stateManager] Invalid arguments, valid signatures are "stateManager.observe(callback)" or "stateManager.observe(schemaName, callback)"`);
case 3: {
// variation: .observe(schemaName, callback, options)
if (!isString(args[0])) {
throw new TypeError(`[stateManager] Invalid arguments, argument 1 should be a string"`);
}
if (!isFunction(args[1])) {
throw new TypeError(`[stateManager] Invalid arguments, argument 2 should be a function"`);
}
if (!isPlainObject(args[2])) {
throw new TypeError(`[stateManager] Invalid arguments, argument 2 should be an object"`);
}
observedSchemaName = args[0];
callback = args[1];
options = Object.assign(defaultOptions, args[2]);
break;
}
// throw in all other cases
default: {
throw new Error(`[stateManager] Invalid signature, refer to the StateManager.observe documentation"`);
}
}

@@ -244,10 +340,27 @@

return new Promise((resolve, reject) => {
const reqId = storeRequestPromise(resolve, reject);
const reqId = this._promiseStore.add(resolve, reject, 'observe-request');
// store the callback for execution on the response. the returned Promise
// is fullfiled once callback has been executed with each existing states
this._observeRequestCallbacks.set(reqId, [callback, filterSchemaName]);
// store the callback for execution on subsequent notifications
this._observeListeners.set(callback, filterSchemaName);
const observeInfos = [observedSchemaName, callback, options];
this._observeRequestCallbacks.set(reqId, observeInfos);
this.client.transport.emit(OBSERVE_REQUEST, reqId);
// NOTE: do not store in `_observeListeners` yet as it can produce race
// conditions, e.g.:
// ```
// await client.stateManager.observe(async (schemaName, stateId, nodeId) => {});
// // client now receives OBSERVE_NOTIFICATIONS
// await otherClient.stateManager.create('a');
// // second observer added in between
// client.stateManager.observe(async (schemaName, stateId, nodeId) => {});
// ````
// OBSERVE_NOTIFICATION is received before the OBSERVE_RESPONSE, then the
// second observer is called twice:
// - OBSERVE_RESPONSE 1 []
// - OBSERVE_NOTIFICATION [ 'a', 1, 0 ]
// - OBSERVE_NOTIFICATION [ 'a', 1, 0 ] // this should not happen
// - OBSERVE_RESPONSE 1 [ [ 'a', 1, 0 ] ]
//
// cf. unit test `observe should properly behave in race condition`
this.client.transport.emit(OBSERVE_REQUEST, reqId, observedSchemaName);
});

@@ -257,12 +370,19 @@ }

/**
* Returns a collection of all the states created from the schema name. Except
* the ones created by the current node.
* Returns a collection of all the states created from the schema name.
*
* @param {string} schemaName - Name of the schema.
* @param {object} options - Options.
* @param {boolean} [options.excludeLocal = false] - If set to true, exclude states
* created locallly, i.e. by the same node, from the collection.
* @returns {server.SharedStateCollection|client.SharedStateCollection}
*/
async getCollection(schemaName) {
const collection = new SharedStateCollection(this, schemaName);
await collection._init();
async getCollection(schemaName, options) {
const collection = new SharedStateCollection(this, schemaName, options);
try {
await collection._init();
} catch (err) {
throw new Error(`Cannot create collection, schema "${schemaName}" does not exists`);
}
return collection;

@@ -269,0 +389,0 @@ }

@@ -24,2 +24,3 @@ // id of the server when owner of a state

export const OBSERVE_RESPONSE = 's:o:res';
export const OBSERVE_ERROR = 's:o:err';
export const OBSERVE_NOTIFICATION = 's:o:not';

@@ -51,3 +52,3 @@

// audit state schema name
export const AUDIT_STATE_NAME = 's:c:audit';
export const AUDIT_STATE_NAME = 'p:s:audit';
export const PRIVATE_STATES = [AUDIT_STATE_NAME];

@@ -1,2 +0,2 @@

import cloneDeep from 'lodash.clonedeep';
import cloneDeep from 'lodash/cloneDeep.js';
import equal from 'fast-deep-equal';

@@ -397,9 +397,9 @@

return this._schema;
} else {
if (!this.has(name)) {
throw new ReferenceError(`[SharedState] Cannot get schema description of undefined parameter "${name}"`);
}
}
return this._schema[name];
if (!this.has(name)) {
throw new ReferenceError(`[SharedState] Cannot get schema description of undefined parameter "${name}"`);
}
return this._schema[name];
}

@@ -410,5 +410,7 @@

const initValues = {};
for (let [name, def] of Object.entries(this._schema)) {
initValues[name] = def.initValue;
}
return initValues;

@@ -420,5 +422,7 @@ }

const defaults = {};
for (let [name, def] of Object.entries(this._schema)) {
defaults[name] = def.defaults;
defaults[name] = def.default;
}
return defaults;

@@ -425,0 +429,0 @@ }

@@ -92,26 +92,26 @@ import ParameterBag from '../common/ParameterBag.js';

if (hasUpdates) {
// send response to requester
// client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates);
// @note: we propagate server-side last, because as the server transport
// is synchronous it can break ordering if a subscription function makes
// itself an update in reaction to an update, therefore network messages
// order would be broken,
// we need to handle cases where:
// client state (client.id: 2) sends a request
// server attached state (client.id: -1) spot a problem and overrides the value
// we want the remote client (id: 2) to receive in the right order:
// We need to handle cases where:
// - client state (client.id: 2) sends a request
// - server attached state (client.id: -1) spot a problem and overrides the value
// We want the remote client (id: 2) to receive in the right order:
// * 1. the value it requested,
// * 2. the value overriden by the server-side attached state (id: -1)
//
// such problem is now better solved with the the upateHook system, none
// nonetheway we don't want to introduce inconsistencies here
//
// Then we propagate server-side last, because as the server transport
// is synchronous it can break ordering if a subscription function makes
// itself an update in reaction to an update. Propagating to server last
// alllows to maintain network messages order consistent.
// this problem could be solved properly with a reducer system:
// if (dirty) {
// -> call (async) reducer
// -> get values from reducer
//. -> dispatch to everybody
// }
// @note - remoteId correspond to unique remote state id
for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
// propagate notification to all other attached clients except server
// propagate RESPONSE to the client that originates the request if not the server
if (client.id !== -1) {
client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
}
// propagate NOTIFICATION to all other attached clients except server
for (let [peerRemoteId, peer] of this._attachedClients) {
if (remoteId !== peerRemoteId && peer.id !== -1) {

@@ -122,8 +122,9 @@ peer.transport.emit(`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);

if (client.id !== -1) {
// propagate RESPONSE to server if it is the requester
if (client.id === -1) {
client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
}
for (let [peerRemoteId, peer] of this._attachedClients.entries()) {
// propagate notification to server
// propagate NOTIFICATION other attached state that belongs to server
for (let [peerRemoteId, peer] of this._attachedClients) {
if (remoteId !== peerRemoteId && peer.id === -1) {

@@ -133,6 +134,2 @@ peer.transport.emit(`${UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context);

}
if (client.id === -1) {
client.transport.emit(`${UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context);
}
} else {

@@ -139,0 +136,0 @@ // propagate back to the requester that the update has been aborted

import 'fast-text-encoding';
import root from 'window-or-global';
const encoder = new root.TextEncoder('utf-8');
const decoder = new root.TextDecoder('utf-8');
const encoder = new TextEncoder('utf-8');
const decoder = new TextDecoder('utf-8');

@@ -60,3 +59,3 @@ const types = [

// slice (copy) the underlying ArrayBuffer to create a clean TypedArray
const data = new root[type](buffer.slice(startOffset));
const data = new globalThis[type](buffer.slice(startOffset));

@@ -63,0 +62,0 @@ return [channel, data];

import { parentPort } from 'node:worker_threads';
import { getTime } from '@ircam/sc-gettime';
import { getTime } from '@ircam/sc-utils';

@@ -5,0 +5,0 @@ let stack = [];

@@ -26,14 +26,13 @@ import crypto from 'node:crypto';

const data = JSON.stringify(obj);
const cipher = crypto.createCipheriv(encryptionMethod, key, encryptionIV)
return Buffer.from(
cipher.update(data, 'utf8', 'hex') + cipher.final('hex')
).toString('base64')
const cipher = crypto.createCipheriv(encryptionMethod, key, encryptionIV);
return Buffer.from(cipher.update(data, 'utf8', 'hex') + cipher.final('hex')).toString('base64');
}
export function decryptData(encryptedData) {
const buff = Buffer.from(encryptedData, 'base64')
const decipher = crypto.createDecipheriv(encryptionMethod, key, encryptionIV)
const buff = Buffer.from(encryptedData, 'base64');
const decipher = crypto.createDecipheriv(encryptionMethod, key, encryptionIV);
const decrypted = `${decipher.update(buff.toString('utf8'), 'hex', 'utf8')}${decipher.final('utf8')}`
const decrypted = `${decipher.update(buff.toString('utf8'), 'hex', 'utf8')}${decipher.final('utf8')}`;
return JSON.parse(decrypted);
}

@@ -8,4 +8,3 @@ import fs from 'node:fs';

import { getTime } from '@ircam/sc-gettime';
import { isPlainObject, idGenerator } from '@ircam/sc-utils';
import { isPlainObject, idGenerator, getTime } from '@ircam/sc-utils';
import chalk from 'chalk';

@@ -16,3 +15,3 @@ import compression from 'compression';

import { KeyvFile } from 'keyv-file';
import merge from 'lodash.merge';
import merge from 'lodash/merge.js';
import pem from 'pem';

@@ -19,0 +18,0 @@ import compile from 'template-literal';

@@ -18,5 +18,8 @@ import BaseSharedStateCollection from '../common/BaseSharedStateCollection.js';

* The `SharedStateCollection` interface represent a collection of all states
* created from a given schema name on the network, at the execption of the ones
* created by the current node.
* created from a given schema name on the network.
*
* It can optionnaly exclude the states created by the current node.
*
* See {@link server.StateManager#getCollection} for factory method API
*
* ```

@@ -23,0 +26,0 @@ * const collection = await server.stateManager.getCollection('my-schema');

@@ -1,2 +0,2 @@

import { getTime } from '@ircam/sc-gettime';
import { getTime } from '@ircam/sc-utils';
import {

@@ -3,0 +3,0 @@ packBinaryMessage,

import EventEmitter from 'node:events';
import { idGenerator, isString, isPlainObject } from '@ircam/sc-utils';
import clonedeep from 'lodash.clonedeep';
import clonedeep from 'lodash/cloneDeep.js';

@@ -20,2 +20,3 @@ import BaseStateManager from '../common/BaseStateManager.js';

OBSERVE_RESPONSE,
OBSERVE_ERROR,
OBSERVE_NOTIFICATION,

@@ -363,3 +364,4 @@ UNOBSERVE_NOTIFICATION,

this._observers.forEach(observer => {
if (observer.id !== nodeId) {
// prevent propagating infos about internal states
if (!PRIVATE_STATES.includes(schemaName)) {
observer.transport.emit(OBSERVE_NOTIFICATION, schemaName, stateId, nodeId);

@@ -369,4 +371,6 @@ }

} catch (err) {
client.transport.emit(CREATE_ERROR, reqId, err.message);
console.error(err.message);
const msg = `Cannot create state "${schemaName}", ${err.message}`;
console.error(msg);
client.transport.emit(CREATE_ERROR, reqId, msg);
}

@@ -376,2 +380,3 @@ } else {

console.error(msg);
client.transport.emit(CREATE_ERROR, reqId, msg);

@@ -417,9 +422,11 @@ }

const msg = `Cannot attach, no existing state for schema "${schemaName}" with stateId: "${stateId}"`;
console.error(msg);
client.transport.emit(ATTACH_ERROR, reqId, msg);
console.error(msg);
}
} else {
const msg = `Cannot attach, schema "${schemaName}" does not exists`;
console.error(msg);
client.transport.emit(ATTACH_ERROR, reqId, msg);
console.error(msg);
}

@@ -431,19 +438,24 @@ });

// ---------------------------------------------
client.transport.addListener(OBSERVE_REQUEST, reqId => {
const statesInfos = [];
client.transport.addListener(OBSERVE_REQUEST, (reqId, observedSchemaName) => {
if (observedSchemaName === null || this._schemas.has(observedSchemaName)) {
const statesInfos = [];
this._serverStatesById.forEach(state => {
const { schemaName, id, _creatorId } = state;
// only track application states
// (e.g. do not propagate infos about audit state)
if (!PRIVATE_STATES.includes(schemaName) && _creatorId !== client.id) {
statesInfos.push([schemaName, id, _creatorId]);
}
});
this._serverStatesById.forEach(state => {
const { schemaName, id, _creatorId } = state;
// add client to observers first because if some (sync) server side
// callback throws, the client would never be added to the list
this._observers.add(client);
// prevent propagating infos about internal states
if (!PRIVATE_STATES.includes(schemaName)) {
statesInfos.push([schemaName, id, _creatorId]);
}
});
client.transport.emit(OBSERVE_RESPONSE, reqId, ...statesInfos);
// add client to observers first because if some synchronous server side
// callback throws, the client would never be added to the list
this._observers.add(client);
client.transport.emit(OBSERVE_RESPONSE, reqId, ...statesInfos);
} else {
const msg = `Cannot observe, schema "${observedSchemaName}" does not exists`;
client.transport.emit(OBSERVE_ERROR, reqId, msg);
}
});

@@ -563,3 +575,3 @@

this._serverStatesById.delete(this.id);
this._serverStatesById.delete(state.id);
}

@@ -566,0 +578,0 @@ }

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc