@soundworks/core
Advanced tools
Comparing version 4.0.0-alpha.1 to 4.0.0-alpha.2
{ | ||
"name": "@soundworks/core", | ||
"version": "4.0.0-alpha.1", | ||
"version": "4.0.0-alpha.2", | ||
"description": "Open-source creative coding framework for distributed applications based on Web technologies", | ||
@@ -46,3 +46,3 @@ "authors": [ | ||
"scripts": { | ||
"doc": "npm run jsdoc && npm run types", | ||
"doc": "npm run jsdoc", | ||
"jsdoc": "rm -Rf docs && jsdoc -c .jsdoc.json --verbose && cp -R assets docs/", | ||
@@ -49,0 +49,0 @@ "lint": "npx eslint src", |
# `soundworks` | ||
[![npm version](https://badge.fury.io/js/@soundworks%2Fcore.svg)](https://badge.fury.io/js/@soundworks%2Fcore) | ||
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) | ||
@@ -21,2 +22,4 @@ ![soundworks-logo](./assets/logo-200x200.png) | ||
![./assets/soundworks-create-min.gif](./assets/soundworks-create-min.gif) | ||
See [https://soundworks.dev/guides/getting-started.html](https://soundworks.dev/guides/getting-started.html) for more informations on the wizard and how to start with `soundworks`. | ||
@@ -42,2 +45,8 @@ | ||
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. | ||
## Share with Us | ||
If you made an application using `soundworks` please let us know here: https://github.com/collective-soundworks/soundworks/discussions/61 | ||
## TypeScript Support | ||
@@ -47,3 +56,3 @@ | ||
However, as we aim to follow the TC39 and W3C specifications as close as possible, we will wait for the https://github.com/tc39/proposal-type-annotations proposal to reach stage 3 to update the source code in a more integrated manner. | ||
However, for maintenance reasons, we aim at following the TC39 and W3C specifications as close as possible. Therefore, we will wait for the https://github.com/tc39/proposal-type-annotations proposal to reach stage 3 to update the source code in a more integrated manner. | ||
@@ -58,8 +67,20 @@ ## Install | ||
## Share with Us | ||
## Credits | ||
If you made an application using `soundworks` please let us know here: https://github.com/collective-soundworks/soundworks/discussions/61 | ||
`soundworks` has been initiated by [Norbert Schnell](https://github.com/NorbertSchnell), [Sébastien Robaszkiewicz](https://github.com/i-Robi), and [Benjamin Matuszewski](https://github.com/b-ma) at the [ISMM](http://ismm.ircam.fr/) team at [Ircam - Centre Pompidou](http://www.ircam.fr/) in the context of the [CoSiMa](http://cosima.ircam.fr/) research project founded by the French National Research Agency (ANR). | ||
## Academic Papers | ||
Development is now led by Benjamin Matuszewski, in the [Sound Music Movement Interaction Team](https://www.stms-lab.fr/team/interaction-son-musique-mouvement/) from the Ircam's STMS-LAB. | ||
### Supporting Research Projects | ||
Initial and futher developments has been supported by the following research projects: | ||
- The [DOTS](http://dots.ircam.fr/) project, funded by the French National Research Agency (ANR) | ||
- The Ircam projects _BeCoMe_ and _SO(a)P_ | ||
- The _Constella(c)tions_ residency, funded by the STARTS program of the European Commission | ||
- The [RAPID-MIX project](http://rapidmix.goldsmithsdigital.com/), funded by the European Union’s Horizon 2020 research and innovation program | ||
- The [CoSiMa](http://cosima.ircam.fr/) project, funded by the French National Research Agency (ANR) | ||
### Academic Papers | ||
- 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)> | ||
@@ -70,13 +91,2 @@ - 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)> | ||
## Credits | ||
`soundworks` has been initiated by [Norbert Schnell](https://github.com/NorbertSchnell), [Sébastien Robaszkiewicz](https://github.com/i-Robi), and [Benjamin Matuszewski](https://github.com/b-ma) at the [ISMM](http://ismm.ircam.fr/) team at [Ircam - Centre Pompidou](http://www.ircam.fr/) in the framework of the [*CoSiMa*](http://cosima.ircam.fr/) research project supported by the [French National Research Agency (ANR)](http://www.agence-nationale-recherche.fr/en/). | ||
Futher developments has been supported in the framework of: | ||
- The [RAPID-MIX project](http://rapidmix.goldsmithsdigital.com/), funded by the European Union’s Horizon 2020 research and innovation program | ||
- The Ircam projects _BeCoMe_ and _SO(a)P_ | ||
- The _Constella(c)tions_ residency of the STARTS program of the European Commission. | ||
Development is pursued, led by Benjamin Matuszewski, in the [Interaction Music Movement Team](https://www.stms-lab.fr/team/interaction-son-musique-mouvement/) from the Ircam's STMS-LAB. | ||
## License | ||
@@ -83,0 +93,0 @@ |
@@ -17,3 +17,36 @@ import isPlainObject from 'is-plain-obj'; | ||
/** | ||
* The `Client` class is the main entry point for the client-side of soundworks | ||
* Configuration object for a client running in a browser runtime. | ||
* | ||
* @typedef BrowserClientConfig | ||
* @memberof client | ||
* @type {object} | ||
* @property {string} role - Role of the client in the application (e.g. 'player', 'controller'). | ||
* @property {object} [app] - Application configration object. | ||
* @property {string} [app.name=''] - Name of the application. | ||
* @property {string} [app.author=''] - Name of the author. | ||
* @property {object} [env] - Environment configration object. | ||
* @property {string} [env.websockets={}] - Configuration options for websockets. | ||
* @property {string} [env.subpath=''] - If running behind a proxy, path to the application. | ||
*/ | ||
/** | ||
* Configuration object for a client running in a node runtime. | ||
* | ||
* @typedef NodeClientConfig | ||
* @memberof client | ||
* @type {object} | ||
* @property {string} role - Role of the client in the application (e.g. 'player', 'controller'). | ||
* @property {object} [app] - Application configration object. | ||
* @property {string} [app.name=''] - Name of the application. | ||
* @property {string} [app.author=''] - Name of the author. | ||
* @property {object} env - Environment configration object. | ||
* @property {boolean} env.serverAddress - Domain name or IP of the server. | ||
* @property {boolean} env.useHttps - Define is the server run in http or in https. | ||
* @property {boolean} env.port - Port on which the server is listening. | ||
* @property {string} [env.websockets={}] - Configuration options for websockets. | ||
* @property {string} [env.subpath=''] - If running behind a proxy, path to the application. | ||
*/ | ||
/** | ||
* The `Client` class is the main entry point for the client-side of a soundworks | ||
* application. | ||
@@ -273,4 +306,5 @@ * | ||
* What it does: | ||
* - optionnaly call {@link client.Client#init} | ||
* - starts all the already created contexts (see {@link client.Context}) | ||
* - implicitly call {@link client.Client#init} if not done manually | ||
* - start all created contexts. For that to happen, you will have to call `client.init` | ||
* manually and instantiate the contexts between `client.init()` and `client.start()` | ||
* | ||
@@ -277,0 +311,0 @@ * @example |
@@ -72,2 +72,4 @@ import Client from './Client.js'; | ||
* The soundworks client instance. | ||
* @type {client.Client} | ||
* @readonly | ||
*/ | ||
@@ -78,2 +80,4 @@ this.client = client; | ||
* Status of the context, i.e. 'idle', 'inited', 'started', 'errored' | ||
* @type {string} | ||
* @readonly | ||
*/ | ||
@@ -118,6 +122,12 @@ this.status = 'idle'; | ||
/** | ||
* Name of the context. Defaults to the class name, override to use a user-defined name. | ||
* Optionnal user-defined name of the context (defaults to the class name). | ||
* | ||
* @returns {string} - Name of the context. | ||
* The context manager will match the client-side and server-side contexts based | ||
* on this name. If the {@link server.ContextManager} don't find a corresponding | ||
* user-defined context with the same name, it will use a default (noop) context. | ||
* | ||
* @readonly | ||
* @type {string} | ||
* @example | ||
* // server-side and client-side contexts are matched based on their respective `name` | ||
* class MyContext extends Context { | ||
@@ -134,8 +144,25 @@ * get name() { | ||
/** | ||
* Start the context. This method is automatically called when `await client.start()` | ||
* is called, or lazilly called when entering the context (i.e. `context.enter()`) | ||
* if the context has been created after `client.start()`. | ||
* Start the context. This method is lazilly called when the client enters the | ||
* context for the first time (cf. ${client.Context#enter}). If you know some | ||
* some heavy and/or potentially long job has to be done when starting the context | ||
* (e.g. call to a web service) it may be a good practice to call it explicitely. | ||
* | ||
* In some situation, you might want to implement this method to handle application | ||
* wise behavior regardeless the client enters or exists the context. | ||
* This method should be implemented to perform operations that are valid for the | ||
* whole lifetime of the context, regardless the client enters or exits the context. | ||
* | ||
* @example | ||
* import { Context } from '@soundworks/core/client.js'; | ||
* | ||
* class MyContext extends Context { | ||
* async start() { | ||
* await super.start(); | ||
* await this.doSomeLongJob(); | ||
* } | ||
* } | ||
* | ||
* // Instantiate the context | ||
* const myContext = new Context(client); | ||
* // manually start the context to perform the long operation before the client | ||
* // enters the context. | ||
* await myContext.start(); | ||
*/ | ||
@@ -146,6 +173,6 @@ async start() {} | ||
* Stop the context. This method is automatically called when `await client.stop()` | ||
* is called. | ||
* is called. It should be used to cleanup context wise operations made in `start()` | ||
* (e.g. destroy the reusable audio graph). | ||
* | ||
* In some situation, you might want to implement this method to handle application | ||
* wise behavior regardeless the client enters or exists the context. | ||
* _WARNING: this method should never be called manually._ | ||
*/ | ||
@@ -152,0 +179,0 @@ async stop() {} |
/** | ||
* Configuration object for a client running in a browser runtime. | ||
* | ||
* @typedef BrowserClientConfig | ||
* @memberof client | ||
* @type {object} | ||
* @property {string} role - Role of the client in the application (e.g. 'player', 'controller'). | ||
* @property {object} [app] - Application configration object. | ||
* @property {string} [app.name=''] - Name of the application. | ||
* @property {string} [app.author=''] - Name of the author. | ||
* @property {object} [env] - Environment configration object. | ||
* @property {string} [env.websockets={}] - Configuration options for websockets. | ||
* @property {string} [env.subpath=''] - If running behind a proxy, path to the application. | ||
*/ | ||
/** | ||
* Configuration object for a client running in a node runtime. | ||
* | ||
* @typedef NodeClientConfig | ||
* @memberof client | ||
* @type {object} | ||
* @property {string} role - Role of the client in the application (e.g. 'player', 'controller'). | ||
* @property {object} [app] - Application configration object. | ||
* @property {string} [app.name=''] - Name of the application. | ||
* @property {string} [app.author=''] - Name of the author. | ||
* @property {object} env - Environment configration object. | ||
* @property {boolean} env.serverAddress - Domain name or IP of the server. | ||
* @property {boolean} env.useHttps - Define is the server run in http or in https. | ||
* @property {boolean} env.port - Port on which the server is listening. | ||
* @property {string} [env.websockets={}] - Configuration options for websockets. | ||
* @property {string} [env.subpath=''] - If running behind a proxy, path to the application. | ||
*/ | ||
/** | ||
* Client-side part of the `soundworks` framework. | ||
@@ -36,0 +3,0 @@ * |
@@ -17,3 +17,4 @@ import BasePlugin from '../common/BasePlugin.js'; | ||
/** | ||
* Base class to extend in order to create new `soundworks` plugins. | ||
* Base class to extend in order to create the client-side counterpart of a | ||
* `soundworks` plugin. | ||
* | ||
@@ -20,0 +21,0 @@ * In the `soundworks` paradigm, a plugin is a component that allows to extend |
@@ -26,3 +26,3 @@ import BasePluginManager from '../common/BasePluginManager.js'; | ||
* and before {@link client.Client#start} or {@link server.Server#start} | ||
* are called for proper initialization. | ||
* to be properly initialized. | ||
* | ||
@@ -39,8 +39,9 @@ * In some sitautions, you might want to register the same plugin factory several times | ||
* ``` | ||
* // client-side | ||
* import { Client } from '@soundworks/core/client.js'; | ||
* import platformPlugin from '@soundworks/plugin-sync/client.js'; | ||
* import syncPlugin from '@soundworks/plugin-sync/client.js'; | ||
* | ||
* const client = new Client(config); | ||
* // register the plugin before `client.start()` | ||
* client.pluginManager.register('sync', pluginSync); | ||
* client.pluginManager.register('sync', syncPlugin); | ||
* | ||
@@ -57,2 +58,21 @@ * await client.start(); | ||
* | ||
* ``` | ||
* // server-side | ||
* import { Server } from '@soundworks/core/server.js'; | ||
* import syncPlugin from '@soundworks/plugin-sync/server.js'; | ||
* | ||
* const server = new Server(config); | ||
* // register the plugin before `server.start()` | ||
* server.pluginManager.register('sync', syncPlugin); | ||
* | ||
* await server.start(); | ||
* | ||
* const sync = await server.pluginManager.get('sync'); | ||
* | ||
* setInterval(() => { | ||
* // log the estimated global synced clock alongside the local clock. | ||
* console.log(sync.getSyncTime(), sync.getLocalTime()); | ||
* }, 1000); | ||
* ``` | ||
* | ||
* @memberof client | ||
@@ -97,4 +117,4 @@ * @extends BasePluginManager | ||
* | ||
* _Note: the async API is designed to enable the dynamic creation of plugins (hopefully | ||
* without brealing changes) in a future release._ | ||
* _Note: the async API is designed to enable the dynamic creation of plugins | ||
* (hopefully without brealing changes) in a future release._ | ||
* | ||
@@ -112,17 +132,6 @@ * @param {client.Plugin#id} id - Id of the plugin as defined when registered. | ||
return super.getUnsafe(id); | ||
return super.unsafeGet(id); | ||
} | ||
// client only methods | ||
// ------------------------------------------------------------ | ||
/** | ||
* @protected | ||
* @ignore | ||
*/ | ||
getRegisteredPlugins() { | ||
return Array.from(this._registeredPlugins.keys()); | ||
} | ||
} | ||
export default PluginManager; |
@@ -31,11 +31,12 @@ import WebSocket from 'isomorphic-ws'; | ||
* [isomorphic-ws](https://github.com/heineiuo/isomorphic-ws) library. | ||
* An instance of `Socket` is automatically created by the `soundworks.Client`. | ||
* An instance of `Socket` is automatically created by the `soundworks.Client` | ||
* (see {@link client.Client#socket}). | ||
* | ||
* _Important: In most cases, you should consider using a {@link client.SharedState} rather than | ||
* sending messages directly through the sockets._ | ||
* _Important: In most cases, you should consider using a {@link client.SharedState} | ||
* rather than directly using the sockets._ | ||
* | ||
* The Socket class concurrently opens two different WebSockets: | ||
* - a socket configured with `binaryType = 'string'` for JSON compatible string | ||
* messages. | ||
* - a socket configured with `binaryType=arraybuffer` for efficient streaming | ||
* - a socket configured with `binaryType = 'blob'` for JSON compatible data | ||
* types (i.e. string, number, boolean, object, array and null). | ||
* - a socket configured with `binaryType= 'arraybuffer'` for efficient streaming | ||
* of binary data. | ||
@@ -46,4 +47,2 @@ * | ||
* | ||
* See {@link client.Client#socket} | ||
* | ||
* @memberof client | ||
@@ -55,3 +54,3 @@ * @hideconstructor | ||
/** | ||
* WebSocket instance w/ string protocol, i.e. `binaryType = 'string'`. | ||
* WebSocket instance configured with `binaryType = 'blob'`. | ||
* | ||
@@ -62,3 +61,3 @@ * @private | ||
/** | ||
* WebSocket instance w/ binary protocol, i.e. `binaryType = 'arraybuffer'`. | ||
* WebSocket instance configured with `binaryType = 'arraybuffer'`. | ||
* | ||
@@ -239,2 +238,18 @@ * @private | ||
/** | ||
* Removes all listeners and immediately close the two sockets. Is automatically | ||
* called on `client.stop()` | ||
* | ||
* @private | ||
*/ | ||
async terminate() { | ||
this.removeAllListeners(); | ||
this.removeAllBinaryListeners(); | ||
this.ws.close(); | ||
this.binaryWs.close(); | ||
return Promise.resolve(); | ||
} | ||
/** | ||
* @param {boolean} binary - Emit to either the string or binary socket. | ||
@@ -301,6 +316,7 @@ * @param {string} channel - Channel name. | ||
/** | ||
* Send JSON compatible messages on a given channel. | ||
* Send messages with JSON compatible data types on a given channel. | ||
* | ||
* @param {string} channel - Channel name of the message. | ||
* @param {...*} args - Message list, as many as needed, of any serializable type). | ||
* @param {string} channel - Channel name. | ||
* @param {...*} args - Payload of the message. As many arguments as needed, of | ||
* JSON compatible data types (i.e. string, number, boolean, object, array and null). | ||
*/ | ||
@@ -313,6 +329,8 @@ send(channel, ...args) { | ||
/** | ||
* Listen to JSON compatible messages on a given channel. | ||
* Listen messages with JSON compatible data types on a given channel. | ||
* | ||
* @param {string} channel - Channel name of the message. | ||
* @param {Function} callback - Callback to execute when a message is received. | ||
* @param {string} channel - Channel name. | ||
* @param {Function} callback - Callback to execute when a message is received, | ||
* arguments of the callback function will match the arguments sent using the | ||
* {@link server.Socket#send} method. | ||
*/ | ||
@@ -326,3 +344,3 @@ addListener(channel, callback) { | ||
* | ||
* @param {string} channel - Channel name of the message. | ||
* @param {string} channel - Channel name. | ||
* @param {Function} callback - Callback to remove. | ||
@@ -335,5 +353,5 @@ */ | ||
/** | ||
* Remove all listeners from JSON compatible messages on a given channel. | ||
* Remove all listeners of messages with JSON compatible data types. | ||
* | ||
* @param {string} channel - Channel name of the message. | ||
* @param {string} channel - Channel name. | ||
*/ | ||
@@ -347,3 +365,3 @@ removeAllListeners(channel = null) { | ||
* | ||
* @param {string} channel - Channel name of the message. | ||
* @param {string} channel - Channel name. | ||
* @param {TypedArray} typedArray - Binary data to be sent. | ||
@@ -357,5 +375,5 @@ */ | ||
/** | ||
* Listen binary messages on a given channel. | ||
* Listen binary messages on a given channel | ||
* | ||
* @param {string} channel - Channel name of the message. | ||
* @param {string} channel - Channel name. | ||
* @param {Function} callback - Callback to execute when a message is received. | ||
@@ -368,6 +386,6 @@ */ | ||
/** | ||
* Remove a listener from binary compatible messages on a given channel | ||
* Remove a listener of binary compatible messages from a given channel | ||
* | ||
* @param {string} channel - Channel of the message. | ||
* @param {Function} callback - Callback to cancel. | ||
* @param {string} channel - Channel name. | ||
* @param {Function} callback - Callback to remove. | ||
*/ | ||
@@ -379,3 +397,3 @@ removeBinaryListener(channel, callback) { | ||
/** | ||
* Remove all listeners from binary compatible messages on a given channel | ||
* Remove all listeners of binary compatible messages on a given channel | ||
* | ||
@@ -387,17 +405,4 @@ * @param {string} channel - Channel of the message. | ||
} | ||
/** | ||
* Removes all listeners and immediately close the two sockets. | ||
*/ | ||
async terminate() { | ||
this.removeAllListeners(); | ||
this.removeAllBinaryListeners(); | ||
this.ws.close(); | ||
this.binaryWs.close(); | ||
return Promise.resolve(); | ||
} | ||
} | ||
export default Socket; |
@@ -79,2 +79,10 @@ import isPlainObject from 'is-plain-obj'; | ||
/** | ||
* Returns the list of the registered plugins ids | ||
* @returns {string[]} | ||
*/ | ||
getRegisteredPlugins() { | ||
return Array.from(this._registeredPlugins.keys()); | ||
} | ||
/** | ||
* Initialize all the registered plugin. Executed during the `Client.init()` or | ||
@@ -102,3 +110,3 @@ * `Server.init()` initialization step. | ||
const promises = Array.from(this._registeredPlugins.keys()).map(id => this.getUnsafe(id)); | ||
const promises = Array.from(this._registeredPlugins.keys()).map(id => this.unsafeGet(id)); | ||
@@ -114,2 +122,3 @@ try { | ||
/** @private */ | ||
async stop() { | ||
@@ -128,3 +137,3 @@ for (let instance of this._instances.values()) { | ||
*/ | ||
async getUnsafe(id) { | ||
async unsafeGet(id) { | ||
if (!isString(id)) { | ||
@@ -152,3 +161,3 @@ throw new Error(`[soundworks.PluginManager] Invalid argument, "pluginManager.get(name)" argument should be a string`); | ||
const { deps } = this._registeredPlugins.get(id); | ||
const promises = deps.map(id => this.getUnsafe(id)); | ||
const promises = deps.map(id => this.unsafeGet(id)); | ||
@@ -155,0 +164,0 @@ await Promise.all(promises); |
import cloneDeep from 'lodash.clonedeep'; | ||
import equal from 'fast-deep-equal'; | ||
/** | ||
* @typedef {Object} server.SharedStateManagerServer~schema | ||
* | ||
* Description of a schema to be register by the {@link server.ServerStateManagerServer} | ||
* A schema consists of a combinaison of key value pairs where the key is the | ||
* name of the parameter, and the value is an object describing the parameter. | ||
* | ||
* Available types are: | ||
* - {@link server.SharedStateManagerServer~schemaBooleanDef} | ||
* - {@link server.SharedStateManagerServer~schemaStringDef} | ||
* - {@link server.SharedStateManagerServer~schemaIntegerDef} | ||
* - {@link server.SharedStateManagerServer~schemaFloatDef} | ||
* - {@link server.SharedStateManagerServer~schemaEnumDef} | ||
* - {@link server.SharedStateManagerServer~schemaAnyDef} | ||
* | ||
* @example | ||
* { | ||
* myBoolean: { | ||
* type: 'boolean' | ||
* default: false, | ||
* }, | ||
* myFloat: { | ||
* type: 'float' | ||
* default: 0.1, | ||
* min: -1, | ||
* max: 1, | ||
* event: true, | ||
* } | ||
* } | ||
*/ | ||
/** | ||
* @typedef {Object} server.SharedStateManagerServer~schemaBooleanDef | ||
* | ||
* @property {String} type='boolean' - Define a boolean parameter. | ||
* @property {Boolean} default - Default value of the parameter. | ||
* @property {Boolean} [nullable=false] - Define if the parameter is nullable. If | ||
* set to `true` the parameter `default` is set to `null`. | ||
* @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g. | ||
* set its value back to `null` after propagation. When `true`, `nullable` is | ||
* automatically set to `true` and `default` to `null`. | ||
* @property {Boolean} [filterChange=true] - Setting this option to `false` forces | ||
* the propagation of a parameter even when its value do not change. It | ||
* offers a kind of middle ground between the default bahavior (e.g. where | ||
* only changed values are propagated) and the behavior of the `event` option | ||
* (which has no state per se). As such, setting this options to `false` if | ||
* `event=true` does not make sens. | ||
* @property {Boolean} [immediate=false] - Setting this option to `true` will | ||
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the | ||
* state that generate the update (i.e. calling `set`), before propagating the | ||
* change on the network. This option can be usefull in cases the network | ||
* would introduce a noticeable latency on the client. If for some reason | ||
* the value is overriden server-side (e.g. in an `updateHook`) the listeners | ||
* will be called again on when the "real" / final value will be received. | ||
* @property {Object} [metas={}] - Optionnal metadata of the parameter. | ||
*/ | ||
/** | ||
* @typedef {Object} server.SharedStateManagerServer~schemaStringDef | ||
* | ||
* @property {String} type='string' - Define a boolean parameter. | ||
* @property {Mixed} default - Default value of the parameter. | ||
* @property {Boolean} [nullable=false] - Define if the parameter is nullable. If | ||
* set to `true` the parameter `default` is set to `null`. | ||
* @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g. | ||
* set its value back to `null` after propagation. When `true`, `nullable` is | ||
* automatically set to `true` and `default` to `null`. | ||
* @property {Boolean} [filterChange=true] - Setting this option to `false` forces | ||
* the propagation of a parameter even when its value do not change. It | ||
* offers a kind of middle ground between the default bahavior (e.g. where | ||
* only changed values are propagated) and the behavior of the `event` option | ||
* (which has no state per se). As such, setting this options to `false` if | ||
* `event=true` does not make sens. | ||
* @property {Boolean} [immediate=false] - Setting this option to `true` will | ||
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the | ||
* state that generate the update (i.e. calling `set`), before propagating the | ||
* change on the network. This option can be usefull in cases the network | ||
* would introduce a noticeable latency on the client. If for some reason | ||
* the value is overriden server-side (e.g. in an `updateHook`) the listeners | ||
* will be called again on when the "real" / final value will be received. | ||
* @property {Object} [metas={}] - Optionnal metadata of the parameter. | ||
*/ | ||
/** | ||
@typedef {Object} server.SharedStateManagerServer~schemaIntegerDef | ||
* | ||
* @property {String} type='integer' - Define a boolean parameter. | ||
* @property {Mixed} default - Default value of the parameter. | ||
* @property {Number} [min=-Infinity] - Minimum value of the parameter. | ||
* @property {Number} [max=+Infinity] - Maximum value of the parameter. | ||
* @property {Boolean} [nullable=false] - Define if the parameter is nullable. If | ||
* set to `true` the parameter `default` is set to `null`. | ||
* @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g. | ||
* set its value back to `null` after propagation. When `true`, `nullable` is | ||
* automatically set to `true` and `default` to `null`. | ||
* @property {Boolean} [filterChange=true] - Setting this option to `false` forces | ||
* the propagation of a parameter even when its value do not change. It | ||
* offers a kind of middle ground between the default bahavior (e.g. where | ||
* only changed values are propagated) and the behavior of the `event` option | ||
* (which has no state per se). As such, setting this options to `false` if | ||
* `event=true` does not make sens. | ||
* @property {Boolean} [immediate=false] - Setting this option to `true` will | ||
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the | ||
* state that generate the update (i.e. calling `set`), before propagating the | ||
* change on the network. This option can be usefull in cases the network | ||
* would introduce a noticeable latency on the client. If for some reason | ||
* the value is overriden server-side (e.g. in an `updateHook`) the listeners | ||
* will be called again on when the "real" / final value will be received. | ||
* @property {Object} [metas={}] - Optionnal metadata of the parameter. | ||
*/ | ||
/** | ||
* @typedef {Object} server.SharedStateManagerServer~schemaFloatDef | ||
* | ||
* @property {String} [type='float'] - Float parameter. | ||
* @property {Mixed} default - Default value. | ||
* @property {Number} [min=-Infinity] - Minimum value. | ||
* @property {Number} [max=+Infinity] - Maximum value. | ||
* @property {Boolean} [nullable=false] - Define if the parameter is nullable. If | ||
* set to `true` the parameter `default` is set to `null`. | ||
* @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g. | ||
* set its value back to `null` after propagation. When `true`, `nullable` is | ||
* automatically set to `true` and `default` to `null`. | ||
* @property {Boolean} [filterChange=true] - Setting this option to `false` forces | ||
* the propagation of a parameter even when its value do not change. It | ||
* offers a kind of middle ground between the default bahavior (e.g. where | ||
* only changed values are propagated) and the behavior of the `event` option | ||
* (which has no state per se). As such, setting this options to `false` if | ||
* `event=true` does not make sens. | ||
* @property {Boolean} [immediate=false] - Setting this option to `true` will | ||
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the | ||
* state that generate the update (i.e. calling `set`), before propagating the | ||
* change on the network. This option can be usefull in cases the network | ||
* would introduce a noticeable latency on the client. If for some reason | ||
* the value is overriden server-side (e.g. in an `updateHook`) the listeners | ||
* will be called again on when the "real" / final value will be received. | ||
* @property {Object} [metas={}] - Optionnal metadata of the parameter. | ||
*/ | ||
/** | ||
* @typedef {Object} server.SharedStateManagerServer~schemaEnumDef | ||
* | ||
* @property {String} [type='enum'] - Enum parameter. | ||
* @property {Mixed} default - Default value of the parameter. | ||
* @property {Array} list - Possible values of the parameter. | ||
* @property {Boolean} [nullable=false] - Define if the parameter is nullable. If | ||
* set to `true` the parameter `default` is set to `null`. | ||
* @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g. | ||
* set its value back to `null` after propagation. When `true`, `nullable` is | ||
* automatically set to `true` and `default` to `null`. | ||
* @property {Boolean} [filterChange=true] - Setting this option to `false` forces | ||
* the propagation of a parameter even when its value do not change. It | ||
* offers a kind of middle ground between the default bahavior (e.g. where | ||
* only changed values are propagated) and the behavior of the `event` option | ||
* (which has no state per se). As such, setting this options to `false` if | ||
* `event=true` does not make sens. | ||
* @property {Boolean} [immediate=false] - Setting this option to `true` will | ||
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the | ||
* state that generate the update (i.e. calling `set`), before propagating the | ||
* change on the network. This option can be usefull in cases the network | ||
* would introduce a noticeable latency on the client. If for some reason | ||
* the value is overriden server-side (e.g. in an `updateHook`) the listeners | ||
* will be called again on when the "real" / final value will be received. | ||
* @property {Object} [metas={}] - Optionnal metadata of the parameter. | ||
*/ | ||
/** | ||
* @typedef {Object} server.SharedStateManagerServer~schemaAnyDef | ||
* | ||
* Note that the `any` type always return a shallow copy of the state internal | ||
* value. Mutating the returned value will not modify the internal state. | ||
* | ||
* @property {String} [type='any'] - Parameter of any type. | ||
* @property {Mixed} default - Default value of the parameter. | ||
* @property {Boolean} [nullable=false] - Define if the parameter is nullable. If | ||
* set to `true` the parameter `default` is set to `null`. | ||
* @property {Boolean} [event=false] - Define if the parameter is a volatile, e.g. | ||
* set its value back to `null` after propagation. When `true`, `nullable` is | ||
* automatically set to `true` and `default` to `null`. | ||
* @property {Boolean} [filterChange=true] - Setting this option to `false` forces | ||
* the propagation of a parameter even when its value do not change. It | ||
* offers a kind of middle ground between the default bahavior (e.g. where | ||
* only changed values are propagated) and the behavior of the `event` option | ||
* (which has no state per se). As such, setting this options to `false` if | ||
* `event=true` does not make sens. | ||
* @property {Boolean} [immediate=false] - Setting this option to `true` will | ||
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the | ||
* state that generate the update (i.e. calling `set`), before propagating the | ||
* change on the network. This option can be usefull in cases the network | ||
* would introduce a noticeable latency on the client. If for some reason | ||
* the value is overriden server-side (e.g. in an `updateHook`) the listeners | ||
* will be called again on when the "real" / final value will be received. | ||
* @property {Object} [metas={}] - Optionnal metadata of the parameter. | ||
*/ | ||
export const sharedOptions = { | ||
@@ -425,3 +236,3 @@ nullable: false, | ||
* | ||
* @param {String} name - Name of the parameter. | ||
* @param {string} name - Name of the parameter. | ||
* @return {Boolean} | ||
@@ -436,3 +247,3 @@ */ | ||
* | ||
* @return {Object} | ||
* @return {object} | ||
*/ | ||
@@ -453,3 +264,3 @@ getValues() { | ||
* | ||
* @param {String} name - Name of the parameter. | ||
* @param {string} name - Name of the parameter. | ||
* @return {Mixed} - Value of the parameter. | ||
@@ -474,5 +285,5 @@ */ | ||
* | ||
* @param {String} name - Name of the parameter. | ||
* @param {string} name - Name of the parameter. | ||
* @param {Mixed} value - Value of the parameter. | ||
* @param {Boolean} [forcePropagation=false] - if true, propagate value even | ||
* @param {boolean} [forcePropagation=false] - if true, propagate value even | ||
* if the value has not changed. | ||
@@ -511,3 +322,3 @@ * @return {Array} - [new value, updated flag]. | ||
* | ||
* @param {String} [name=null] - Name of the parameter to reset. | ||
* @param {string} [name=null] - Name of the parameter to reset. | ||
*/ | ||
@@ -527,3 +338,3 @@ // reset(name = null) { | ||
* | ||
* @return {Object} | ||
* @return {object} | ||
*/ | ||
@@ -530,0 +341,0 @@ getSchema(name = null) { |
@@ -5,9 +5,76 @@ import Client from './Client.js'; | ||
/** | ||
* Base class to extend in order to create the server-side counterpart of | ||
* a {@link client.Context}. If not defined, a default context will be created | ||
* Base class to extend in order to implment the optionnal server-side counterpart | ||
* of a {@link client.Context}. If not defined, a default context will be created | ||
* and used by the server. | ||
* | ||
* In the `soundworks` paradigm, a client has a "role" (e.g. _player_, _controller_) | ||
* see {@link client.Client#role}) and can be in different "contexts" (e.g. different | ||
* part of the experience such as sections of a music piece, etc.). The | ||
* {@link client.Context} and optionnal {@link serverContext} abstractions provide | ||
* a simple and unified way to model these reccuring aspects of an application. | ||
* | ||
* If a `server.Context` is recognized as the server-side counterpart of a | ||
* {@link client.Context}, based on their respective `name` (see {@link client.Context#name} | ||
* and {@link server.Context#name}), `soundworks` will ensure the logic defined | ||
* by the server-side Context will be executed at the beginning of the | ||
* {@link client.Client#enter} and {@link client.Client#exit} methods. The example | ||
* show how soundwords handles (and guarantees) the order of the `enter()` steps | ||
* between the client-side and the server-side parts of the context. The same goes | ||
* for the `exit()` method. | ||
* | ||
* ``` | ||
* // client-side | ||
* import { Context } from '@soundworks/core/client.js'; | ||
* | ||
* class MyContext extends Context { | ||
* async enter() { | ||
* // 1. client side context enter() starts | ||
* // server-side logic is triggered first | ||
* await super.enter(); | ||
* // 4. server-side context enter() is fully done | ||
* // some async job can be done here | ||
* await new Promise(resolve => setTimeout(resolve, 1000)); | ||
* // 5. client-side context enter() ends | ||
* } | ||
* } | ||
* | ||
* // Instanciate the context (assuming the `client.role` is 'test') | ||
* const myContext = new MyContext(client); | ||
* | ||
* // At some point in the application, the client enters the context trigerring | ||
* // the steps 1 to 5 described in the client-side and server-side `enter()` | ||
* // implementations. Note that the server-side `enter()` is never called manually. | ||
* await myContext.enter(); | ||
* ``` | ||
* | ||
* ``` | ||
* // server-side | ||
* import { Context } from '@soundworks/core/server.js'; | ||
* | ||
* class MyContext extends Context { | ||
* async enter(client) { | ||
* // 2. server-side context enter() starts | ||
* await super.enter(client); | ||
* // some async job can be done here | ||
* await new Promise(resolve => setTimeout(resolve, 1000)); | ||
* // 3. server-side context enter() ends | ||
* } | ||
* } | ||
* | ||
* // Instantiate the context | ||
* const myContext = new Context(server); | ||
* ``` | ||
* | ||
* @memberof server | ||
*/ | ||
class Context { | ||
/** | ||
* @param {client.Client} client - The soundworks client instance. | ||
* @param {string|string[]} [roles=[]] - Optionnal list of client roles that can | ||
* use this context. In large applications, this may be usefull to guarantee | ||
* that a context can be consumed only by specific client roles, throwing an | ||
* error if any other client role tries to use it. If empty, no access policy | ||
* will be used. | ||
* @throws Will throw if the first argument is not a soundworks server instance. | ||
*/ | ||
constructor(server, roles = []) { | ||
@@ -18,9 +85,6 @@ if (!(server instanceof Server)) { | ||
if (roles === null) { | ||
throw new Error('Invalid client types'); | ||
} | ||
/** | ||
* soundworks server | ||
* @type {server.Server} | ||
* @readonly | ||
*/ | ||
@@ -31,3 +95,3 @@ this.server = server; | ||
* List of clients that are currently in this context | ||
* @type {Client[]} | ||
* @type {server.Client[]} | ||
*/ | ||
@@ -38,3 +102,4 @@ this.clients = new Set(); | ||
* Status of the context ('idle', 'inited', 'started' or 'errored') | ||
* @type {String} | ||
* @type {string} | ||
* @readonly | ||
*/ | ||
@@ -44,5 +109,12 @@ this.status = 'idle'; | ||
/** | ||
* List of client role associated with this context. | ||
* List of client roles that can use this context. | ||
* @type {string[]} | ||
* @readonly | ||
*/ | ||
roles = Array.isArray(roles) ? roles : [roles]; | ||
if (roles.length === 0) { | ||
roles = Object.keys(server.config.app.clients); | ||
} | ||
this.roles = new Set(roles); | ||
@@ -55,4 +127,17 @@ | ||
/** | ||
* Name of the context, default to the class name. | ||
* Override the `get name()` getter to use a user-defined context name. | ||
* Optionnal user-defined name of the context (defaults to the class name). | ||
* | ||
* The context manager will match the client-side and server-side contexts based | ||
* on this name. If the {@link server.ContextManager} don't find a corresponding | ||
* user-defined context with the same name, it will use a default (noop) context. | ||
* | ||
* @readonly | ||
* @type {string} | ||
* @example | ||
* // server-side and client-side contexts are matched based on their respective `name` | ||
* class MyContext extends Context { | ||
* get name() { | ||
* return 'my-user-defined-context-name'; | ||
* } | ||
* } | ||
*/ | ||
@@ -64,6 +149,27 @@ get name() { | ||
/** | ||
* Method automatically called when the server starts, or lazilly called if | ||
* the context is created after `server.start()` | ||
* Start the context. This method is lazilly called when a client enters the | ||
* context for the first time (cf. ${server.Context#enter}). If you know some | ||
* some heavy and/or potentially long job has to be done when starting the context | ||
* (e.g. connect to a database, parsing a long file) it may be a good practice | ||
* to call it explicitely. | ||
* | ||
* _WARNING: this method should never be called manually._ | ||
* This method should be implemented to perform operations that are valid for the | ||
* whole lifetime of the context, regardless a client enters or exits the context. | ||
* | ||
* @example | ||
* import { Context } from '@soundworks/core/server.js'; | ||
* | ||
* class MyContext extends Context { | ||
* async start() { | ||
* await super.start(); | ||
* await this.doSomeLongJob(); | ||
* } | ||
* } | ||
* | ||
* // Instantiate the context | ||
* const myContext = new Context(server, ['test']); | ||
* // manually start the context to perform the long operation before the first | ||
* // client enters the context | ||
* await myContext.start(); | ||
* ``` | ||
*/ | ||
@@ -73,3 +179,5 @@ async start() {} | ||
/** | ||
* Method automatically called when the server stops. | ||
* Stop the context. The method that is automatically called when the server | ||
* stops. It should be used to cleanup context wise operations made in `start()` | ||
* (e.g. disconnect from a database, release a file handle). | ||
* | ||
@@ -81,5 +189,24 @@ * _WARNING: this method should never be called manually._ | ||
/** | ||
* Method automatically called when the client enters the context. | ||
* Enter the context. Implement this method to define the logic that should be | ||
* done (e.g. creating a shared state, etc.) when a client enters the context. | ||
* | ||
* If the context has not been started yet, the `start` method is implicitely executed. | ||
* | ||
* _WARNING: this method should never be called manually._ | ||
* | ||
* @param {server.Client} client - Server-side representation of the client | ||
* that enters the context. | ||
* @returns {Promise} - Promise that resolves when the context is entered. | ||
* @example | ||
* class MyContext extends Context { | ||
* async enter(client) { | ||
* await super.enter(client); | ||
* registerTheClientSomewhere(client); | ||
* } | ||
* | ||
* async exit(client) { | ||
* await super.exit(client); | ||
* unregisterTheClientSomewhere(client); | ||
* } | ||
* } | ||
*/ | ||
@@ -95,5 +222,22 @@ async enter(client) { | ||
/** | ||
* Method automatically called when the client exits the context. | ||
* Exit the context. Implement this method to define the logic that should be | ||
* done (e.g. delete a shared state, etc.) when a client exits the context. | ||
* | ||
* _WARNING: this method should never be called manually._ | ||
* * _WARNING: this method should never be called manually._ | ||
* | ||
* @param {server.Client} client - Server-side representation of the client | ||
* that exits the context. | ||
* @returns {Promise} - Promise that resolves when the context is exited. | ||
* @example | ||
* class MyContext extends Context { | ||
* async enter(client) { | ||
* await super.enter(client); | ||
* this.state = await this.client.stateManager.create('my-context-state'); | ||
* } | ||
* | ||
* async exit(client) { | ||
* await super.exit(client); | ||
* await this.state.delete(); | ||
* } | ||
* } | ||
*/ | ||
@@ -100,0 +244,0 @@ async exit(client) { |
@@ -60,2 +60,3 @@ import Context from './Context.js'; | ||
* @memberof server | ||
* @hideconstructor | ||
*/ | ||
@@ -95,6 +96,5 @@ class ContextManager { | ||
* | ||
* @see {server.Context#name} | ||
* @param {String} contextName - Name of the context. | ||
* _WARNING: Most of the time, you should not have to call this method manually._ | ||
* | ||
* @private | ||
* @param {server.Context#name} contextName - Name of the context. | ||
*/ | ||
@@ -141,5 +141,4 @@ async get(contextName) { | ||
const ctor = createNamedContextClass(contextName); | ||
const roles = Object.keys(this.server.config.app.clients); | ||
// this will automatically register the context in the context manager | ||
new ctor(this.server, roles); | ||
new ctor(this.server); | ||
} | ||
@@ -146,0 +145,0 @@ |
/** | ||
* Configuration object for the server. | ||
* | ||
* @typedef ServerConfig | ||
* @memberof server | ||
* @type {object} | ||
* @property {object} [app] - Application configration object. | ||
* @property {object} app.clients - Definition of the application clients. | ||
* @property {string} [app.name=''] - Name of the application. | ||
* @property {string} [app.author=''] - Name of the author. | ||
* @property {object} [env] - Environment configration object. | ||
* @property {boolean} env.port - Port on which the server is listening. | ||
* @property {boolean} env.useHttps - Define is the server run in http or in https. | ||
* @property {boolean} [env.httpsInfos={}] - Path to cert files for https. | ||
* @property {boolean} env.serverAddress - Domain name or IP of the server. | ||
* Mandatory if node clients are defined | ||
* @property {string} [env.websockets={}] - Configuration options for websockets. | ||
* @property {string} [env.subpath=''] - If running behind a proxy, path to the application. | ||
*/ | ||
/** | ||
* Server-side part of the *soundworks* framework. | ||
@@ -23,0 +3,0 @@ * |
@@ -17,3 +17,4 @@ import BasePlugin from '../common/BasePlugin.js'; | ||
/** | ||
* Base class to extend in order to create new `soundworks` plugins. | ||
* Base class to extend in order to create the server-side counterpart of a | ||
* `soundworks` plugin. | ||
* | ||
@@ -20,0 +21,0 @@ * In the `soundworks` paradigm, a plugin is a component that allows to extend |
@@ -26,3 +26,3 @@ import BasePluginManager from '../common/BasePluginManager.js'; | ||
* and before {@link client.Client#start} or {@link server.Server#start} | ||
* are called for proper initialization. | ||
* to be properly initialized. | ||
* | ||
@@ -37,8 +37,28 @@ * In some sitautions, you might want to register the same plugin factory several times | ||
* ``` | ||
* // client-side | ||
* import { Client } from '@soundworks/core/client.js'; | ||
* import syncPlugin from '@soundworks/plugin-sync/client.js'; | ||
* | ||
* const client = new Client(config); | ||
* // register the plugin before `client.start()` | ||
* client.pluginManager.register('sync', syncPlugin); | ||
* | ||
* await client.start(); | ||
* | ||
* const sync = await client.pluginManager.get('sync'); | ||
* | ||
* setInterval(() => { | ||
* // log the estimated global synced clock alongside the local clock. | ||
* console.log(sync.getSyncTime(), sync.getLocalTime()); | ||
* }, 1000); | ||
* ``` | ||
* | ||
* ``` | ||
* // server-side | ||
* import { Server } from '@soundworks/core/server.js'; | ||
* import platformPlugin from '@soundworks/plugin-sync/server.js'; | ||
* import syncPlugin from '@soundworks/plugin-sync/server.js'; | ||
* | ||
* const server = new Server(config); | ||
* // register the plugin before `server.start()` | ||
* server.pluginManager.register('sync', pluginSync); | ||
* server.pluginManager.register('sync', syncPlugin); | ||
* | ||
@@ -91,4 +111,4 @@ * await server.start(); | ||
* | ||
* _Note: the async API is designed to enable the dynamic creation of plugins (hopefully | ||
* without brealing changes) in a future release._ | ||
* _Note: the async API is designed to enable the dynamic creation of plugins | ||
* (hopefully without brealing changes) in a future release._ | ||
* | ||
@@ -106,3 +126,3 @@ * @param {server.Plugin#id} id - Id of the plugin as defined when registered. | ||
return super.getUnsafe(id); | ||
return super.unsafeGet(id); | ||
} | ||
@@ -109,0 +129,0 @@ |
@@ -34,2 +34,23 @@ import fs from 'node:fs'; | ||
/** | ||
* Configuration object for the server. | ||
* | ||
* @typedef ServerConfig | ||
* @memberof server | ||
* @type {object} | ||
* @property {object} [app] - Application configration object. | ||
* @property {object} app.clients - Definition of the application clients. | ||
* @property {string} [app.name=''] - Name of the application. | ||
* @property {string} [app.author=''] - Name of the author. | ||
* @property {object} [env] - Environment configration object. | ||
* @property {boolean} env.port - Port on which the server is listening. | ||
* @property {boolean} env.useHttps - Define is the server run in http or in https. | ||
* @property {boolean} [env.httpsInfos={}] - Path to cert files for https. | ||
* @property {boolean} env.serverAddress - Domain name or IP of the server. | ||
* Mandatory if node clients are defined | ||
* @property {string} [env.websockets={}] - Configuration options for websockets. | ||
* @property {string} [env.subpath=''] - If running behind a proxy, path to the application. | ||
*/ | ||
/** @private */ | ||
const DEFAULT_CONFIG = { | ||
@@ -78,26 +99,58 @@ env: { | ||
/** | ||
* Server side entry point for a `soundworks` application. | ||
* The `Server` class is the main entry point for the server-side of a soundworks | ||
* application. | ||
* | ||
* This object hosts configuration informations, as well as methods to | ||
* initialize and start the application. It is also responsible for creating | ||
* the static file (http) server as well as the socket server. | ||
* The `Server` instance allows to access soundworks components such as {@link server.StateManager}, | ||
* {@link server.PluginManager},{@link server.Socket} or {@link server.ContextManager}. | ||
* Its is also responsible for handling the initialization lifecycles of the different | ||
* soundworks components. | ||
* | ||
* @memberof server | ||
* ``` | ||
* import { Server } from '@soundworks/core/server'; | ||
* | ||
* @param {server.ServerConfig} config | ||
* @example | ||
* import { Server } from '@soundworks/core/server'; | ||
* const server = new Server({ | ||
* app: { | ||
* name: 'my-example-app', | ||
* clients: { myClient: { target: 'node' } }, | ||
* clients: { | ||
* player: { target: 'browser', default: true }, | ||
* controller: { target: 'browser' }, | ||
* thing: { target: 'node' } | ||
* }, | ||
* }, | ||
* env: { | ||
* port: 8888, | ||
* port: 8000, | ||
* }, | ||
* }); | ||
* await server.init(); | ||
* | ||
* await server.start(); | ||
* ``` | ||
* | ||
* According to the clients definitions provided in `config.app.clients`, the | ||
* server will automatically create a dedicated route for each browser client role. | ||
* For example, given the config object of the example above that defines two | ||
* different client roles for browser targets (i.e. `player` and `controller`): | ||
* | ||
* ``` | ||
* config.app.clients = { | ||
* player: { target: 'browser', default: true }, | ||
* controller: { target: 'browser' }, | ||
* } | ||
* ``` | ||
* | ||
* The server will listen to the following URLs: | ||
* - `http://127.0.0.1:8000/` for the `player` role, which is defined as the default client. | ||
* - `http://127.0.0.1:8000/controller` for the `controller` role. | ||
* | ||
* @memberof server | ||
*/ | ||
class Server { | ||
/** | ||
* @param {server.ServerConfig} config - Configuration object for the server. | ||
* @throws | ||
* - If `config.app.clients` is empty. | ||
* - If a `node` client is defined but `config.env.serverAddress` is not defined. | ||
* - if `config.env.useHttps` is `true` and `config.env.httpsInfos` is not `null` | ||
* (which generates self signed certificated), `config.env.httpsInfos.cert` and | ||
* `config.env.httpsInfos.key` should point to valid cert files. | ||
*/ | ||
constructor(config) { | ||
@@ -108,4 +161,3 @@ if (!isPlainObject(config)) { | ||
/** | ||
* Configuration informations. | ||
* Defaults to: | ||
* Given config object merged with the following defaults: | ||
* ``` | ||
@@ -116,5 +168,12 @@ * { | ||
* port: 8000, | ||
* subfolder: '', | ||
* serverAddress: null, | ||
* subpath: '', | ||
* websockets: { | ||
* path: 'socket', | ||
* pingInterval: 5000, | ||
* }, | ||
* useHttps: false, | ||
* httpsInfos: null, | ||
* crossOriginIsolated: true, | ||
* verbose: true, | ||
* }, | ||
@@ -124,3 +183,3 @@ * app: { | ||
* clients: {}, | ||
* }, | ||
* } | ||
* } | ||
@@ -169,4 +228,17 @@ * ``` | ||
/** | ||
* Router. Internally use polka. | ||
* (cf. {@link https://github.com/lukeed/polka}) | ||
* Instance of the express router. | ||
* | ||
* The router can be used to open new route, for example to expose a directory | ||
* of static assets (in default soundworks applications only the `public` is exposed). | ||
* | ||
* @see {@link https://github.com/expressjs/express} | ||
* @example | ||
* import { Server } from '@soundworks/core/server.js'; | ||
* import express from 'express'; | ||
* | ||
* // create the soundworks server instance | ||
* const server = new Server(config); | ||
* | ||
* // expose assets located in the `soundfiles` directory on the network | ||
* server.router.use('/soundfiles', express.static('soundfiles'))); | ||
*/ | ||
@@ -178,4 +250,6 @@ this.router = express(); | ||
/** | ||
* Http(s) server instance. The node `http` or `https` module instance | ||
* (cf. {@link https://nodejs.org/api/http.html}) | ||
* Raw Node.js `http` or `https` instance | ||
* | ||
* @see {@link https://nodejs.org/api/http.html} | ||
* @see {@link https://nodejs.org/api/https.html} | ||
*/ | ||
@@ -185,11 +259,4 @@ this.httpServer = null; | ||
/** | ||
* Key / value storage with Promise based Map API | ||
* basically a wrapper around kvey (cf. {@link https://github.com/lukechilds/keyv}) | ||
* @private | ||
*/ | ||
this.db = this.createNamespacedDb('core'); | ||
/** | ||
* The {@link server.Sockets} instance. A small wrapper around | ||
* [`ws`](https://github.com/websockets/ws) server. | ||
* Instance of the {@link server.Sockets} class. | ||
* | ||
* @see {@link server.Sockets} | ||
@@ -201,3 +268,4 @@ * @type {server.Sockets} | ||
/** | ||
* The {@link server.PluginManager} instance. | ||
* Instance of the {@link server.PluginManager} class. | ||
* | ||
* @see {@link server.PluginManager} | ||
@@ -209,3 +277,4 @@ * @type {server.PluginManager} | ||
/** | ||
* The {@link server.StateManager} instance. | ||
* Instance of the {@link server.StateManager} class. | ||
* | ||
* @see {@link server.StateManager} | ||
@@ -217,4 +286,6 @@ * @type {server.StateManager} | ||
/** | ||
* The {@link server.ContextManager} instance. | ||
* Instance of the {@link server.ContextManager} class. | ||
* | ||
* @see {@link server.ContextManager} | ||
* @type {server.ContextManager} | ||
*/ | ||
@@ -224,4 +295,4 @@ this.contextManager = new ContextManager(this); | ||
/** | ||
* If https is required, will contain informations about the certificates | ||
* (self-signed, validity dates, etc.) | ||
* If `https` is required, hold informations about the certificates, e.g. if | ||
* self-signed, the dates of validity of the certificates, etc. | ||
*/ | ||
@@ -231,8 +302,19 @@ this.httpsInfos = null; | ||
/** | ||
* Current status of the server ['idle', 'inited', 'started'] | ||
* Status of the server, 'idle', 'inited', 'started' or 'errored'. | ||
* | ||
* @type {string} | ||
*/ | ||
this.status = 'idle'; | ||
/** | ||
* Simple key / value database with Promise based Map API store on filesystem, | ||
* basically a tiny wrapper around the `kvey` package. | ||
* | ||
* @private | ||
* @see {@link https://github.com/lukechilds/keyv} | ||
*/ | ||
this.db = this.createNamespacedDb('core'); | ||
/** @private */ | ||
this._applicationTemplateConfig = { | ||
this._applicationTemplateOptions = { | ||
templateEngine: null, | ||
@@ -248,3 +330,3 @@ templatePath: null, | ||
// create audit state | ||
// register audit state schema | ||
this.stateManager.registerSchema(AUDIT_STATE_NAME, auditSchema); | ||
@@ -257,6 +339,9 @@ | ||
/** | ||
* Method to be called before `start` in the initialization lifecycle of the | ||
* soundworks server. Note that if `init()`` is not explicitely called, `start()` | ||
* will call it implicitely. | ||
* The `init` method is part of the initialization lifecycle of the `soundworks` | ||
* server. Most of the time, the `init` method will be implicitly called by the | ||
* {@link server.Server#start} method. | ||
* | ||
* In some situations you might want to call this method manually, in such cases | ||
* the method should be called before the {@link server.Server#start} method. | ||
* | ||
* What it does: | ||
@@ -266,6 +351,6 @@ * - create the audit state | ||
* declared in `config.app.clients` | ||
* - starts all registered plugins | ||
* - initialize all registered plugins | ||
* | ||
* After `await server.init()`, you can safely use the StateManager, as well | ||
* as any registered Plugins. | ||
* After `await server.init()` is fulfilled, the {@link server.Server#stateManager} | ||
* and all registered plugins can be safely used. | ||
* | ||
@@ -440,5 +525,5 @@ * @example | ||
if (!nodeOnly) { | ||
if (this._applicationTemplateConfig.templateEngine === null | ||
|| this._applicationTemplateConfig.templatePath === null | ||
|| this._applicationTemplateConfig.clientConfigFunction === null | ||
if (this._applicationTemplateOptions.templateEngine === null | ||
|| this._applicationTemplateOptions.templatePath === null | ||
|| this._applicationTemplateOptions.clientConfigFunction === null | ||
) { | ||
@@ -484,13 +569,19 @@ throw new Error('[soundworks:Server] A browser client has been found in "config.app.clients" but configuration for html templating is missing. You should probably call `server.setDefaultTemplateConfig()` if you use the soundworks-template and/or refer (at your own risks) to the documentation of `setCustomTemplateConfig()`'); | ||
/** | ||
* Method to be called when `init` step is done in the initialization | ||
* lifecycle of the soundworks server. If `server.init()` has not been called | ||
* explicitely, `server.start()` will call it automatically. | ||
* The `start` method is part of the initialization lifecycle of the `soundworks` | ||
* server. The `start` method will implicitly call the {@link server.Server#init} | ||
* method if it has not been called manually. | ||
* | ||
* What it does: | ||
* - starts all registered contexts (context are automatically registered | ||
* when instantiated) | ||
* - start the web socket server | ||
* - launch the HTTP server on given port | ||
* - implicitely call {@link server.Server#init} if not done manually | ||
* - launch the HTTP and WebSocket servers | ||
* - start all created contexts. To this end, you will have to call `server.init` | ||
* manually and instantiate the contexts between `server.init()` and `server.start()` | ||
* | ||
* After `await server.start()` the server is ready to accept incoming connexions | ||
* After `await server.start()` the server is ready to accept incoming connections | ||
* | ||
* @example | ||
* import { Server } from '@soundworks/core/server.js' | ||
* | ||
* const server = new Server(config); | ||
* await server.start(); | ||
*/ | ||
@@ -583,6 +674,18 @@ async start() { | ||
// @todo - handle gracefull close of the server (but define what it means first...) | ||
/** | ||
* Stop the server, close all existing WebSocket connections. | ||
* Mainly usefull for test. | ||
* Stops all started contexts, plugins, close all the socket connections and | ||
* the http(s) server. | ||
* | ||
* In most situations, you might not need to call this method. However, it can | ||
* be usefull for unit testing or similar situations where you want to create | ||
* and delete several servers in the same process. | ||
* | ||
* @example | ||
* import { Server } from '@soundworks/core/server.js' | ||
* | ||
* const server = new Server(config); | ||
* await server.start(); | ||
* | ||
* await new Promise(resolve => setTimeout(resolve, 1000)); | ||
* await server.stop(); | ||
*/ | ||
@@ -630,3 +733,3 @@ async stop() { | ||
clientConfigFunction, | ||
} = this._applicationTemplateConfig; | ||
} = this._applicationTemplateOptions; | ||
@@ -806,4 +909,4 @@ const clientTmpl = path.join(templatePath, `${role}.tmpl`); | ||
/** | ||
* Configure the server to work out-of-the box with the soundworks-template | ||
* directory tree structure. | ||
* Configure the server to work _out-of-the-box_ within the soundworks application | ||
* template provided by `@soundworks/create. | ||
* | ||
@@ -820,5 +923,8 @@ * - uses [template-literal](https://www.npmjs.com/package/template-literal) package | ||
* - the `./.build/public` directory which is exposed behind the `build` path | ||
* | ||
* _Note: except in very rare cases (so rare that they are quite difficult to imagine), | ||
* you should rely on these defaults._ | ||
*/ | ||
setDefaultTemplateConfig() { | ||
this._applicationTemplateConfig = { | ||
useDefaultApplicationTemplate() { | ||
this._applicationTemplateOptions = { | ||
templateEngine: { compile }, | ||
@@ -847,16 +953,26 @@ templatePath: path.join('.build', 'server', 'tmpl'), | ||
/** | ||
* Define your own template path, template engine, and clientConfig function. | ||
* This method is for very advanced use-cases and only be used if you know what | ||
* you are doing. As such its behavior could probably be improved a lot... | ||
* | ||
* If you end up using this, please contact me to explain your use-case :) | ||
* Define custom template path, template engine, and clientConfig function. | ||
* This method is proposed for very advanced use-cases and should very probably | ||
* be improved. If you consider using this for some reason, please get in touch | ||
* first to explain your use-case :) | ||
*/ | ||
setCustomTemplateConfig(options) { | ||
Object.assign(this._applicationTemplateConfig, options); | ||
setCustomApplicationTemplateOptions(options) { | ||
Object.assign(this._applicationTemplateOptions, options); | ||
} | ||
/** | ||
* Get the global audit state of the application. | ||
* Attach and retrieve the global audit state of the application. | ||
* | ||
* The audit state is a {@link server.SharedState} instance that keeps track of | ||
* global informations about the application such as, the number of connected | ||
* clients, network latency estimation, etc. | ||
* | ||
* The audit state is created by the server on start up. | ||
* | ||
* @returns {Promise<server.SharedState>} | ||
* @throws Will throw if called before `server.init()` | ||
* @see {@link server.SharedState} | ||
* @example | ||
* const auditState = await server.getAuditState(); | ||
* auditState.onUpdate(() => console.log(auditState.getValues()), true); | ||
*/ | ||
@@ -863,0 +979,0 @@ async getAuditState() { |
@@ -9,35 +9,44 @@ import { getTime } from '@ircam/sc-gettime'; | ||
// const CONNECTING = 0; | ||
// const OPEN = 1; | ||
// const CLOSING = 2; | ||
// const CLOSED = 3; | ||
// const READY_STATES = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; | ||
// Status codes: | ||
// | ||
// CONNECTING = 0; | ||
// OPEN = 1; | ||
// CLOSING = 2; | ||
// CLOSED = 3; | ||
// READY_STATES = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; | ||
/** | ||
* Simple wrapper with simple pubsub system built on top of `ws` sockets. | ||
* The abstraction contains two different socket: | ||
* - one configured for string (JSON compatible) messages | ||
* - one configured with `binaryType=arraybuffer` for streaming data more | ||
* efficiently. | ||
* The Socket class is a simple publish / subscribe wrapper built on top of the | ||
* [ws](https://github.com/websockets/ws) library. An instance of {@link server.Socket} | ||
* is automatically created per client when it connects (see {@link server.Client#socket}). | ||
* | ||
* @see https://github.com/websockets/ws | ||
* _Important: In most cases, you should consider using a {@link client.SharedState} | ||
* rather than directly using the sockets._ | ||
* | ||
* The Socket class contains two different WebSockets: | ||
* - a socket configured with `binaryType = 'blob'` for JSON compatible data | ||
* types (i.e. string, number, boolean, object, array and null). | ||
* - a socket configured with `binaryType= 'arraybuffer'` for efficient streaming | ||
* of binary data. | ||
* | ||
* @memberof server | ||
* @hideconstructor | ||
*/ | ||
class Socket { | ||
constructor(ws, binaryWs, rooms, sockets, options = {}) { | ||
/** | ||
* Reference to the sockets object, is mainly dedicated to allow | ||
* broadcasting from a given socket instance. | ||
* @type {server.Sockets} | ||
* @example | ||
* socket.sockets.broadcast('my-room', this, 'update-value', 1); | ||
* Configuration object | ||
* | ||
* @type {object} | ||
*/ | ||
this.sockets = sockets; | ||
this.config = { | ||
pingInterval: 5 * 1000, | ||
...options, | ||
}; | ||
/** | ||
* `ws` socket instance configured with `binaryType=blob` (string) | ||
* | ||
* @type {object} | ||
* @private | ||
* @type {Object} | ||
*/ | ||
@@ -48,4 +57,5 @@ this.ws = ws; | ||
* `ws` socket instance configured with `binaryType=arraybuffer` (TypedArray) | ||
* | ||
* @type {object} | ||
* @private | ||
* @type {Object} | ||
*/ | ||
@@ -55,18 +65,22 @@ this.binaryWs = binaryWs; | ||
/** | ||
* `ws` socket instance configured with `binaryType=arraybuffer` (TypedArray) | ||
* @private | ||
* @type {Map} | ||
* Reference to the sockets object, is mainly dedicated to allow | ||
* broadcasting from a given socket instance. | ||
* | ||
* @type {server.Sockets} | ||
* @example | ||
* socket.sockets.broadcast('my-room', this, 'update-value', 1); | ||
*/ | ||
this.rooms = rooms; | ||
this.sockets = sockets; | ||
/** | ||
* Configuration object | ||
* @type {Object} | ||
* Reference to the rooms object | ||
* | ||
* @type {Map} | ||
* @private | ||
*/ | ||
this.config = { | ||
pingInterval: 5 * 1000, | ||
...options, | ||
}; | ||
this.rooms = rooms; | ||
/** @private */ | ||
this._stringListeners = new Map(); | ||
/** @private */ | ||
this._binaryListeners = new Map(); | ||
@@ -156,5 +170,7 @@ | ||
/** | ||
* Called when the string socket closes (aka client reload). | ||
* Removes all listeners and immediately close the two sockets. Is automatically | ||
* called on `server.stop()` | ||
* | ||
* @private | ||
*/ | ||
/** @private */ | ||
terminate() { | ||
@@ -197,3 +213,8 @@ // clear ping/pong check | ||
/** @private */ | ||
/** | ||
* @param {boolean} binary - Emit to either the string or binary socket. | ||
* @param {string} channel - Channel name. | ||
* @param {...*} args - Content of the message. | ||
* @private | ||
*/ | ||
_emit(binary, channel, ...args) { | ||
@@ -208,3 +229,8 @@ const listeners = binary ? this._binaryListeners : this._stringListeners; | ||
/** @private */ | ||
/** | ||
* @param {Function[]} listeners - List of listeners, either for the string or binary socket. | ||
* @param {string} channel - Channel name. | ||
* @param {Function} callback - The function to be added to the listeners. | ||
* @private | ||
*/ | ||
_addListener(listeners, channel, callback) { | ||
@@ -219,3 +245,8 @@ if (!listeners.has(channel)) { | ||
/** @private */ | ||
/** | ||
* @param {Function[]} listeners - List of listeners, either for the string or binary socket. | ||
* @param {string} channel - Channel name. | ||
* @param {Function} callback - The function to be removed from the listeners. | ||
* @private | ||
*/ | ||
_removeListener(listeners, channel, callback) { | ||
@@ -232,5 +263,12 @@ if (listeners.has(channel)) { | ||
/** @private */ | ||
/** | ||
* @param {Function[]} listeners - List of listeners, either for the string or binary socket. | ||
* @param {string} [channel=null] - Channel name of the listeners to remove. If null | ||
* all the listeners are cleared. | ||
* @private | ||
*/ | ||
_removeAllListeners(listeners, channel) { | ||
if (listeners.has(channel)) { | ||
if (channel === null) { | ||
listeners.clear(); | ||
} else if (listeners.has(channel)) { | ||
listeners.delete(channel); | ||
@@ -242,3 +280,4 @@ } | ||
* Add the socket to a room | ||
* @param {String} roomId - Id of the room | ||
* | ||
* @param {string} roomId - Id of the room. | ||
*/ | ||
@@ -256,3 +295,4 @@ addToRoom(roomId) { | ||
* Remove the socket from a room | ||
* @param {String} roomId - Id of the room | ||
* | ||
* @param {string} roomId - Id of the room. | ||
*/ | ||
@@ -267,6 +307,7 @@ removeFromRoom(roomId) { | ||
/** | ||
* Sends JSON compatible messages on a given channel | ||
* Send messages with JSON compatible data types on a given channel. | ||
* | ||
* @param {String} channel - Channel of the message | ||
* @param {...*} args - Arguments of the message (as many as needed, of any type) | ||
* @param {string} channel - Channel name. | ||
* @param {...*} args - Payload of the message. As many arguments as needed, of | ||
* JSON compatible data types (i.e. string, number, boolean, object, array and null). | ||
*/ | ||
@@ -290,6 +331,8 @@ send(channel, ...args) { | ||
/** | ||
* Listen JSON compatible messages on a given channel | ||
* Listen messages with JSON compatible data types on a given channel. | ||
* | ||
* @param {String} channel - Channel of the message | ||
* @param {Function} callback - Callback to execute when a message is received | ||
* @param {string} channel - Channel name. | ||
* @param {Function} callback - Callback to execute when a message is received, | ||
* arguments of the callback function will match the arguments sent using the | ||
* {@link server.Socket#send} method. | ||
*/ | ||
@@ -301,6 +344,6 @@ addListener(channel, callback) { | ||
/** | ||
* Remove a listener from JSON compatible messages on a given channel | ||
* Remove a listener of messages with JSON compatible data types from a given channel. | ||
* | ||
* @param {String} channel - Channel of the message | ||
* @param {Function} callback - Callback to cancel | ||
* @param {string} channel - Channel name. | ||
* @param {Function} callback - Callback to remove. | ||
*/ | ||
@@ -312,7 +355,7 @@ removeListener(channel, callback) { | ||
/** | ||
* Remove all listeners from JSON compatible messages on a given channel | ||
* Remove all listeners of messages with JSON compatible data types. | ||
* | ||
* @param {String} channel - Channel of the message | ||
* @param {string} channel - Channel name. | ||
*/ | ||
removeAllListeners(channel) { | ||
removeAllListeners(channel = null) { | ||
this._removeAllListeners(this._stringListeners, channel); | ||
@@ -322,6 +365,6 @@ } | ||
/** | ||
* Sends binary messages on a given channel | ||
* Send binary messages on a given channel. | ||
* | ||
* @param {String} channel - Channel of the message | ||
* @param {TypedArray} typedArray - Data to send | ||
* @param {string} channel - Channel name. | ||
* @param {TypedArray} typedArray - Binary data to be sent. | ||
*/ | ||
@@ -341,4 +384,4 @@ sendBinary(channel, typedArray) { | ||
* | ||
* @param {String} channel - Channel of the message | ||
* @param {Function} callback - Callback to execute when a message is received | ||
* @param {string} channel - Channel name. | ||
* @param {Function} callback - Callback to execute when a message is received. | ||
*/ | ||
@@ -350,6 +393,6 @@ addBinaryListener(channel, callback) { | ||
/** | ||
* Remove a listener from binary compatible messages on a given channel | ||
* Remove a listener of binary compatible messages from a given channel | ||
* | ||
* @param {String} channel - Channel of the message | ||
* @param {Function} callback - Callback to cancel | ||
* @param {string} channel - Channel name. | ||
* @param {Function} callback - Callback to remove. | ||
*/ | ||
@@ -361,7 +404,7 @@ removeBinaryListener(channel, callback) { | ||
/** | ||
* Remove all listeners from binary compatible messages on a given channel | ||
* Remove all listeners of binary compatible messages on a given channel | ||
* | ||
* @param {String} channel - Channel of the message | ||
* @param {string} channel - Channel of the message. | ||
*/ | ||
removeAllBinaryListeners(channel) { | ||
removeAllBinaryListeners(channel = null) { | ||
this._removeAllListeners(this._binaryListeners, channel); | ||
@@ -368,0 +411,0 @@ } |
@@ -14,3 +14,4 @@ import { Worker } from 'node:worker_threads'; | ||
/** | ||
* Websocket server that creates and host all {@link server.Socket} instance. | ||
* Manager all {@link server.Socket} instances. | ||
* | ||
* Most of the time, you shouldn't have to use this class instance directely, but | ||
@@ -40,5 +41,5 @@ * it could be usefull in some situations, for broadcasting messages, creating rooms, etc. | ||
/** | ||
* Initialize sockets, all sockets are added by default added to two rooms: | ||
* Initialize sockets, all sockets are added to two rooms by default: | ||
* - to the room corresponding to the client `role` | ||
* - to the '*' that holds all connected sockets | ||
* - to the '*' room that holds all connected sockets | ||
* | ||
@@ -105,3 +106,7 @@ * @private | ||
/** @protected */ | ||
/** | ||
* Terminate all existing sockets | ||
* | ||
* @private | ||
*/ | ||
terminate() { | ||
@@ -152,3 +157,3 @@ // terminate stat worker thread | ||
* @param {server.Socket} socket - Socket to add to the room. | ||
* @param {String} roomId - Id of the room | ||
* @param {String} roomId - Id of the room. | ||
*/ | ||
@@ -163,3 +168,3 @@ addToRoom(socket, roomId) { | ||
* @param {server.Socket} socket - Socket to remove from the room. | ||
* @param {String} [roomId=null] - Id of the room | ||
* @param {String} roomId - Id of the room. | ||
*/ | ||
@@ -171,12 +176,13 @@ removeFromRoom(socket, roomId) { | ||
/** | ||
* Send a string message to all client of given room(s). If no room | ||
* not specified, the message is sent to all clients | ||
* Send a message of JSON compatible data types to all client of given room(s). | ||
* If no room is specified, the message is sent to all clients. | ||
* | ||
* | ||
* @param {String|Array} roomsIds - Ids of the rooms that must receive | ||
* the message. If null the message is sent to all clients | ||
* @param {server.Socket} excludeSocket - Optionnal | ||
* socket to ignore when broadcasting the message, typically the client | ||
* at the origin of the message | ||
* @param {String} channel - Channel of the message | ||
* @param {...*} args - Arguments of the message (as many as needed, of any type) | ||
* the message. If `null` the message is sent to all clients. | ||
* @param {server.Socket} excludeSocket - Optionnal socket to ignore when | ||
* broadcasting the message, typically the client at the origin of the message. | ||
* @param {String} channel - Channel name. | ||
* @param {...*} args - Payload of the message. As many arguments as needed, of | ||
* JSON compatible data types (i.e. string, number, boolean, object, array and null). | ||
*/ | ||
@@ -188,12 +194,11 @@ broadcast(roomIds, excludeSocket, channel, ...args) { | ||
/** | ||
* Send a binary message (TypedArray) to all client of given room(s). If no room | ||
* not specified, the message is sent to all clients | ||
* Send a binary message to all client of given room(s). If no room is specified | ||
* specified, the message is sent to all clients. | ||
* | ||
* @param {String|Array} roomsIds - Ids of the rooms that must receive | ||
* the message. If null the message is sent to all clients | ||
* @param {server.Socket} excludeSocket - Optionnal | ||
* socket to ignore when broadcasting the message, typically the client | ||
* at the origin of the message | ||
* @param {String} channel - Channel of the message | ||
* @param {...*} args - Arguments of the message (as many as needed, of any type) | ||
* the message. If `null` the message is sent to all clients. | ||
* @param {server.Socket} excludeSocket - Optionnal socket to ignore when | ||
* broadcasting the message, typically the client at the origin of the message. | ||
* @param {string} channel - Channel name. | ||
* @param {TypedArray} typedArray - Binary data to be sent. | ||
*/ | ||
@@ -200,0 +205,0 @@ broadcastBinary(roomIds, excludeSocket, channel, typedArray) { |
@@ -30,10 +30,222 @@ import EventEmitter from 'node:events'; | ||
/** | ||
* @typedef {object} server.StateManager~schema | ||
* | ||
* Description of a schema to be registered by the {@link server.StateManager#registerSchema} | ||
* | ||
* A schema is the blueprint, or definition from which shared states can be created. | ||
* | ||
* It consists of a set of key / value pairs where the key is the name of | ||
* the parameter, and the value is an object describing the parameter. | ||
* | ||
* The value can be of any of the foolowing types: | ||
* - {@link server.StateManager~schemaBooleanDefinition} | ||
* - {@link server.StateManager~schemaStringDefinition} | ||
* - {@link server.StateManager~schemaIntegerDefinition} | ||
* - {@link server.StateManager~schemaFloatDefinition} | ||
* - {@link server.StateManager~schemaEnumDefinition} | ||
* - {@link server.StateManager~schemaAnyDefinition} | ||
* | ||
* @example | ||
* const mySchema = { | ||
* triggerSound: { | ||
* type: 'boolean', | ||
* event: true, | ||
* }, | ||
* volume: { | ||
* type: 'float' | ||
* default: 0, | ||
* min: -80, | ||
* max: 6, | ||
* } | ||
* }; | ||
* | ||
* server.stateManager.registerSchema('my-schema-name', mySchema); | ||
*/ | ||
/** | ||
* Describe a {@link server.StateManager~schema} entry of "boolean" type. | ||
* | ||
* @typedef {object} server.StateManager~schemaBooleanDefinition | ||
* @property {string} type='boolean' - Define a boolean parameter. | ||
* @property {boolean} default - Default value of the parameter. | ||
* @property {boolean} [nullable=false] - Define if the parameter is nullable. If | ||
* set to `true` the parameter `default` is set to `null`. | ||
* @property {boolean} [event=false] - Define if the parameter is a volatile, e.g. | ||
* set its value back to `null` after propagation. When `true`, `nullable` is | ||
* automatically set to `true` and `default` to `null`. | ||
* @property {boolean} [filterChange=true] - Setting this option to `false` forces | ||
* the propagation of a parameter even when its value do not change. It | ||
* offers a kind of middle ground between the default bahavior (e.g. where | ||
* only changed values are propagated) and the behavior of the `event` option | ||
* (which has no state per se). As such, setting this options to `false` if | ||
* `event=true` does not make sens. | ||
* @property {boolean} [immediate=false] - Setting this option to `true` will | ||
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the | ||
* state that generate the update (i.e. calling `set`), before propagating the | ||
* change on the network. This option can be usefull in cases the network | ||
* would introduce a noticeable latency on the client. If for some reason | ||
* the value is overriden server-side (e.g. in an `updateHook`) the listeners | ||
* will be called again on when the "real" / final value will be received. | ||
* @property {object} [metas={}] - Optionnal metadata of the parameter. | ||
*/ | ||
/** | ||
* Describe a {@link server.StateManager~schema} entry of "string" type. | ||
* | ||
* @typedef {object} server.StateManager~schemaStringDefinition | ||
* @property {string} type='string' - Define a boolean parameter. | ||
* @property {string} default - Default value of the parameter. | ||
* @property {boolean} [nullable=false] - Define if the parameter is nullable. If | ||
* set to `true` the parameter `default` is set to `null`. | ||
* @property {boolean} [event=false] - Define if the parameter is a volatile, e.g. | ||
* set its value back to `null` after propagation. When `true`, `nullable` is | ||
* automatically set to `true` and `default` to `null`. | ||
* @property {boolean} [filterChange=true] - Setting this option to `false` forces | ||
* the propagation of a parameter even when its value do not change. It | ||
* offers a kind of middle ground between the default bahavior (e.g. where | ||
* only changed values are propagated) and the behavior of the `event` option | ||
* (which has no state per se). As such, setting this options to `false` if | ||
* `event=true` does not make sens. | ||
* @property {boolean} [immediate=false] - Setting this option to `true` will | ||
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the | ||
* state that generate the update (i.e. calling `set`), before propagating the | ||
* change on the network. This option can be usefull in cases the network | ||
* would introduce a noticeable latency on the client. If for some reason | ||
* the value is overriden server-side (e.g. in an `updateHook`) the listeners | ||
* will be called again on when the "real" / final value will be received. | ||
* @property {object} [metas={}] - Optionnal metadata of the parameter. | ||
*/ | ||
/** | ||
* Describe a {@link server.StateManager~schema} entry of "integer" type. | ||
* | ||
* @typedef {object} server.StateManager~schemaIntegerDefinition | ||
* @property {string} type='integer' - Define a boolean parameter. | ||
* @property {number} default - Default value of the parameter. | ||
* @property {number} [min=-Infinity] - Minimum value of the parameter. | ||
* @property {number} [max=+Infinity] - Maximum value of the parameter. | ||
* @property {boolean} [nullable=false] - Define if the parameter is nullable. If | ||
* set to `true` the parameter `default` is set to `null`. | ||
* @property {boolean} [event=false] - Define if the parameter is a volatile, e.g. | ||
* set its value back to `null` after propagation. When `true`, `nullable` is | ||
* automatically set to `true` and `default` to `null`. | ||
* @property {boolean} [filterChange=true] - Setting this option to `false` forces | ||
* the propagation of a parameter even when its value do not change. It | ||
* offers a kind of middle ground between the default bahavior (e.g. where | ||
* only changed values are propagated) and the behavior of the `event` option | ||
* (which has no state per se). As such, setting this options to `false` if | ||
* `event=true` does not make sens. | ||
* @property {boolean} [immediate=false] - Setting this option to `true` will | ||
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the | ||
* state that generate the update (i.e. calling `set`), before propagating the | ||
* change on the network. This option can be usefull in cases the network | ||
* would introduce a noticeable latency on the client. If for some reason | ||
* the value is overriden server-side (e.g. in an `updateHook`) the listeners | ||
* will be called again on when the "real" / final value will be received. | ||
* @property {object} [metas={}] - Optionnal metadata of the parameter. | ||
*/ | ||
/** | ||
* Describe a {@link server.StateManager~schema} entry of "float" type. | ||
* | ||
* @typedef {object} server.StateManager~schemaFloatDefinition | ||
* @property {string} [type='float'] - Float parameter. | ||
* @property {number} default - Default value. | ||
* @property {number} [min=-Infinity] - Minimum value. | ||
* @property {number} [max=+Infinity] - Maximum value. | ||
* @property {boolean} [nullable=false] - Define if the parameter is nullable. If | ||
* set to `true` the parameter `default` is set to `null`. | ||
* @property {boolean} [event=false] - Define if the parameter is a volatile, e.g. | ||
* set its value back to `null` after propagation. When `true`, `nullable` is | ||
* automatically set to `true` and `default` to `null`. | ||
* @property {boolean} [filterChange=true] - Setting this option to `false` forces | ||
* the propagation of a parameter even when its value do not change. It | ||
* offers a kind of middle ground between the default bahavior (e.g. where | ||
* only changed values are propagated) and the behavior of the `event` option | ||
* (which has no state per se). As such, setting this options to `false` if | ||
* `event=true` does not make sens. | ||
* @property {boolean} [immediate=false] - Setting this option to `true` will | ||
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the | ||
* state that generate the update (i.e. calling `set`), before propagating the | ||
* change on the network. This option can be usefull in cases the network | ||
* would introduce a noticeable latency on the client. If for some reason | ||
* the value is overriden server-side (e.g. in an `updateHook`) the listeners | ||
* will be called again on when the "real" / final value will be received. | ||
* @property {object} [metas={}] - Optionnal metadata of the parameter. | ||
*/ | ||
/** | ||
* Describe a {@link server.StateManager~schema} entry of "enum" type. | ||
* | ||
* @typedef {object} server.StateManager~schemaEnumDefinition | ||
* @property {string} [type='enum'] - Enum parameter. | ||
* @property {string} default - Default value of the parameter. | ||
* @property {Array} list - Possible values of the parameter. | ||
* @property {boolean} [nullable=false] - Define if the parameter is nullable. If | ||
* set to `true` the parameter `default` is set to `null`. | ||
* @property {boolean} [event=false] - Define if the parameter is a volatile, e.g. | ||
* set its value back to `null` after propagation. When `true`, `nullable` is | ||
* automatically set to `true` and `default` to `null`. | ||
* @property {boolean} [filterChange=true] - Setting this option to `false` forces | ||
* the propagation of a parameter even when its value do not change. It | ||
* offers a kind of middle ground between the default bahavior (e.g. where | ||
* only changed values are propagated) and the behavior of the `event` option | ||
* (which has no state per se). As such, setting this options to `false` if | ||
* `event=true` does not make sens. | ||
* @property {boolean} [immediate=false] - Setting this option to `true` will | ||
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the | ||
* state that generate the update (i.e. calling `set`), before propagating the | ||
* change on the network. This option can be usefull in cases the network | ||
* would introduce a noticeable latency on the client. If for some reason | ||
* the value is overriden server-side (e.g. in an `updateHook`) the listeners | ||
* will be called again on when the "real" / final value will be received. | ||
* @property {object} [metas={}] - Optionnal metadata of the parameter. | ||
*/ | ||
/** | ||
* Describe a {@link server.StateManager~schema} entry of "any" type. | ||
* | ||
* Note that the `any` type always return a shallow copy of the state internal | ||
* value. Mutating the returned value will therefore not modify the internal state. | ||
* | ||
* @typedef {object} server.StateManager~schemaAnyDefinition | ||
* @property {string} [type='any'] - Parameter of any type. | ||
* @property {*} default - Default value of the parameter. | ||
* @property {boolean} [nullable=false] - Define if the parameter is nullable. If | ||
* set to `true` the parameter `default` is set to `null`. | ||
* @property {boolean} [event=false] - Define if the parameter is a volatile, e.g. | ||
* set its value back to `null` after propagation. When `true`, `nullable` is | ||
* automatically set to `true` and `default` to `null`. | ||
* @property {boolean} [filterChange=true] - Setting this option to `false` forces | ||
* the propagation of a parameter even when its value do not change. It | ||
* offers a kind of middle ground between the default bahavior (e.g. where | ||
* only changed values are propagated) and the behavior of the `event` option | ||
* (which has no state per se). As such, setting this options to `false` if | ||
* `event=true` does not make sens. | ||
* @property {boolean} [immediate=false] - Setting this option to `true` will | ||
* trigger any change (e.g. call the `onUpdate` listeners) immediately on the | ||
* state that generate the update (i.e. calling `set`), before propagating the | ||
* change on the network. This option can be usefull in cases the network | ||
* would introduce a noticeable latency on the client. If for some reason | ||
* the value is overriden server-side (e.g. in an `updateHook`) the listeners | ||
* will be called again on when the "real" / final value will be received. | ||
* @property {object} [metas={}] - Optionnal metadata of the parameter. | ||
*/ | ||
/** | ||
* @callback server.StateManager~ObserveCallback | ||
* @async | ||
* @param {String} schemaName - name of the schema | ||
* @param {Number} stateId - id of the state | ||
* @param {Number} nodeId - id of the node that created the state | ||
* @param {string} schemaName - name of the schema | ||
* @param {number} stateId - id of the state | ||
* @param {number} nodeId - id of the node that created the state | ||
*/ | ||
/** | ||
* @callback server.StateManager~updateHook | ||
* @async | ||
* | ||
* @param {object} updates - Update object as given on a set callback, or | ||
* result of the previous hook | ||
* @param {object} currentValues - Current values of the state. | ||
* @param {object} [context=null] - Optionnal context passed by the creator | ||
* of the update. | ||
* | ||
* @return {object} The "real" updates to be applied on the state. | ||
*/ | ||
/** | ||
* The `StateManager` allows to create new {@link server.SharedState}s, or attach | ||
@@ -118,6 +330,6 @@ * to {@link server.SharedState}s created by other nodes (clients or server). It | ||
* | ||
* @param {Number} nodeId - Id of the client node, as given in | ||
* @param {number} nodeId - Id of the client node, as given in | ||
* {@link client.StateManager} | ||
* @param {Object} transport - Tranpsort mecanism to communicate with the | ||
* client. Should implement a basic EventEmitter API. | ||
* @param {object} transport - Transport mecanism to communicate with the | ||
* client. Must implement a basic EventEmitter API. | ||
* | ||
@@ -242,3 +454,3 @@ * @private | ||
* | ||
* @param {Number} nodeId - Id of the client node, as given in | ||
* @param {number} nodeId - Id of the client node, as given in | ||
* {@link client.StateManager} | ||
@@ -286,3 +498,3 @@ * | ||
* | ||
* @param {String} schemaName - Name of the schema. | ||
* @param {string} schemaName - Name of the schema. | ||
* @param {server.StateManager~schema} schema - Data structure | ||
@@ -322,2 +534,3 @@ * describing the states that will be created from this schema. | ||
* Delete a schema and all associated states. | ||
* | ||
* When a schema is deleted, all states created from this schema are deleted | ||
@@ -327,3 +540,3 @@ * as well, therefore all attached clients are detached and the `onDetach` | ||
* | ||
* @param {String} schemaName - Name of the schema. | ||
* @param {string} schemaName - Name of the schema. | ||
*/ | ||
@@ -354,15 +567,2 @@ deleteSchema(schemaName) { | ||
/** | ||
* @callback server.StateManager~updateHook | ||
* @async | ||
* | ||
* @param {Object} updates - Update object as given on a set callback, or | ||
* result of the previous hook | ||
* @param {Object} currentValues - Current values of the state. | ||
* @param {Object} [context=null] - Optionnal context passed by the creator | ||
* of the update. | ||
* | ||
* @return {Object} The "real" updates to be applied on the state. | ||
*/ | ||
/** | ||
* Register a function for a given schema (e.g. will be applied on all states | ||
@@ -379,3 +579,3 @@ * created from this schema) that will be executed before the update values | ||
* | ||
* @param {String} schemaName - Kind of states on which applying the hook. | ||
* @param {string} schemaName - Kind of states on which applying the hook. | ||
* @param {server.StateManager~updateHook} updateHook - Function | ||
@@ -415,5 +615,4 @@ * called between the `set` call and the actual update. | ||
} | ||
} | ||
export default StateManager; |
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
329961
8546
91