Socket
Socket
Sign inDemoInstall

@soundworks/core

Package Overview
Dependencies
21
Maintainers
1
Versions
60
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 4.0.0-alpha.5 to 4.0.0-alpha.6

.github/workflows/gh-pages.yml

9

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

@@ -46,4 +46,3 @@ "authors": [

"scripts": {
"doc": "npm run jsdoc",
"jsdoc": "rm -Rf docs && jsdoc -c .jsdoc.json --verbose && cp -R assets docs/",
"doc": "rm -Rf docs && jsdoc -c .jsdoc.json --verbose && cp -R assets docs/",
"lint": "npx eslint src",

@@ -53,3 +52,3 @@ "test": "mocha",

"types": "rm -Rf types && tsc",
"version": "npm run toc && npm run doc && git add docs"
"version": "npm run toc"
},

@@ -62,2 +61,3 @@ "dependencies": {

"compression": "^1.7.1",
"cross-fetch": "^3.1.5",
"express": "^4.18.1",

@@ -72,3 +72,2 @@ "fast-deep-equal": "^3.1.3",

"pem": "^1.14.6",
"serve-static": "^1.15.0",
"template-literal": "^1.0.4",

@@ -75,0 +74,0 @@ "uuid": "^9.0.0",

@@ -201,2 +201,8 @@ import { isBrowser, isPlainObject } from '@ircam/sc-utils';

/**
* Token of the client if connected through HTTP authentication.
* @private
*/
this.token = null;
/** @private */

@@ -244,5 +250,6 @@ this._onStatusChangeCallbacks = new Set();

// wait for handshake response before starting stateManager and pluginManager
this.socket.addListener(CLIENT_HANDSHAKE_RESPONSE, async ({ id, uuid }) => {
this.socket.addListener(CLIENT_HANDSHAKE_RESPONSE, async ({ id, uuid, token }) => {
this.id = id;
this.uuid = uuid;
this.token = token;

@@ -249,0 +256,0 @@ resolve();

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

@@ -113,4 +114,8 @@

const queryParams = `role=${role}&key=${key}`;
let queryParams = `role=${role}&key=${key}`;
if (config.token) {
queryParams += `&token=${config.token}`;
}
// ----------------------------------------------------------

@@ -150,8 +155,3 @@ // init string socket

ws.addEventListener('error', e => {
if (e.type === 'error' && e.error.code === 'ECONNREFUSED') {
if (!connectionRefusedLogged) {
logger.log('[soundworks.Socket] Connection refused, waiting for the server to start');
connectionRefusedLogged = true;
}
if (e.type === 'error') {
if (ws.terminate) {

@@ -163,3 +163,11 @@ ws.terminate();

setTimeout(trySocket, 1000);
// for node clients, retry connection
if (e.error && e.error.code === 'ECONNREFUSED') {
if (!connectionRefusedLogged) {
logger.log('[soundworks.Socket] Connection refused, waiting for the server to start');
connectionRefusedLogged = true;
}
setTimeout(trySocket, 1000);
}
}

@@ -166,0 +174,0 @@ });

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

});
}

@@ -154,3 +153,3 @@ }

this.detach = () => {
throw new Error(`[stateManager] State "${this.schemaName} (${this.id})" already detached, cannot detach twice`);
throw new Error(`[SharedState] State "${this.schemaName} (${this.id})" already detached, cannot detach twice`);
};

@@ -275,6 +274,5 @@ }

for (let name in updates) {
// throw early (client-side and not only server-side) if parameter is undefined
if (!this._parameters.has(name)) {
throw new ReferenceError(`[SharedState] State "${this.schemaName}": cannot set value of undefined parameter "${name}"`);
}
// try to coerce value early, so that eventual errors are triggered early
// on the node requesting the update
const _ = this._parameters.coerceValue(name, updates[name]);

@@ -318,3 +316,4 @@ // @note: general idea...

/**
* Get the value of a paramter of the state.
* Get the value of a parameter of the state. If the parameter is of `any` type,
* a deep copy is returned.
*

@@ -326,3 +325,3 @@ * @param {string} name - Name of the param.

* @example
* const value = state.get('name');
* const value = state.get('paramName');
*/

@@ -334,4 +333,23 @@ get(name) {

/**
* Get all the key / value pairs of the state.
* Similar to `get` but returns a reference to the underlying value in case of
* `any` type. May be usefull if the underlying value is big (e.g. sensors
* recordings, etc.) and deep cloning expensive. Be aware that if changes are
* made on the returned object, the state of your application will become
* inconsistent.
*
* @param {string} name - Name of the param.
* @throws Throws if `name` does not correspond to an existing field
* of the state.
* @return {mixed}
* @example
* const value = state.getUnsafe('paramName');
*/
getUnsafe(name) {
return this._parameters.getUnsafe(name);
}
/**
* Get all the key / value pairs of the state. If a parameter is of `any`
* type, a deep copy is made.
*
* @return {object}

@@ -346,2 +364,19 @@ * @example

/**
* Get all the key / value pairs of the state. If a parameter is of `any`
* type, a deep copy is made.
* Similar to `getValues` but returns a reference to the underlying value in
* case of `any` type. May be usefull if the underlying value is big (e.g.
* sensors recordings, etc.) and deep cloning expensive. Be aware that if
* changes are made on the returned object, the state of your application will
* become inconsistent.
*
* @return {object}
* @example
* const values = state.getValues();
*/
getValuesUnsafe() {
return this._parameters.getValuesUnsafe();
}
/**
* Get the schema from which the state has been created.

@@ -433,3 +468,3 @@ *

*
* @param {client.SharedState~onUpdateCallback|client.SharedState~onUpdateCallback} callback
* @param {client.SharedState~onUpdateCallback|server.SharedState~onUpdateCallback} callback
* Callback to execute when an update is applied on the state.

@@ -469,4 +504,4 @@ * @param {Boolean} [executeListener=false] - Execute the callback immediately

*
* @param {Function} callback - callback to execute when detaching from the state.
* wether the client as called `detach`, or the state has been deleted by its
* @param {Function} callback - Callback to execute when detaching from the state.
* Whether the client as called `detach`, or the state has been deleted by its
* creator.

@@ -483,3 +518,3 @@ */

*
* @param {Function} callback - callback to execute when the state is deleted.
* @param {Function} callback - Callback to execute when the state is deleted.
*/

@@ -486,0 +521,0 @@ onDelete(callback) {

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

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

@@ -34,3 +36,3 @@ CREATE_REQUEST,

this._statesById = new Map();
this._observeListeners = new Set();
this._observeListeners = new Map(); // Map <callback, filterSchemaName>
this._cachedSchemas = new Map();

@@ -92,7 +94,11 @@ this._observeRequestCallbacks = new Map();

// we don't call another callback that may have been registered earlier.
const callback = this._observeRequestCallbacks.get(reqId);
const [callback, filterSchemaName] = this._observeRequestCallbacks.get(reqId);
this._observeRequestCallbacks.delete(reqId);
const promises = list.map(([schemaName, stateId, nodeId]) => {
return callback(schemaName, stateId, nodeId);
if (filterSchemaName === '*' || filterSchemaName === schemaName) {
return callback(schemaName, stateId, nodeId);
} else {
return Promise.resolve();
}
});

@@ -104,3 +110,3 @@

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

@@ -110,8 +116,11 @@ this.client.transport.emit(UNOBSERVE_NOTIFICATION);

};
resolveRequest(reqId, unsubscribe);
});
this.client.transport.addListener(OBSERVE_NOTIFICATION, (...list) => {
list.forEach(([schemaName, stateId, nodeId]) => {
this._observeListeners.forEach(callback => callback(schemaName, stateId, nodeId));
this.client.transport.addListener(OBSERVE_NOTIFICATION, (schemaName, stateId, nodeId) => {
this._observeListeners.forEach((filterSchemaName, callback) => {
if (filterSchemaName === '*' || filterSchemaName === schemaName) {
callback(schemaName, stateId, nodeId);
}
});

@@ -177,8 +186,12 @@ });

*
* @todo Optionnal schema name
* Note that the states that are created by the same node are not propagated through
* the observe callback.
*
* @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.
* @returns {Promise<Function>} - Return a Promise that resolves when the callback
* as been executed for the first time. The promise value is a function which
* allows to stop observing the network.
* @returns {Promise<Function>} - Returns a Promise that resolves when the given
* callback as been executed on each existing states. The promise value is a
* function which allows to stop observing the states on the network.
* @example

@@ -192,12 +205,53 @@ * client.stateManager.observe(async (schemaName, stateId, nodeId) => {

*/
async observe(callback) {
this._observeListeners.add(callback);
// store function
// note: all filtering is done only on client-side as it is really more simple to
// handle this way and the network overhead is very low for observe notifications:
// i.e. schemaName, stateId, nodeId
async observe(...args) {
let filterSchemaName;
let callback;
if (args.length === 1) {
filterSchemaName = '*';
callback = args[0];
if (!isFunction(callback)) {
throw new Error(`[stateManager] Invalid arguments, valid signatures are "stateManager.observe(callback)" or "stateManager.observe(schemaName, callback)"`);
}
} else if (args.length === 2) {
filterSchemaName = args[0];
callback = args[1];
if (!isString(filterSchemaName) || !isFunction(callback)) {
throw new Error(`[stateManager] Invalid arguments, valid signatures are "stateManager.observe(callback)" or "stateManager.observe(schemaName, callback)"`);
}
} else {
throw new Error(`[stateManager] Invalid arguments, valid signatures are "stateManager.observe(callback)" or "stateManager.observe(schemaName, callback)"`);
}
// resend request to get updated list of states
return new Promise((resolve, reject) => {
const reqId = storeRequestPromise(resolve, reject);
// store the callback to be executed on the response
this._observeRequestCallbacks.set(reqId, callback);
// 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);
this.client.transport.emit(OBSERVE_REQUEST, reqId);
});
}
/**
* Returns a collection of all the states created from the schema name. Except
* the ones created by the current node.
*
* @param {string} schemaName - Name of the schema.
* @returns {server.SharedStateCollection|client.SharedStateCollection}
*/
async getCollection(schemaName) {
const collection = new SharedStateCollection(this, schemaName);
await collection._init();
return collection;
}
}

@@ -204,0 +258,0 @@

@@ -20,3 +20,3 @@ import cloneDeep from 'lodash.clonedeep';

if (typeof value !== 'boolean') {
throw new TypeError(`[SharedState] Invalid value for boolean param "${name}": ${value}`);
throw new TypeError(`[SharedState] Invalid value "${value}" for boolean parameter "${name}"`);
}

@@ -34,3 +34,3 @@

if (typeof value !== 'string') {
throw new TypeError(`[SharedState] Invalid value for string param "${name}": ${value}`);
throw new TypeError(`[SharedState] Invalid value "${value}" for string parameter "${name}"`);
}

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

if (!(typeof value === 'number' && Math.floor(value) === value)) {
throw new TypeError(`[SharedState] Invalid value for integer param "${name}": ${value}`);
throw new TypeError(`[SharedState] Invalid value "${value}" for integer parameter "${name}"`);
}

@@ -102,3 +102,3 @@

if (typeof value !== 'number' || value !== value) { // reject NaN
throw new TypeError(`[SharedState] Invalid value for float param "${name}": ${value}`);
throw new TypeError(`[SharedState] Invalid value "${value}" for float parameter "${name}"`);
}

@@ -116,3 +116,3 @@

if (def.list.indexOf(value) === -1) {
throw new TypeError(`[SharedState] Invalid value for enum param "${name}": ${value}`);
throw new TypeError(`[SharedState] Invalid value "${value}" for enum parameter "${name}"`);
}

@@ -249,3 +249,4 @@

/**
* Return values of all parameters as a flat object.
* Return values of all parameters as a flat object. If a parameter is of `any`
* type, a deep copy is made.
*

@@ -265,2 +266,21 @@ * @return {object}

/**
* Return values of all parameters as a flat object. Similar to `getValues` but
* returns a reference to the underlying value in case of `any` type. May be
* usefull if the underlying value is big (e.g. sensors recordings, etc.) and
* deep cloning expensive. Be aware that if changes are made on the returned
* object, the state of your application will become inconsistent.
*
* @return {object}
*/
getValuesUnsafe() {
let values = {};
for (let name in this._values) {
values[name] = this.getUnsafe(name);
}
return values;
}
/**
* Return the value of the given parameter. If the parameter is of `any` type,

@@ -287,13 +307,27 @@ * a deep copy is returned.

/**
* Set the value of a parameter. If the value of the parameter is updated
* (aka if previous value is different from new value) all registered
* callbacks are registered.
* Similar to `get` but returns a reference to the underlying value in case of
* `any` type. May be usefull if the underlying value is big (e.g. sensors
* recordings, etc.) and deep cloning expensive. Be aware that if changes are
* made on the returned object, the state of your application will become
* inconsistent.
*
* @param {string} name - Name of the parameter.
* @return {Mixed} - Value of the parameter.
*/
getUnsafe(name) {
if (!this.has(name)) {
throw new ReferenceError(`[SharedState] Cannot get value of undefined parameter "${name}"`);
}
return this._values[name];
}
/**
* Check that the value is valid according to the schema and return it coerced
* to the schema definition
*
* @param {String} name - Name of the parameter.
* @param {Mixed} value - Value of the parameter.
* @param {boolean} [forcePropagation=false] - if true, propagate value even
* if the value has not changed.
* @return {Array} - [new value, updated flag].
*/
set(name, value) {
coerceValue(name, value) {
if (!this.has(name)) {

@@ -304,3 +338,2 @@ throw new ReferenceError(`[SharedState] Cannot set value of undefined parameter "${name}"`);

const def = this._schema[name];
const { coerceFunction } = types[def.type];

@@ -312,5 +345,20 @@ if (value === null && def.nullable === false) {

} else {
const { coerceFunction } = types[def.type];
value = coerceFunction(name, def, value);
}
return value;
}
/**
* Set the value of a parameter. If the value of the parameter is updated
* (aka if previous value is different from new value) all registered
* callbacks are registered.
*
* @param {string} name - Name of the parameter.
* @param {Mixed} value - Value of the parameter.
* @return {Array} - [new value, updated flag].
*/
set(name, value) {
value = this.coerceValue(name, value);
const currentValue = this._values[name];

@@ -320,5 +368,4 @@ const updated = !equal(currentValue, value);

// we store a deep copy of the object as we don't want the client to be able
// to modify our underlying data, which leads unexpected behavior where the
// to modify our underlying data, which leads to unexpected behavior where the
// deep equal check to returns true, and therefore the update is not triggered.
//
// @see tests/common.state-manager.spec.js

@@ -325,0 +372,0 @@ // 'should copy stored value for "any" type to have a predictable behavior'

@@ -46,2 +46,8 @@ import { idGenerator } from '@ircam/sc-utils';

this.socket = socket;
/**
* Is set in server._onSocketConnection
* @private
*/
this.token = null;
}

@@ -48,0 +54,0 @@ }

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

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

@@ -20,2 +21,3 @@ import compression from 'compression';

import auditSchema from './audit-schema.js';
import { encryptData, decryptData } from './crypto.js';
import Client from './Client.js';

@@ -34,2 +36,3 @@ import ContextManager from './ContextManager.js';

let _dbNamespaces = new Set();

@@ -79,2 +82,4 @@

const TOKEN_VALID_DURATION = 20; // sec
// set terminal title

@@ -163,4 +168,4 @@ /** @private */

/**
* Given config object merged with the following defaults:
* ```
* @description Given config object merged with the following defaults:
* @example
* {

@@ -186,3 +191,2 @@ * env: {

* }
* ```
*/

@@ -245,4 +249,6 @@ this.config = merge({}, DEFAULT_CONFIG, config);

*/
this.router = express.Router();
// compression (must be set before serve-static)
// @note: we use express() instead of express.Router() because all 404 and
// error stuff is handled by default
this.router = express();
// compression (must be set before express.static())
this.router.use(compression());

@@ -323,2 +329,6 @@

this._auditState = null;
/** @private */
this._pendingConnectionTokens = new Set();
/** @private */
this._trustedClients = new Set();

@@ -368,8 +378,21 @@ // register audit state schema

if (this.config.env.auth) {
const ids = idGenerator();
const soundworksAuth = (req, res, next) => {
let role = null;
const isProtected = this.config.env.auth.clients
.map(type => req.path.endsWith(`/${type}`))
.reduce((acc, value) => acc || value, false);
for (let [_role, config] of Object.entries(this.config.app.clients)) {
if (req.path === config.route) {
role = _role;
}
}
// route that are not client entry points just pass through the middleware
if (role === null) {
next();
return;
}
const isProtected = this.isProtected(role);
if (isProtected) {

@@ -385,6 +408,22 @@ // authentication middleware

// -> access granted...
// generate token for web socket to check connections
const id = ids.next().value;
const ip = req.ip;
const time = getTime();
const token = { id, ip, time };
const encryptedToken = encryptData(token);
this._pendingConnectionTokens.add(encryptedToken);
setTimeout(() => {
this._pendingConnectionTokens.delete(encryptedToken);
}, TOKEN_VALID_DURATION * 1000);
// pass to the response object to be send to the client
res.swToken = encryptedToken;
return next();
}
// -> access denied...
// show login / password modal
res.writeHead(401, {

@@ -394,5 +433,6 @@ 'WWW-Authenticate':'Basic',

});
res.end('Authentication required.');
} else {
// route not protected
// route is not protected
return next();

@@ -607,3 +647,3 @@ }

this.config.env.websockets,
(role, socket) => this._onSocketConnection(role, socket),
(...args) => this._onSocketConnection(...args),
);

@@ -727,2 +767,4 @@

this.config.app.clients[role].route = route;
// define template filename: `${role}.html` or `default.html`

@@ -761,2 +803,8 @@ const {

// if the client has gone through the connection middleware (add succedeed),
// add the token to the data object
if (res.swToken) {
data.token = res.swToken;
}
// CORS / COOP / COEP headers for `crossOriginIsolated pages,

@@ -776,2 +824,3 @@ // enables `sharedArrayBuffers` and high precision timers

};
// http request

@@ -788,8 +837,24 @@ router.get(route, soundworksClientHandler);

*/
_onSocketConnection(role, socket) {
_onSocketConnection(role, socket, connectionToken) {
const client = new Client(role, socket);
const roles = Object.keys(this.config.app.clients);
// this has been validated
if (this.isProtected(role) && this.isValidConnectionToken(connectionToken)) {
const { ip } = decryptData(connectionToken);
const newData = {
ip: ip,
id: client.id,
};
const newToken = encryptData(newData);
client.token = newToken;
this._pendingConnectionTokens.delete(connectionToken);
this._trustedClients.add(client);
}
socket.addListener('close', async () => {
// do nothin if client type was invalid
// do nothing if client role is invalid
if (roles.includes(role)) {

@@ -801,2 +866,6 @@ // decrement audit state counter

// delete token
if (this._trustedClients.has(client)) {
this._trustedClients.delete(client);
}
// clean context manager, await before cleaning state manager

@@ -854,4 +923,4 @@ await this.contextManager.removeClient(client);

const { id, uuid } = client;
socket.send(CLIENT_HANDSHAKE_RESPONSE, { id, uuid });
const { id, uuid, token } = client;
socket.send(CLIENT_HANDSHAKE_RESPONSE, { id, uuid, token });
});

@@ -933,2 +1002,23 @@ }

useDefaultApplicationTemplate() {
const buildDirectory = path.join('.build', 'public');
const useMinifiedFile = {};
const roles = Object.keys(this.config.app.clients);
roles.forEach(role => {
if (this.config.env.type === 'production') {
// check if minified file exists
const minifiedFilePath = path.join(buildDirectory, `${role}.min.js`);
if (fs.existsSync(minifiedFilePath)) {
useMinifiedFile[role] = true;
} else {
console.log(chalk.yellow(` > Minified file not found for client "${role}", falling back to normal build file (use \`npm run build:production && npm start\` to use minified files)`));
useMinifiedFile[role] = false;
}
} else {
useMinifiedFile[role] = false;
}
});
this._applicationTemplateOptions = {

@@ -948,2 +1038,3 @@ templateEngine: { compile },

subpath: config.env.subpath,
useMinifiedFile: useMinifiedFile[role],
},

@@ -955,3 +1046,3 @@ };

this.router.use(express.static('public'));
this.router.use('/build', express.static(path.join('.build', 'public')));
this.router.use('/build', express.static(buildDirectory));
}

@@ -992,4 +1083,79 @@

}
/** @private */
isProtected(role) {
if (this.config.env.auth && Array.isArray(this.config.env.auth.clients)) {
return this.config.env.auth.clients.includes(role);
}
return false;
}
/** @private */
isValidConnectionToken(token) {
// token should be in pending token list
if (!this._pendingConnectionTokens.has(token)) {
return false;
}
// check the token is not too old
const data = decryptData(token);
const now = getTime();
// token is valid only for 30 seconds (this is arbitrary)
if (now > data.time + TOKEN_VALID_DURATION) {
// delete the token, is too old
this._pendingConnectionTokens.delete(token);
return false;
} else {
return true;
}
}
/**
* Check if the given client is trusted, i.e. config.env.type == 'production'
* and the client is protected behind a password.
*
* @param {server.Client} client - Client to be tested
* @returns {Boolean}
*/
isTrustedClient(client) {
if (this.config.env.type !== 'production') {
return true;
} else {
return this._trustedClients.has(client);
}
}
/**
* Check if the token from a client is trusted, i.e. config.env.type == 'production'
* and the client is protected behind a password.
*
* @param {Number} clientId - Id of the client
* @param {Number} clientIp - Ip of the client
* @param {String} token - Token to be tested
* @returns {Boolean}
*/
// for stateless interactions, e.g. POST files
isTrustedToken(clientId, clientIp, token) {
if (this.config.env.type !== 'production') {
return true;
} else {
for (let client of this._trustedClients) {
if (client.id === clientId && client.token === token) {
// check that given token is consistent with client ip and id
const { id, ip } = decryptData(client.token);
if (clientId === id && clientIp === ip) {
return true;
}
}
}
return false;
}
}
}
export default Server;

@@ -5,5 +5,5 @@ import { Worker } from 'node:worker_threads';

import querystring from 'querystring';
import { WebSocketServer } from 'ws';
import WebSocket from 'ws';
import querystring from 'querystring';

@@ -73,3 +73,3 @@ import Socket from './Socket.js';

this._wss = new WebSocketServer({
server: server.httpServer,
noServer: true,
path: `/${config.path}`, // @note - update according to existing config files (aka cosima-apps)

@@ -80,3 +80,3 @@ });

const queryString = querystring.decode(req.url.split('?')[1]);
const { role, key } = queryString;
const { role, key, token } = queryString;
const binary = !!(parseInt(queryString.binary));

@@ -102,5 +102,26 @@

onConnectionCallback(role, socket);
onConnectionCallback(role, socket, token);
}
});
// check if client can connect
server.httpServer.on('upgrade', async (req, socket, head) => {
const queryString = querystring.decode(req.url.split('?')[1]);
const { role, token } = queryString;
if (server.isProtected(role)) {
// we don't have any ip in the upgrade request, so we just check the
// connection token is pending
const allowed = server.isValidConnectionToken(token);
if (!allowed) {
socket.destroy('not allowed');
}
}
this._wss.handleUpgrade(req, socket, head, (ws) => {
this._wss.emit('connection', ws, req);
});
});
}

@@ -107,0 +128,0 @@

@@ -362,3 +362,5 @@ import EventEmitter from 'node:events';

this._observers.forEach(observer => {
observer.transport.emit(OBSERVE_NOTIFICATION, [schemaName, stateId, nodeId]);
if (observer.id !== nodeId) {
observer.transport.emit(OBSERVE_NOTIFICATION, schemaName, stateId, nodeId);
}
});

@@ -432,3 +434,3 @@ } catch (err) {

// (e.g. do not propagate infos about audit state)
if (!PRIVATE_STATES.includes(schemaName)) {
if (!PRIVATE_STATES.includes(schemaName) && _creatorId !== client.id) {
statesInfos.push([schemaName, id, _creatorId]);

@@ -435,0 +437,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