@soundworks/core 3.7.0 → 4.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +16 -0
- package/LICENSE +1 -1
- package/README.md +32 -30
- package/package.json +48 -28
- package/src/client/Client.js +391 -0
- package/src/client/Context.js +225 -0
- package/src/client/ContextManager.js +111 -0
- package/src/client/Plugin.js +66 -0
- package/src/client/PluginManager.js +123 -0
- package/src/client/SharedState.js +84 -0
- package/src/client/Socket.js +389 -0
- package/src/client/StateManager.js +67 -0
- package/src/client/index.js +77 -0
- package/src/common/BasePlugin.js +163 -0
- package/src/common/BasePluginManager.js +227 -0
- package/{common/SharedState.js → src/common/BaseSharedState.js} +198 -234
- package/src/common/BaseStateManager.js +200 -0
- package/{common → src/common}/ParameterBag.js +74 -112
- package/{common → src/common}/SharedStatePrivate.js +56 -62
- package/src/common/constants.js +51 -0
- package/src/common/logger.js +154 -0
- package/src/common/promise-store.js +25 -0
- package/src/common/sockets-utils.js +73 -0
- package/src/common/utils.js +22 -0
- package/src/server/Client.js +50 -0
- package/src/server/Context.js +99 -0
- package/src/server/ContextManager.js +266 -0
- package/src/server/Plugin.js +77 -0
- package/src/server/PluginManager.js +150 -0
- package/src/server/Server.js +849 -0
- package/src/server/SharedState.js +86 -0
- package/{server → src/server}/Socket.js +114 -88
- package/{server → src/server}/Sockets.js +73 -60
- package/{common/SharedStateManagerServer.js → src/server/StateManager.js} +181 -129
- package/src/server/audit-network-latency.worker.js +57 -0
- package/src/server/audit-schema.js +42 -0
- package/src/server/index.js +45 -0
- package/tsconfig.json +21 -0
- package/types/client/Client.d.ts +194 -0
- package/types/client/Client.d.ts.map +1 -0
- package/types/client/Context.d.ts +139 -0
- package/types/client/Context.d.ts.map +1 -0
- package/types/client/ContextManager.d.ts +45 -0
- package/types/client/ContextManager.d.ts.map +1 -0
- package/types/client/Plugin.d.ts +53 -0
- package/types/client/Plugin.d.ts.map +1 -0
- package/types/client/PluginManager.d.ts +96 -0
- package/types/client/PluginManager.d.ts.map +1 -0
- package/types/client/SharedState.d.ts +85 -0
- package/types/client/SharedState.d.ts.map +1 -0
- package/types/client/Socket.d.ts +138 -0
- package/types/client/Socket.d.ts.map +1 -0
- package/types/client/StateManager.d.ts +72 -0
- package/types/client/StateManager.d.ts.map +1 -0
- package/types/client/index.d.ts +11 -0
- package/types/client/index.d.ts.map +1 -0
- package/types/common/BasePlugin.d.ts +145 -0
- package/types/common/BasePlugin.d.ts.map +1 -0
- package/types/common/BasePluginManager.d.ts +88 -0
- package/types/common/BasePluginManager.d.ts.map +1 -0
- package/types/common/BaseSharedState.d.ts +173 -0
- package/types/common/BaseSharedState.d.ts.map +1 -0
- package/types/common/BaseStateManager.d.ts +70 -0
- package/types/common/BaseStateManager.d.ts.map +1 -0
- package/types/common/ParameterBag.d.ts +143 -0
- package/types/common/ParameterBag.d.ts.map +1 -0
- package/types/common/SharedStatePrivate.d.ts +20 -0
- package/types/common/SharedStatePrivate.d.ts.map +1 -0
- package/types/common/constants.d.ts +35 -0
- package/types/common/constants.d.ts.map +1 -0
- package/types/common/logger.d.ts +16 -0
- package/types/common/logger.d.ts.map +1 -0
- package/types/common/promise-store.d.ts +4 -0
- package/types/common/promise-store.d.ts.map +1 -0
- package/types/common/sockets-utils.d.ts +9 -0
- package/types/common/sockets-utils.d.ts.map +1 -0
- package/types/common/utils.d.ts +5 -0
- package/types/common/utils.d.ts.map +1 -0
- package/types/server/Client.d.ts +40 -0
- package/types/server/Client.d.ts.map +1 -0
- package/types/server/Context.d.ts +59 -0
- package/types/server/Context.d.ts.map +1 -0
- package/types/server/ContextManager.d.ts +69 -0
- package/types/server/ContextManager.d.ts.map +1 -0
- package/types/server/Plugin.d.ts +70 -0
- package/types/server/Plugin.d.ts.map +1 -0
- package/types/server/PluginManager.d.ts +91 -0
- package/types/server/PluginManager.d.ts.map +1 -0
- package/types/server/Server.d.ts +223 -0
- package/types/server/Server.d.ts.map +1 -0
- package/types/server/SharedState.d.ts +86 -0
- package/types/server/SharedState.d.ts.map +1 -0
- package/types/server/Socket.d.ts +128 -0
- package/types/server/Socket.d.ts.map +1 -0
- package/types/server/Sockets.d.ts +73 -0
- package/types/server/Sockets.d.ts.map +1 -0
- package/types/server/StateManager.d.ts +190 -0
- package/types/server/StateManager.d.ts.map +1 -0
- package/types/server/audit-network-latency.worker.d.ts +2 -0
- package/types/server/audit-network-latency.worker.d.ts.map +1 -0
- package/types/server/audit-schema.d.ts +27 -0
- package/types/server/audit-schema.d.ts.map +1 -0
- package/types/server/index.d.ts +7 -0
- package/types/server/index.d.ts.map +1 -0
- package/client/AbstractExperience.js +0 -54
- package/client/AbstractPlugin.js +0 -230
- package/client/Client.js +0 -193
- package/client/PluginManager.js +0 -279
- package/client/Socket.js +0 -346
- package/client/index.js +0 -48
- package/client.js +0 -28
- package/common/SharedStateManagerClient.js +0 -194
- package/common/Signal.js +0 -40
- package/common/SignalAll.js +0 -44
- package/common/logger.js +0 -162
- package/common/shared-state-utils.js +0 -89
- package/common/sockets-encoder-decoder.js +0 -73
- package/server/AbstractExperience.js +0 -126
- package/server/AbstractPlugin.js +0 -214
- package/server/Client.js +0 -70
- package/server/PluginManager.js +0 -273
- package/server/Server.js +0 -785
- package/server/index.js +0 -49
- package/server.js +0 -28
package/.eslintrc
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"@ircam",
|
|
4
|
+
"plugin:jsdoc/recommended"
|
|
5
|
+
],
|
|
6
|
+
"settings": {
|
|
7
|
+
"jsdoc": {
|
|
8
|
+
"tagNamePreference": {
|
|
9
|
+
"augments": {
|
|
10
|
+
"message": "@extends is to be used over @augments as it is more evocative of classes than @augments",
|
|
11
|
+
"replacement": "extends"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,58 +1,60 @@
|
|
|
1
1
|
# `soundworks`
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/js/@soundworks%2Fcore)
|
|
4
|
+
|
|
3
5
|

|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Open-source creative coding framework for distributed applications based on Web technologies.
|
|
8
|
+
|
|
9
|
+
Primarily focused on music, `soundworks` aims at supporting rapid development of real-time distributed applications using _JavaScript_. It provides abstractions to hide the complexity of the network and to foster very rapid-prototyping and trial-and-error workflows that are typical in artistic practices.
|
|
10
|
+
|
|
11
|
+
*__WARNING: The version 4 of `@soundworks/core` is under heavy development.__*
|
|
6
12
|
|
|
7
|
-
##
|
|
13
|
+
## Getting Started
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
The best and most simple way to start using `soundworks` is to use the `@soundworks/create` wizard.
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- [Installation](#installation)
|
|
15
|
-
- [Application Template](#application-template)
|
|
16
|
-
- [Academic Papers](#academic-papers)
|
|
17
|
-
- [Credits](#credits)
|
|
18
|
-
- [License](#license)
|
|
17
|
+
```sh
|
|
18
|
+
npx @soundworks/create@latest
|
|
19
|
+
```
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
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`.
|
|
21
22
|
|
|
23
|
+
<!--
|
|
22
24
|
## Documentation
|
|
23
25
|
|
|
24
|
-
[https://
|
|
26
|
+
- Guides and Tutorials: [https://soundworks.dev](https://soundworks.dev)
|
|
27
|
+
- API: [https://soundworks.dev/api](https://soundworks.dev/api)
|
|
28
|
+
-->
|
|
25
29
|
|
|
26
30
|
## API
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
## Overview
|
|
32
|
+
The API is not publicly published for now. To access the API documentation locally, just clone this repository, go to the v4 branch and launch some http server in the docs directory. For example, using the [serve](https://www.npmjs.com/package/serve) package:
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
```sh
|
|
35
|
+
git clone https://github.com/collective-soundworks/soundworks.git
|
|
36
|
+
cd soundworks
|
|
37
|
+
git checkout v4
|
|
38
|
+
serve docs
|
|
39
|
+
```
|
|
33
40
|
|
|
34
|
-
|
|
41
|
+
## TypeScript Support
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
- Http(s) server and basic routing
|
|
38
|
-
- WebSockets initialization
|
|
39
|
-
- Processes initialization
|
|
40
|
-
- Distributed state management
|
|
43
|
+
Basic TypeScript support will be proposed in a (hopefully) near future.
|
|
41
44
|
|
|
42
|
-
|
|
45
|
+
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.
|
|
43
46
|
|
|
44
|
-
##
|
|
47
|
+
## Install
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
Note that the `@soundworks/core` package is automatically installed when creating an application using the `@soundworks/create` wizard, so most of the time you should not have to install this package manually. See [https://soundworks.dev/guides/getting-started.html](https://soundworks.dev/guides/getting-started.html) for more informations on the `soundworks` wizard.
|
|
47
50
|
|
|
48
51
|
```
|
|
49
52
|
npm install @soundworks/core
|
|
50
53
|
```
|
|
51
54
|
|
|
52
|
-
##
|
|
55
|
+
## Share with Us
|
|
53
56
|
|
|
54
|
-
|
|
55
|
-
[https://github.com/collective-soundworks/soundworks-template](https://github.com/collective-soundworks/soundworks-template).
|
|
57
|
+
If you made an application using `soundworks` please let us know here: https://github.com/collective-soundworks/soundworks/discussions/61
|
|
56
58
|
|
|
57
59
|
## Academic Papers
|
|
58
60
|
|
|
@@ -67,7 +69,7 @@ The simplest way to start a new `soundworks` application is using the applicatio
|
|
|
67
69
|
|
|
68
70
|
Futher developments has been supported in the framework of:
|
|
69
71
|
- The [RAPID-MIX project](http://rapidmix.goldsmithsdigital.com/), funded by the European Union’s Horizon 2020 research and innovation program
|
|
70
|
-
- The Ircam
|
|
72
|
+
- The Ircam projects _BeCoMe_ and _SO(a)P_
|
|
71
73
|
- The _Constella(c)tions_ residency of the STARTS program of the European Commission.
|
|
72
74
|
|
|
73
75
|
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.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soundworks/core",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.0.0-alpha.1",
|
|
4
|
+
"description": "Open-source creative coding framework for distributed applications based on Web technologies",
|
|
5
5
|
"authors": [
|
|
6
6
|
"Benjamin Matuszewski",
|
|
7
7
|
"Sébastien Robaszkiewicz",
|
|
@@ -11,64 +11,84 @@
|
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
"./client.js": {
|
|
17
|
+
"module": "./src/client/index.js",
|
|
18
|
+
"node": "./src/client/index.js",
|
|
19
|
+
"types": "./types/client/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./server.js": {
|
|
22
|
+
"node": "./src/server/index.js",
|
|
23
|
+
"types": "./types/server/index.d.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
14
26
|
"repository": {
|
|
15
27
|
"type": "git",
|
|
16
|
-
"url": "https://github.com/collective-soundworks/soundworks"
|
|
28
|
+
"url": "git+https://github.com/collective-soundworks/soundworks.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/collective-soundworks/soundworks/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://soundworks.dev",
|
|
34
|
+
"keywords": [
|
|
35
|
+
"soundworks",
|
|
36
|
+
"creative-coding",
|
|
37
|
+
"music",
|
|
38
|
+
"full-stack",
|
|
39
|
+
"real-time",
|
|
40
|
+
"distributed-system"
|
|
41
|
+
],
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=16.0.0"
|
|
17
44
|
},
|
|
18
|
-
"main": "",
|
|
19
45
|
"scripts": {
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"prepublishOnly": "npm run build",
|
|
23
|
-
"toc": "markdown-toc -i README.md",
|
|
24
|
-
"version": "npm run toc && npm run doc && git add docs",
|
|
25
|
-
"build:client": "babel src/client --out-dir client",
|
|
26
|
-
"build:server": "babel src/server --out-dir server",
|
|
27
|
-
"build:common": "babel src/common --out-dir common",
|
|
28
|
-
"build": "npm run clean && npm run build:server && npm run build:client && npm run build:common",
|
|
29
|
-
"dev:client": "chokidar src/client/ -c \"npm run build:client\"",
|
|
30
|
-
"dev:server": "chokidar src/server/ -c \"npm run build:server\"",
|
|
31
|
-
"dev:common": "chokidar src/common/ -c \"npm run build:common\"",
|
|
32
|
-
"dev": "npm run build && (npm run dev:client & npm run dev:server & npm run dev:common)",
|
|
46
|
+
"doc": "npm run jsdoc && npm run types",
|
|
47
|
+
"jsdoc": "rm -Rf docs && jsdoc -c .jsdoc.json --verbose && cp -R assets docs/",
|
|
33
48
|
"lint": "npx eslint src",
|
|
34
|
-
"test": "mocha"
|
|
49
|
+
"test": "mocha",
|
|
50
|
+
"toc": "markdown-toc -i README.md",
|
|
51
|
+
"types": "rm -Rf types && tsc",
|
|
52
|
+
"version": "npm run toc && npm run doc && git add docs"
|
|
35
53
|
},
|
|
36
54
|
"dependencies": {
|
|
55
|
+
"@ircam/sc-gettime": "^1.0.0",
|
|
37
56
|
"chalk": "^4.1.2",
|
|
38
57
|
"columnify": "^1.6.0",
|
|
39
58
|
"compression": "^1.7.1",
|
|
40
|
-
"
|
|
59
|
+
"express": "^4.18.1",
|
|
41
60
|
"fast-deep-equal": "^3.1.3",
|
|
42
61
|
"fast-text-encoding": "^1.0.0",
|
|
62
|
+
"is-plain-obj": "3.0.0",
|
|
43
63
|
"isomorphic-ws": "^4.0.1",
|
|
44
64
|
"keyv": "^4.2.2",
|
|
45
65
|
"keyv-file": "^0.2.0",
|
|
46
66
|
"lodash.clonedeep": "^4.5.0",
|
|
47
67
|
"lodash.merge": "^4.6.2",
|
|
48
68
|
"pem": "^1.14.6",
|
|
49
|
-
"
|
|
69
|
+
"serve-static": "^1.15.0",
|
|
70
|
+
"template-literal": "^1.0.4",
|
|
50
71
|
"uuid": "^8.3.2",
|
|
51
72
|
"window-or-global": "^1.0.1",
|
|
52
|
-
"ws": "^8.
|
|
73
|
+
"ws": "^8.11.0"
|
|
53
74
|
},
|
|
54
75
|
"devDependencies": {
|
|
55
|
-
"@
|
|
56
|
-
"@babel/core": "^7.17.9",
|
|
57
|
-
"@babel/preset-env": "^7.16.11",
|
|
58
|
-
"@soundworks/plugin-delay": "^1.0.0",
|
|
76
|
+
"@ircam/eslint-config": "^1.0.0",
|
|
59
77
|
"chai": "^4.3.6",
|
|
60
78
|
"chai-shallow-deep-equal": "^1.4.6",
|
|
61
|
-
"chokidar": "^3.5.3",
|
|
62
|
-
"chokidar-cli": "^3.0.0",
|
|
63
79
|
"commitizen": "^4.2.5",
|
|
64
80
|
"cz-conventional-changelog": "^3.3.0",
|
|
65
81
|
"docdash": "^1.2.0",
|
|
66
82
|
"dotenv": "^16.0.3",
|
|
67
83
|
"eslint": "^8.24.0",
|
|
84
|
+
"eslint-plugin-jsdoc": "^39.6.4",
|
|
85
|
+
"generate-changelog": "^1.8.0",
|
|
68
86
|
"jsdoc": "^3.6.10",
|
|
69
87
|
"markdown-toc": "^1.2.0",
|
|
70
88
|
"mocha": "^9.2.2",
|
|
71
|
-
"puppeteer": "^18.0.5"
|
|
89
|
+
"puppeteer": "^18.0.5",
|
|
90
|
+
"tcp-ping": "^0.1.1",
|
|
91
|
+
"typescript": "^4.9.3"
|
|
72
92
|
},
|
|
73
93
|
"optionalDependencies": {
|
|
74
94
|
"bufferutil": "^4.0.5",
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import isPlainObject from 'is-plain-obj';
|
|
2
|
+
|
|
3
|
+
import ContextManager from './ContextManager.js';
|
|
4
|
+
import PluginManager from './PluginManager.js';
|
|
5
|
+
import Socket from './Socket.js';
|
|
6
|
+
import StateManager from './StateManager.js';
|
|
7
|
+
import {
|
|
8
|
+
CLIENT_HANDSHAKE_REQUEST,
|
|
9
|
+
CLIENT_HANDSHAKE_RESPONSE,
|
|
10
|
+
CLIENT_HANDSHAKE_ERROR,
|
|
11
|
+
AUDIT_STATE_NAME,
|
|
12
|
+
} from '../common/constants.js';
|
|
13
|
+
import { isBrowser } from '../common/utils.js';
|
|
14
|
+
import logger from '../common/logger.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The `Client` class is the main entry point for the client-side of soundworks
|
|
18
|
+
* application.
|
|
19
|
+
*
|
|
20
|
+
* A `Client` instance allows to access soundworks components such as {@link client.StateManager},
|
|
21
|
+
* {@link client.PluginManager},{@link client.Socket} or {@link client.ContextManager}.
|
|
22
|
+
* Its is also responsible for handling the initialization lifecycles of the different
|
|
23
|
+
* soundworks components.
|
|
24
|
+
*
|
|
25
|
+
* ```
|
|
26
|
+
* import { Client } from '@soundworks/core/client.js';
|
|
27
|
+
* // create a new soundworks `Client` instance
|
|
28
|
+
* const client = new Client({ role: 'player' });
|
|
29
|
+
* // init and start the client
|
|
30
|
+
* await client.start();
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @memberof client
|
|
34
|
+
*/
|
|
35
|
+
class Client {
|
|
36
|
+
/**
|
|
37
|
+
* @param {client.BrowserClientConfig|client.NodeClientConfig} config -
|
|
38
|
+
* Configuration of the soundworks client.
|
|
39
|
+
* @throws Will throw if the given config object is invalid.
|
|
40
|
+
*/
|
|
41
|
+
constructor(config) {
|
|
42
|
+
if (!isPlainObject(config)) {
|
|
43
|
+
throw new Error(`[soundworks:Client] Invalid argument for Client constructor, config should be an object`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!('role' in config)) {
|
|
47
|
+
throw new Error('[soundworks:Client] Invalid config object, "config.role" should be defined');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// for node clients env.https is requires to open the websocket
|
|
51
|
+
if (!isBrowser()) {
|
|
52
|
+
if (!('env' in config)) {
|
|
53
|
+
throw new Error('[soundworks:Client] Invalid config object, "config.env" { useHttps, serverAddress, port } should be defined');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let missing = [];
|
|
57
|
+
|
|
58
|
+
if (!('useHttps' in config.env)) {
|
|
59
|
+
missing.push('useHttps');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!('serverAddress' in config.env)) {
|
|
63
|
+
missing.push('serverAddress');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!('port' in config.env)) {
|
|
67
|
+
missing.push('port');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (missing.length) {
|
|
71
|
+
throw new Error(`[soundworks:Client] Invalid config object, "config.env" is missing: ${missing.join(', ')}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Role of the client in the application.
|
|
77
|
+
*
|
|
78
|
+
* @type {string}
|
|
79
|
+
*/
|
|
80
|
+
this.role = config.role;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Configuration object.
|
|
84
|
+
*
|
|
85
|
+
* @type {client.BrowserClientConfig|client.NodeClientConfig}
|
|
86
|
+
*/
|
|
87
|
+
this.config = config;
|
|
88
|
+
|
|
89
|
+
if (!this.config.env) {
|
|
90
|
+
this.config.env = {};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// minimal configuration for websockets
|
|
94
|
+
this.config.env.websockets = Object.assign({
|
|
95
|
+
path: 'socket',
|
|
96
|
+
pingInterval: 5000,
|
|
97
|
+
}, config.env.websockets);
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Session id of the client (incremeted positive number), generated and
|
|
101
|
+
* retrieved by the server during `client.init`. The counter is reset when
|
|
102
|
+
* the server restarts.
|
|
103
|
+
*
|
|
104
|
+
* @type {number}
|
|
105
|
+
*/
|
|
106
|
+
this.id = null;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Unique session uuid of the client (uuidv4), generated and retrieved by
|
|
110
|
+
* the server during {@link client.Client#init}.
|
|
111
|
+
*
|
|
112
|
+
* @type {string}
|
|
113
|
+
*/
|
|
114
|
+
this.uuid = null;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Instance of the {@link client.Socket} class that handle websockets communications with
|
|
118
|
+
* the server.
|
|
119
|
+
*
|
|
120
|
+
* @see {@link client.Socket}
|
|
121
|
+
* @type {client.Socket}
|
|
122
|
+
*/
|
|
123
|
+
this.socket = new Socket();
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Runtime platform on which the client is executed, i.e. 'browser' or 'node'.
|
|
127
|
+
*
|
|
128
|
+
* @type {string}
|
|
129
|
+
*/
|
|
130
|
+
this.target = isBrowser() ? 'browser' : 'node';
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Instance of the {@link client.ContextManager} class.
|
|
134
|
+
*
|
|
135
|
+
* The context manager can be safely used after `client.init()` has been fulfilled.
|
|
136
|
+
*
|
|
137
|
+
* @see {@link client.ContextManager}
|
|
138
|
+
* @type {client.ContextManager}
|
|
139
|
+
*/
|
|
140
|
+
this.contextManager = new ContextManager();
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Instance of the {@link client.PluginManager} class.
|
|
144
|
+
*
|
|
145
|
+
* The context manager can be safely used after `client.init()` has been fulfilled.
|
|
146
|
+
*
|
|
147
|
+
* @see {@link client.PluginManager}
|
|
148
|
+
* @type {client.PluginManager}
|
|
149
|
+
*/
|
|
150
|
+
this.pluginManager = new PluginManager(this);
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Instance of the {@link client.StateManager} class.
|
|
154
|
+
*
|
|
155
|
+
* The context manager can be safely used after `client.init()` has been fulfilled.
|
|
156
|
+
*
|
|
157
|
+
* @see {@link client.StateManager}
|
|
158
|
+
* @type {client.StateManager}
|
|
159
|
+
*/
|
|
160
|
+
this.stateManager = null;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Status of the client, 'idle', 'inited', 'started' or 'errored'.
|
|
164
|
+
*
|
|
165
|
+
* @type {string}
|
|
166
|
+
*/
|
|
167
|
+
this.status = 'idle';
|
|
168
|
+
|
|
169
|
+
/** @private */
|
|
170
|
+
this._onStatusChangeCallbacks = new Set();
|
|
171
|
+
/** @private */
|
|
172
|
+
this._auditState = null;
|
|
173
|
+
|
|
174
|
+
logger.configure(!!config.env.verbose);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The `init` method is part of the initialization lifecycle of the `soundworks`
|
|
179
|
+
* client. Most of the time, the `init` method will be implicitly called by the
|
|
180
|
+
* {@link client.Client#start} method.
|
|
181
|
+
*
|
|
182
|
+
* In some situations you might want to call this method manually, in such cases
|
|
183
|
+
* the method should be called before the {@link client.Client#start} method.
|
|
184
|
+
*
|
|
185
|
+
* What it does:
|
|
186
|
+
* - connect the sockets to be server
|
|
187
|
+
* - perform the handshake with soundworks server (retrieve id, etc.)
|
|
188
|
+
* - launch the state manager
|
|
189
|
+
* - initialize all registered plugin
|
|
190
|
+
*
|
|
191
|
+
* After `await client.init()` is fulfilled, the {@link client.Client#stateManager},
|
|
192
|
+
* the {@link client.Client#pluginManager} and the {@link client.Client#socket}
|
|
193
|
+
* can be safely used.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* import { Client } from '@soundworks/core/client.js'
|
|
197
|
+
*
|
|
198
|
+
* const client = new Client(config);
|
|
199
|
+
* // optionnal explicit call of `init` before `start`
|
|
200
|
+
* await client.init();
|
|
201
|
+
* await client.start();
|
|
202
|
+
*/
|
|
203
|
+
async init() {
|
|
204
|
+
// init socket communications (string and binary)
|
|
205
|
+
await this.socket.init(this.role, this.config);
|
|
206
|
+
|
|
207
|
+
// we need the try/catch block to change the promise rejection into proper error
|
|
208
|
+
try {
|
|
209
|
+
await new Promise((resolve, reject) => {
|
|
210
|
+
// wait for handshake response before starting stateManager and pluginManager
|
|
211
|
+
this.socket.addListener(CLIENT_HANDSHAKE_RESPONSE, async ({ id, uuid }) => {
|
|
212
|
+
this.id = id;
|
|
213
|
+
this.uuid = uuid;
|
|
214
|
+
|
|
215
|
+
resolve();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
this.socket.addListener(CLIENT_HANDSHAKE_ERROR, (err) => {
|
|
219
|
+
let msg = ``;
|
|
220
|
+
|
|
221
|
+
switch(err.type) {
|
|
222
|
+
case 'invalid-client-type':
|
|
223
|
+
msg = `[soundworks:Client] ${err.message}`;
|
|
224
|
+
break;
|
|
225
|
+
case 'invalid-plugin-list':
|
|
226
|
+
msg = `[soundworks:Client] ${err.message}`;
|
|
227
|
+
break;
|
|
228
|
+
default:
|
|
229
|
+
msg = `Undefined error`;
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
this.socket.terminate();
|
|
234
|
+
reject(msg);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// send handshake request
|
|
238
|
+
const payload = {
|
|
239
|
+
role: this.role,
|
|
240
|
+
registeredPlugins: this.pluginManager.getRegisteredPlugins(),
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
this.socket.send(CLIENT_HANDSHAKE_REQUEST, payload);
|
|
244
|
+
});
|
|
245
|
+
} catch(err) {
|
|
246
|
+
throw new Error(err);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ------------------------------------------------------------
|
|
250
|
+
// CREATE STATE MANAGER
|
|
251
|
+
// ------------------------------------------------------------
|
|
252
|
+
this.stateManager = new StateManager(this.id, {
|
|
253
|
+
emit: this.socket.send.bind(this.socket), // need to alias this
|
|
254
|
+
addListener: this.socket.addListener.bind(this.socket),
|
|
255
|
+
removeAllListeners: this.socket.removeAllListeners.bind(this.socket),
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// ------------------------------------------------------------
|
|
259
|
+
// INIT PLUGIN MANAGER
|
|
260
|
+
// ------------------------------------------------------------
|
|
261
|
+
await this.pluginManager.start();
|
|
262
|
+
|
|
263
|
+
await this._dispatchStatus('inited');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* The `start` method is part of the initialization lifecycle of the `soundworks`
|
|
268
|
+
* client. The `start` method will implicitly call the {@link client.Client#init}
|
|
269
|
+
* method if it has not been called manually.
|
|
270
|
+
*
|
|
271
|
+
* What it does:
|
|
272
|
+
* - optionnaly call {@link client.Client#init}
|
|
273
|
+
* - starts all the already created contexts (see {@link client.Context})
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* import { Client } from '@soundworks/core/client.js'
|
|
277
|
+
*
|
|
278
|
+
* const client = new Client(config);
|
|
279
|
+
* await client.start();
|
|
280
|
+
*/
|
|
281
|
+
async start() {
|
|
282
|
+
if (this.status === 'idle') {
|
|
283
|
+
await this.init();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (this.status === 'started') {
|
|
287
|
+
throw new Error(`[soundworks:Server] Cannot call "client.start()" twice`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (this.status !== 'inited') {
|
|
291
|
+
throw new Error(`[soundworks:Server] Cannot "client.start()" before "client.init()"`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ------------------------------------------------------------
|
|
295
|
+
// START CONTEXT MANAGER
|
|
296
|
+
// ------------------------------------------------------------
|
|
297
|
+
await this.contextManager.start();
|
|
298
|
+
|
|
299
|
+
await this._dispatchStatus('started');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Stops all started contexts, plugins and terminates the socket connections.
|
|
304
|
+
*
|
|
305
|
+
* In most situations, you might not need to call this method. However, it can
|
|
306
|
+
* be usefull for unit testing or similar situations where you want to create
|
|
307
|
+
* and delete several clients in the same process.
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* import { Client } from '@soundworks/core/client.js'
|
|
311
|
+
*
|
|
312
|
+
* const client = new Client(config);
|
|
313
|
+
* await client.start();
|
|
314
|
+
*
|
|
315
|
+
* await new Promise(resolve => setTimeout(resolve, 1000));
|
|
316
|
+
* await client.stop();
|
|
317
|
+
*/
|
|
318
|
+
async stop() {
|
|
319
|
+
if (this.status !== 'started') {
|
|
320
|
+
throw new Error(`[soundworks:Client] Cannot "client.stop()" before "client.start()"`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
await this.contextManager.stop();
|
|
324
|
+
await this.pluginManager.stop();
|
|
325
|
+
await this.socket.terminate();
|
|
326
|
+
|
|
327
|
+
await this._dispatchStatus('stopped');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Attach and retrieve the global audit state of the application.
|
|
332
|
+
*
|
|
333
|
+
* The audit state is a {@link client.SharedState} instance that keeps track of
|
|
334
|
+
* global informations about the application such as, the number of connected
|
|
335
|
+
* clients, network latency estimation, etc. It is usefull for controller client
|
|
336
|
+
* roles to give the user an overview about the state of the application.
|
|
337
|
+
*
|
|
338
|
+
* The audit state is lazily attached to the client only if this method is called.
|
|
339
|
+
*
|
|
340
|
+
* @returns {Promise<client.SharedState>}
|
|
341
|
+
* @throws Will throw if called before `client.init()`
|
|
342
|
+
* @see {@link client.SharedState}
|
|
343
|
+
* @example
|
|
344
|
+
* const auditState = await client.getAuditState();
|
|
345
|
+
* auditState.onUpdate(() => console.log(auditState.getValues()), true);
|
|
346
|
+
*/
|
|
347
|
+
async getAuditState() {
|
|
348
|
+
if (this.status === 'idle') {
|
|
349
|
+
throw new Error(`[soundworks.Client] Cannot access audit state before "client.init()"`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (this._auditState === null) {
|
|
353
|
+
this._auditState = await this.stateManager.attach(AUDIT_STATE_NAME);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return this._auditState;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Listen for the status change ('inited', 'started', 'stopped') of the client.
|
|
361
|
+
*
|
|
362
|
+
* @param {Function} callback - Listener to the status change.
|
|
363
|
+
* @returns {Function} Delete the listener.
|
|
364
|
+
*/
|
|
365
|
+
onStatusChange(callback) {
|
|
366
|
+
this._onStatusChangeCallbacks.add(callback);
|
|
367
|
+
|
|
368
|
+
return () => this._onStatusChangeCallbacks.delete(callback);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** @private */
|
|
372
|
+
async _dispatchStatus(status) {
|
|
373
|
+
this.status = status;
|
|
374
|
+
|
|
375
|
+
// if node target and launched in a child process, forward status to parent process
|
|
376
|
+
if (this.target === 'node' && process.send !== undefined) {
|
|
377
|
+
process.send(`soundworks:client:${status}`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// execute all callbacks in parallel
|
|
381
|
+
const promises = [];
|
|
382
|
+
|
|
383
|
+
for (let callback of this._onStatusChangeCallbacks) {
|
|
384
|
+
promises.push(callback(status));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
await Promise.all(promises);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export default Client;
|