@soundworks/core
Advanced tools
Comparing version 3.1.0-beta.1 to 3.1.0-beta.2
@@ -121,3 +121,3 @@ "use strict"; | ||
}, config.env.websockets); // init communications | ||
}, config.env.websockets); // init socket communications (string and binary) | ||
@@ -124,0 +124,0 @@ await this.socket.init(this.type, this.config); |
@@ -145,3 +145,3 @@ "use strict"; | ||
const ctor = factory(_AbstractPlugin.default); | ||
throw new Error(`[soundworks:core] plugin "${name}" of type "${ctor.name}" already registered`); | ||
throw new Error(`[soundworks:core] Plugin "${name}" of type "${ctor.name}" already registered`); | ||
} | ||
@@ -169,3 +169,3 @@ | ||
if (!this._registeredPlugins[name]) { | ||
throw new Error(`Cannot get or require plugin "${name}", plugin is not registered | ||
throw new Error(`[soundworks:core] Cannot get or require plugin "${name}", plugin is not registered | ||
> registered plugins are: | ||
@@ -178,3 +178,3 @@ ${Object.keys(this._registeredPlugins).map(n => `> - ${n}\n`).join('')} | ||
if (_experienceRequired && this.signals.start.value === true) { | ||
throw new Error(`Plugin "${name}" required after pluginManager start`); | ||
throw new Error(`[soundworks:core] Plugin "${name}" required after pluginManager start`); | ||
} | ||
@@ -181,0 +181,0 @@ |
@@ -56,2 +56,9 @@ "use strict"; | ||
this.ws = null; | ||
/** | ||
* WebSocket instance (binary protocol - binaryType = 'arraybuffer'). | ||
*/ | ||
/** @private */ | ||
this.binaryWs = null; | ||
this._stringListeners = new Map(); | ||
@@ -71,7 +78,7 @@ this._binaryListeners = new Map(); | ||
init(clientType, config) { | ||
async init(clientType, config) { | ||
// unique key that allows to associate the two sockets to the same client. | ||
// note: the key is only used to pair to two sockets, so its usage is very | ||
// limited in time therefore a random number should hopefully be sufficient. | ||
const key = (Math.random() + '').replace(/^0./, ''); // open web sockets | ||
const key = (Math.random() + '').replace(/^0\./, ''); // open web sockets | ||
@@ -111,43 +118,67 @@ let { | ||
const stringSocketUrl = `${url}?binary=0&${queryParams}`; | ||
this.ws = new _isomorphicWs.default(stringSocketUrl); | ||
log(`string socket initialized - url: ${stringSocketUrl}`); | ||
const stringSocketPromise = new Promise((resolve, reject) => { | ||
this.ws.addEventListener('open', resolve); | ||
}); // parse incoming messages for pubsub | ||
await new Promise((resolve, reject) => { | ||
const trySocket = async () => { | ||
log(`[string socket] trying connection - url: ${stringSocketUrl}`); | ||
const ws = new _isomorphicWs.default(stringSocketUrl); | ||
ws.addEventListener('open', connectEvent => { | ||
// parse incoming messages for pubsub | ||
this.ws = ws; | ||
this.ws.addEventListener('message', e => { | ||
const [channel, args] = (0, _socketsEncoderDecoder.unpackStringMessage)(e.data); | ||
this.ws.addEventListener('message', e => { | ||
const [channel, args] = (0, _socketsEncoderDecoder.unpackStringMessage)(e.data); | ||
this._emit(false, channel, ...args); | ||
}); // broadcast all `WebSocket` native events | ||
this._emit(false, channel, ...args); | ||
}); // broadcast all `WebSocket` native events | ||
['close', 'error', 'upgrade', 'message'].forEach(eventName => { | ||
this.ws.addEventListener(eventName, e => { | ||
this._emit(false, eventName, e); | ||
}); | ||
}); // forward open event | ||
['open', 'close', 'error', 'upgrade', 'message'].forEach(eventName => { | ||
this.ws.addEventListener(eventName, e => { | ||
this._emit(false, eventName, e.data); | ||
}); | ||
}); // ---------------------------------------------------------- | ||
// init binary socket | ||
// ---------------------------------------------------------- | ||
this._emit(false, 'open', connectEvent); // continue with raw socket | ||
resolve(); | ||
}); // cf. https://github.com/collective-soundworks/soundworks/issues/17 | ||
ws.addEventListener('error', e => { | ||
if (e.type === 'error' && e.error.code === 'ECONNREFUSED') { | ||
console.log('[socket] connection refused - (retry in 1s)'); | ||
setTimeout(trySocket, 1000); | ||
} | ||
}); | ||
}; | ||
trySocket(); | ||
}); // // ---------------------------------------------------------- | ||
// // init binary socket | ||
// // ---------------------------------------------------------- | ||
const binarySocketUrl = `${url}?binary=1&${queryParams}`; | ||
this.binaryWs = new _isomorphicWs.default(binarySocketUrl); | ||
this.binaryWs.binaryType = 'arraybuffer'; | ||
log(`binary socket initialized - url: ${binarySocketUrl}`); | ||
const binarySocketPromise = new Promise((resolve, reject) => { | ||
this.binaryWs.addEventListener('open', resolve); | ||
}); // parse incoming messages for pubsub | ||
await new Promise((resolve, reject) => { | ||
log(`[binary socket] trying connection - url: ${binarySocketUrl}`); | ||
const ws = new _isomorphicWs.default(binarySocketUrl); | ||
ws.binaryType = 'arraybuffer'; | ||
ws.addEventListener('open', connectEvent => { | ||
// parse incoming messages for pubsub | ||
this.binaryWs = ws; | ||
this.binaryWs.addEventListener('message', e => { | ||
const [channel, data] = (0, _socketsEncoderDecoder.unpackBinaryMessage)(e.data); | ||
this.binaryWs.addEventListener('message', e => { | ||
const [channel, data] = (0, _socketsEncoderDecoder.unpackBinaryMessage)(e.data); | ||
this._emit(true, channel, data); | ||
}); // broadcast all `WebSocket` native events | ||
this._emit(true, channel, data); | ||
}); // broadcast all `WebSocket` native events | ||
['close', 'error', 'upgrade', 'message'].forEach(eventName => { | ||
this.binaryWs.addEventListener(eventName, e => { | ||
this._emit(true, eventName, e); | ||
}); | ||
}); // forward open event | ||
['open', 'close', 'error', 'upgrade', 'message'].forEach(eventName => { | ||
this.binaryWs.addEventListener(eventName, e => { | ||
this._emit(true, eventName, e.data); | ||
this._emit(true, 'open', connectEvent); | ||
resolve(); | ||
}); | ||
}); // wait for both socket to be opened | ||
}); // everyone is connected | ||
return Promise.all([stringSocketPromise, binarySocketPromise]); | ||
return Promise.resolve(); | ||
} | ||
@@ -154,0 +185,0 @@ /** @private */ |
@@ -6,5 +6,5 @@ "use strict"; | ||
}); | ||
exports.default = exports.types = exports.sharedOptions = void 0; | ||
exports.types = exports.sharedOptions = exports.default = void 0; | ||
var _lodash = _interopRequireDefault(require("lodash.cloneDeep")); | ||
var _lodash = _interopRequireDefault(require("lodash.clonedeep")); | ||
@@ -11,0 +11,0 @@ var _fastDeepEqual = _interopRequireDefault(require("fast-deep-equal")); |
@@ -6,7 +6,7 @@ "use strict"; | ||
}); | ||
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; | ||
exports.idGenerator = idGenerator; | ||
exports.rejectRequest = rejectRequest; | ||
exports.resolveRequest = resolveRequest; | ||
exports.storeRequestPromise = storeRequestPromise; | ||
exports.resolveRequest = resolveRequest; | ||
exports.rejectRequest = rejectRequest; | ||
exports.UPDATE_NOTIFICATION = exports.UPDATE_ABORT = exports.UPDATE_RESPONSE = exports.UPDATE_REQUEST = exports.OBSERVE_NOTIFICATION = exports.OBSERVE_RESPONSE = exports.OBSERVE_REQUEST = exports.DETACH_ERROR = exports.DETACH_RESPONSE = exports.DETACH_REQUEST = exports.ATTACH_ERROR = exports.ATTACH_RESPONSE = exports.ATTACH_REQUEST = exports.DELETE_NOTIFICATION = exports.DELETE_ERROR = exports.DELETE_RESPONSE = exports.DELETE_REQUEST = exports.CREATE_ERROR = exports.CREATE_RESPONSE = exports.CREATE_REQUEST = exports.SERVER_ID = void 0; | ||
// id of the server when owner of a state | ||
@@ -55,2 +55,4 @@ const SERVER_ID = -1; | ||
exports.UPDATE_NOTIFICATION = UPDATE_NOTIFICATION; | ||
const DELETE_SCHEMA = 's:d:s'; | ||
exports.DELETE_SCHEMA = DELETE_SCHEMA; | ||
@@ -57,0 +59,0 @@ function* idGenerator() { |
@@ -168,3 +168,3 @@ "use strict"; | ||
// to avoid retrigger listeners. | ||
// If the value has overriden by the server, `changed` will true | ||
// If the value has been overriden by the server, `changed` will true | ||
// anyway so it should behave correctly. | ||
@@ -322,3 +322,3 @@ if (!changed || event) { | ||
* } | ||
* } | ||
* }); | ||
* | ||
@@ -334,2 +334,4 @@ * @see {common.SharedState#set} | ||
* when an update is applied on the state. | ||
* @param {Boolean} [executeListener=false] - execute the given listener with | ||
* current state values. (`oldValues` will be set to `{}`, and `context` to `null`) | ||
* | ||
@@ -343,9 +345,16 @@ * @example | ||
* } | ||
* } | ||
* }); | ||
*/ | ||
subscribe(listener) { | ||
subscribe(listener, executeListener = false) { | ||
this._subscriptions.add(listener); | ||
if (executeListener === true) { | ||
const currentValues = this.getValues(); | ||
const oldValues = {}; | ||
const context = null; | ||
listener(currentValues, oldValues, context); | ||
} | ||
return () => { | ||
@@ -352,0 +361,0 @@ this._subscriptions.delete(listener); |
@@ -102,2 +102,8 @@ "use strict"; | ||
}); | ||
}); // --------------------------------------------- | ||
// Clear cache when schema is deleted | ||
// --------------------------------------------- | ||
this.client.transport.addListener(_sharedStateUtils.DELETE_SCHEMA, schemaName => { | ||
this._cachedSchemas.delete(schemaName); | ||
}); | ||
@@ -104,0 +110,0 @@ } |
@@ -276,2 +276,3 @@ "use strict"; | ||
deleteSchema(schemaName) { | ||
// @note: deleting schema | ||
for (let [id, state] of this._serverStatesById.entries()) { | ||
@@ -287,2 +288,7 @@ if (state.schemaName === schemaName) { | ||
} | ||
} // clear schema cache of all connected clients | ||
for (let client of this._clientByNodeId.values()) { | ||
client.transport.emit(`${_sharedStateUtils.DELETE_SCHEMA}`, schemaName); | ||
} | ||
@@ -289,0 +295,0 @@ |
@@ -10,2 +10,4 @@ "use strict"; | ||
var _lodash = _interopRequireDefault(require("lodash.clonedeep")); | ||
var _sharedStateUtils = require("./shared-state-utils"); | ||
@@ -45,79 +47,104 @@ | ||
const values = this._parameters.getValues(); // @note: we may need a proper update queue to avoid race conditions | ||
const values = this._parameters.getValues(); | ||
let hookAborted = false; // cf. https://github.com/collective-soundworks/soundworks/issues/45 | ||
for (let hook of hooks.values()) { | ||
updates = await hook(updates, values, context); | ||
const result = await hook(updates, values, context); | ||
if (result === null) { | ||
// explicit abort if hook returns null | ||
hookAborted = true; | ||
break; | ||
} else if (result === undefined) { | ||
// implicit continue if hook returns undefined | ||
continue; | ||
} else { | ||
// the hook returned an updates object | ||
updates = result; | ||
} | ||
} | ||
const filteredUpdates = {}; | ||
let hasUpdates = false; | ||
if (hookAborted === false) { | ||
const filteredUpdates = {}; | ||
let hasUpdates = false; | ||
for (let name in updates) { | ||
// from v3.1.0 - the `filteredUpdates` check is made using 'fast-deep-equal' | ||
// cf. https://github.com/epoberezkin/fast-deep-equal | ||
// therefore unchanged objects are not considered changed | ||
// nor propagated anymore. | ||
// until v3.0.4 - we checked the `schema[name].type === 'any'`, to always consider | ||
// objects as dirty, because if the state is attached locally, we | ||
// compare the Object instances instead of their values. | ||
// @note - this should be made more robust but how? | ||
const [newValue, changed] = this._parameters.set(name, updates[name]); // if `filterChange` is set to `false` we don't check if the value | ||
// has been changed or not, it is always propagated to client states | ||
for (let name in updates) { | ||
// from v3.1.0 - the `filteredUpdates` check is made using 'fast-deep-equal' | ||
// cf. https://github.com/epoberezkin/fast-deep-equal | ||
// therefore unchanged objects are not considered changed | ||
// nor propagated anymore. | ||
// until v3.0.4 - we checked the `schema[name].type === 'any'`, to always consider | ||
// objects as dirty, because if the state is attached locally, we | ||
// compare the Object instances instead of their values. | ||
// @note - this should be made more robust but how? | ||
const [newValue, changed] = this._parameters.set(name, updates[name]); // if `filterChange` is set to `false` we don't check if the value | ||
// has been changed or not, it is always propagated to client states | ||
const { | ||
filterChange | ||
} = this._parameters.getSchema(name); | ||
const { | ||
filterChange | ||
} = this._parameters.getSchema(name); | ||
if (filterChange && changed || !filterChange) { | ||
filteredUpdates[name] = newValue; | ||
hasUpdates = true; | ||
if (filterChange && changed || !filterChange) { | ||
filteredUpdates[name] = newValue; | ||
hasUpdates = true; | ||
} | ||
} | ||
} | ||
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: | ||
// * 1. the value it requested, | ||
// * 2. the value overriden by the server-side attached state (id: -1) | ||
// this problem could be solved properly with a reducer system: | ||
// if (dirty) { | ||
// -> call (async) reducer | ||
// -> get values from reducer | ||
//. -> dispatch to everybody | ||
// } | ||
for (let [peerRemoteId, peer] of this._attachedClients.entries()) { | ||
// propagate notification to all other attached clients except server | ||
if (remoteId !== peerRemoteId && peer.id !== -1) { | ||
peer.transport.emit(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context); | ||
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: | ||
// * 1. the value it requested, | ||
// * 2. the value overriden by the server-side attached state (id: -1) | ||
// this problem could be solved properly with a reducer system: | ||
// if (dirty) { | ||
// -> call (async) reducer | ||
// -> get values from reducer | ||
//. -> dispatch to everybody | ||
// } | ||
for (let [peerRemoteId, peer] of this._attachedClients.entries()) { | ||
// propagate notification to all other attached clients except server | ||
if (remoteId !== peerRemoteId && peer.id !== -1) { | ||
peer.transport.emit(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context); | ||
} | ||
} | ||
} | ||
if (client.id !== -1) { | ||
client.transport.emit(`${_sharedStateUtils.UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context); | ||
} | ||
if (client.id !== -1) { | ||
client.transport.emit(`${_sharedStateUtils.UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context); | ||
} | ||
for (let [peerRemoteId, peer] of this._attachedClients.entries()) { | ||
// propagate notification to server | ||
if (remoteId !== peerRemoteId && peer.id === -1) { | ||
peer.transport.emit(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context); | ||
for (let [peerRemoteId, peer] of this._attachedClients.entries()) { | ||
// propagate notification to server | ||
if (remoteId !== peerRemoteId && peer.id === -1) { | ||
peer.transport.emit(`${_sharedStateUtils.UPDATE_NOTIFICATION}-${this.id}-${peerRemoteId}`, filteredUpdates, context); | ||
} | ||
} | ||
} | ||
if (client.id === -1) { | ||
client.transport.emit(`${_sharedStateUtils.UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context); | ||
if (client.id === -1) { | ||
client.transport.emit(`${_sharedStateUtils.UPDATE_RESPONSE}-${this.id}-${remoteId}`, reqId, filteredUpdates, context); | ||
} | ||
} else { | ||
// propagate back to the requester that the update has been aborted | ||
// ignore all other attached clients. | ||
client.transport.emit(`${_sharedStateUtils.UPDATE_ABORT}-${this.id}-${remoteId}`, reqId, updates, context); | ||
} | ||
} else { | ||
// propagate back to the requester that the update has been aborted | ||
// ignore all other attached clients. | ||
client.transport.emit(`${_sharedStateUtils.UPDATE_ABORT}-${this.id}-${remoteId}`, reqId, updates, context); | ||
// retrieve values from inner state (also handle immediate approriately) | ||
const oldValues = {}; | ||
for (let name in updates) { | ||
oldValues[name] = this._parameters.get(name); | ||
} // aborted by hook (updates have been overriden to {}) | ||
client.transport.emit(`${_sharedStateUtils.UPDATE_ABORT}-${this.id}-${remoteId}`, reqId, oldValues, context); | ||
} | ||
@@ -124,0 +151,0 @@ }); |
@@ -7,4 +7,4 @@ "use strict"; | ||
exports.packBinaryMessage = packBinaryMessage; | ||
exports.packStringMessage = packStringMessage; | ||
exports.unpackBinaryMessage = unpackBinaryMessage; | ||
exports.packStringMessage = packStringMessage; | ||
exports.unpackStringMessage = unpackStringMessage; | ||
@@ -11,0 +11,0 @@ |
{ | ||
"name": "@soundworks/core", | ||
"version": "3.1.0-beta.1", | ||
"version": "3.1.0-beta.2", | ||
"description": "full-stack javascript framework for distributed audio visual experiences on the web", | ||
@@ -35,3 +35,3 @@ "authors": [ | ||
"dependencies": { | ||
"chalk": "^2.4.2", | ||
"chalk": "^4.1.2", | ||
"columnify": "^1.5.4", | ||
@@ -44,4 +44,4 @@ "compression": "^1.7.1", | ||
"jsdoc-template": "^1.2.0", | ||
"keyv": "^3.1.0", | ||
"keyv-file": "^0.1.13", | ||
"keyv": "^4.0.3", | ||
"keyv-file": "^0.2.0", | ||
"lodash.clonedeep": "^4.5.0", | ||
@@ -52,5 +52,5 @@ "lodash.merge": "^4.6.2", | ||
"serve-static": "^1.13.2", | ||
"uuidv4": "^4.0.0", | ||
"uuid": "^8.3.2", | ||
"window-or-global": "^1.0.1", | ||
"ws": "^7.0.0" | ||
"ws": "^8.2.3" | ||
}, | ||
@@ -61,11 +61,14 @@ "devDependencies": { | ||
"@babel/preset-env": "^7.4.5", | ||
"braintree-jsdoc-template": "^3.3.0", | ||
"chai": "^4.2.0", | ||
"chokidar": "^3.0.1", | ||
"chokidar-cli": "^1.2.2", | ||
"chokidar-cli": "^3.0.0", | ||
"docdash": "^1.2.0", | ||
"jsdoc": "^3.6.2", | ||
"markdown-toc": "^1.2.0", | ||
"mocha": "^8.1.3" | ||
"mocha": "^9.1.3" | ||
}, | ||
"optionalDependencies": { | ||
"bufferutil": "^4.0.5", | ||
"utf-8-validate": "^5.0.7" | ||
} | ||
} |
@@ -8,6 +8,4 @@ "use strict"; | ||
var _uuidv = _interopRequireDefault(require("uuidv4")); | ||
var _uuid = require("uuid"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function* idGenerator() { | ||
@@ -51,3 +49,3 @@ for (let i = 0; true; i++) { | ||
this.uuid = (0, _uuidv.default)(); | ||
this.uuid = (0, _uuid.v4)(); | ||
/** | ||
@@ -54,0 +52,0 @@ * Socket connection with the remote {@link client.Client}. |
@@ -90,3 +90,3 @@ "use strict"; | ||
const ctor = factory(_AbstractPlugin.default); | ||
throw new Error(`[soundworks:core] plugin "${name}" of type "${ctor.name}" already registered`); | ||
throw new Error(`[soundworks:core] Plugin "${name}" of type "${ctor.name}" already registered`); | ||
} | ||
@@ -110,3 +110,3 @@ | ||
if (!this._registeredPlugins[name]) { | ||
throw new Error(`Cannot get or require plugin "${name}", plugin is not registered | ||
throw new Error(`[soundworks:core] Cannot get or require plugin "${name}", plugin is not registered | ||
> registered plugins are: | ||
@@ -119,3 +119,3 @@ ${Object.keys(this._registeredPlugins).map(n => `> - ${n}\n`).join('')} | ||
if (_experience && this.signals.start.value === true) { | ||
throw new Error(`Plugin "${name}" required after pluginManager start`); | ||
throw new Error(`[soundworks:core] Plugin "${name}" required after pluginManager start`); | ||
} | ||
@@ -122,0 +122,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
162724
10
4010
20
+ Addeduuid@^8.3.2
+ Addedansi-styles@4.3.0(transitive)
+ Addedbufferutil@4.0.8(transitive)
+ Addedchalk@4.1.2(transitive)
+ Addedcolor-convert@2.0.1(transitive)
+ Addedcolor-name@1.1.4(transitive)
+ Addedhas-flag@4.0.0(transitive)
+ Addedjson-buffer@3.0.1(transitive)
+ Addedkeyv@4.5.4(transitive)
+ Addedkeyv-file@0.2.0(transitive)
+ Addednode-gyp-build@4.8.2(transitive)
+ Addedsupports-color@7.2.0(transitive)
+ Addedutf-8-validate@5.0.10(transitive)
+ Addeduuid@8.3.2(transitive)
+ Addedws@8.18.0(transitive)
- Removeduuidv4@^4.0.0
- Removedansi-styles@3.2.1(transitive)
- Removedchalk@2.4.2(transitive)
- Removedcolor-convert@1.9.3(transitive)
- Removedcolor-name@1.1.3(transitive)
- Removedhas-flag@3.0.0(transitive)
- Removedjson-buffer@3.0.0(transitive)
- Removedkeyv@3.1.0(transitive)
- Removedkeyv-file@0.1.13(transitive)
- Removedsupports-color@5.5.0(transitive)
- Removeduuid@3.3.2(transitive)
- Removeduuidv4@4.0.0(transitive)
- Removedws@7.5.10(transitive)
Updatedchalk@^4.1.2
Updatedkeyv@^4.0.3
Updatedkeyv-file@^0.2.0
Updatedws@^8.2.3