@soundworks/core 3.7.0 → 4.0.0-alpha.0

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.
Files changed (124) hide show
  1. package/.eslintrc +16 -0
  2. package/LICENSE +1 -1
  3. package/README.md +32 -30
  4. package/package.json +48 -28
  5. package/src/client/Client.js +391 -0
  6. package/src/client/Context.js +225 -0
  7. package/src/client/ContextManager.js +111 -0
  8. package/src/client/Plugin.js +66 -0
  9. package/src/client/PluginManager.js +123 -0
  10. package/src/client/SharedState.js +84 -0
  11. package/src/client/Socket.js +389 -0
  12. package/src/client/StateManager.js +67 -0
  13. package/src/client/index.js +77 -0
  14. package/src/common/BasePlugin.js +163 -0
  15. package/src/common/BasePluginManager.js +227 -0
  16. package/{common/SharedState.js → src/common/BaseSharedState.js} +198 -234
  17. package/src/common/BaseStateManager.js +200 -0
  18. package/{common → src/common}/ParameterBag.js +74 -112
  19. package/{common → src/common}/SharedStatePrivate.js +56 -62
  20. package/src/common/constants.js +51 -0
  21. package/src/common/logger.js +154 -0
  22. package/src/common/promise-store.js +25 -0
  23. package/src/common/sockets-utils.js +73 -0
  24. package/src/common/utils.js +22 -0
  25. package/src/server/Client.js +50 -0
  26. package/src/server/Context.js +99 -0
  27. package/src/server/ContextManager.js +266 -0
  28. package/src/server/Plugin.js +77 -0
  29. package/src/server/PluginManager.js +150 -0
  30. package/src/server/Server.js +849 -0
  31. package/src/server/SharedState.js +86 -0
  32. package/{server → src/server}/Socket.js +114 -88
  33. package/{server → src/server}/Sockets.js +73 -60
  34. package/{common/SharedStateManagerServer.js → src/server/StateManager.js} +181 -129
  35. package/src/server/audit-network-latency.worker.js +57 -0
  36. package/src/server/audit-schema.js +42 -0
  37. package/src/server/index.js +45 -0
  38. package/tsconfig.json +21 -0
  39. package/types/client/Client.d.ts +194 -0
  40. package/types/client/Client.d.ts.map +1 -0
  41. package/types/client/Context.d.ts +139 -0
  42. package/types/client/Context.d.ts.map +1 -0
  43. package/types/client/ContextManager.d.ts +45 -0
  44. package/types/client/ContextManager.d.ts.map +1 -0
  45. package/types/client/Plugin.d.ts +53 -0
  46. package/types/client/Plugin.d.ts.map +1 -0
  47. package/types/client/PluginManager.d.ts +96 -0
  48. package/types/client/PluginManager.d.ts.map +1 -0
  49. package/types/client/SharedState.d.ts +85 -0
  50. package/types/client/SharedState.d.ts.map +1 -0
  51. package/types/client/Socket.d.ts +138 -0
  52. package/types/client/Socket.d.ts.map +1 -0
  53. package/types/client/StateManager.d.ts +72 -0
  54. package/types/client/StateManager.d.ts.map +1 -0
  55. package/types/client/index.d.ts +11 -0
  56. package/types/client/index.d.ts.map +1 -0
  57. package/types/common/BasePlugin.d.ts +145 -0
  58. package/types/common/BasePlugin.d.ts.map +1 -0
  59. package/types/common/BasePluginManager.d.ts +88 -0
  60. package/types/common/BasePluginManager.d.ts.map +1 -0
  61. package/types/common/BaseSharedState.d.ts +173 -0
  62. package/types/common/BaseSharedState.d.ts.map +1 -0
  63. package/types/common/BaseStateManager.d.ts +70 -0
  64. package/types/common/BaseStateManager.d.ts.map +1 -0
  65. package/types/common/ParameterBag.d.ts +143 -0
  66. package/types/common/ParameterBag.d.ts.map +1 -0
  67. package/types/common/SharedStatePrivate.d.ts +20 -0
  68. package/types/common/SharedStatePrivate.d.ts.map +1 -0
  69. package/types/common/constants.d.ts +35 -0
  70. package/types/common/constants.d.ts.map +1 -0
  71. package/types/common/logger.d.ts +16 -0
  72. package/types/common/logger.d.ts.map +1 -0
  73. package/types/common/promise-store.d.ts +4 -0
  74. package/types/common/promise-store.d.ts.map +1 -0
  75. package/types/common/sockets-utils.d.ts +9 -0
  76. package/types/common/sockets-utils.d.ts.map +1 -0
  77. package/types/common/utils.d.ts +5 -0
  78. package/types/common/utils.d.ts.map +1 -0
  79. package/types/server/Client.d.ts +40 -0
  80. package/types/server/Client.d.ts.map +1 -0
  81. package/types/server/Context.d.ts +59 -0
  82. package/types/server/Context.d.ts.map +1 -0
  83. package/types/server/ContextManager.d.ts +69 -0
  84. package/types/server/ContextManager.d.ts.map +1 -0
  85. package/types/server/Plugin.d.ts +70 -0
  86. package/types/server/Plugin.d.ts.map +1 -0
  87. package/types/server/PluginManager.d.ts +91 -0
  88. package/types/server/PluginManager.d.ts.map +1 -0
  89. package/types/server/Server.d.ts +223 -0
  90. package/types/server/Server.d.ts.map +1 -0
  91. package/types/server/SharedState.d.ts +86 -0
  92. package/types/server/SharedState.d.ts.map +1 -0
  93. package/types/server/Socket.d.ts +128 -0
  94. package/types/server/Socket.d.ts.map +1 -0
  95. package/types/server/Sockets.d.ts +73 -0
  96. package/types/server/Sockets.d.ts.map +1 -0
  97. package/types/server/StateManager.d.ts +190 -0
  98. package/types/server/StateManager.d.ts.map +1 -0
  99. package/types/server/audit-network-latency.worker.d.ts +2 -0
  100. package/types/server/audit-network-latency.worker.d.ts.map +1 -0
  101. package/types/server/audit-schema.d.ts +27 -0
  102. package/types/server/audit-schema.d.ts.map +1 -0
  103. package/types/server/index.d.ts +7 -0
  104. package/types/server/index.d.ts.map +1 -0
  105. package/client/AbstractExperience.js +0 -54
  106. package/client/AbstractPlugin.js +0 -230
  107. package/client/Client.js +0 -193
  108. package/client/PluginManager.js +0 -279
  109. package/client/Socket.js +0 -346
  110. package/client/index.js +0 -48
  111. package/client.js +0 -28
  112. package/common/SharedStateManagerClient.js +0 -194
  113. package/common/Signal.js +0 -40
  114. package/common/SignalAll.js +0 -44
  115. package/common/logger.js +0 -162
  116. package/common/shared-state-utils.js +0 -89
  117. package/common/sockets-encoder-decoder.js +0 -73
  118. package/server/AbstractExperience.js +0 -126
  119. package/server/AbstractPlugin.js +0 -214
  120. package/server/Client.js +0 -70
  121. package/server/PluginManager.js +0 -273
  122. package/server/Server.js +0 -785
  123. package/server/index.js +0 -49
  124. 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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2020 IRCAM – Centre Pompidou (France, Paris)
1
+ Copyright (c) 2014-present IRCAM – Centre Pompidou (France, Paris)
2
2
 
3
3
  All rights reserved.
4
4
 
package/README.md CHANGED
@@ -1,58 +1,60 @@
1
1
  # `soundworks`
2
2
 
3
+ [![npm version](https://badge.fury.io/js/@soundworks%2Fcore.svg)](https://badge.fury.io/js/@soundworks%2Fcore)
4
+
3
5
  ![soundworks-logo](./assets/logo-200x200.png)
4
6
 
5
- > Full-stack JavaScript framework for distributed WebAudio and multimedia applications.
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
- ## Table of Contents
13
+ ## Getting Started
8
14
 
9
- <!-- toc -->
15
+ The best and most simple way to start using `soundworks` is to use the `@soundworks/create` wizard.
10
16
 
11
- - [Documentation](#documentation)
12
- - [API](#api)
13
- - [Overview](#overview)
14
- - [Installation](#installation)
15
- - [Application Template](#application-template)
16
- - [Academic Papers](#academic-papers)
17
- - [Credits](#credits)
18
- - [License](#license)
17
+ ```sh
18
+ npx init @soundworks
19
+ ```
19
20
 
20
- <!-- tocstop -->
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://collective-soundworks.github.io](https://collective-soundworks.github.io)
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
- [http://collective-soundworks.github.io/soundworks/](http://collective-soundworks.github.io/soundworks/)
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
- `soundworks` follows a client / server architecture where the server is written using [Node.js](https://nodejs.org/) and clients can be either regular browser clients or Node.js clients running for example on a Raspberry Pi.
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
- ![high-level-architecture](./assets/high-level-architecture.png)
41
+ ## TypeScript Support
35
42
 
36
- The core of the framework is very minimal and dedicated at handling:
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
- `soundworks` can be extended with plugins to reuse common logic such as audio file loading, clock synchronisation, etc. Each plugin leaves in a separate repo for better modularity and to simplify version management.
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
- ## Installation
47
+ ## Install
45
48
 
46
- _Note: most of the time you won't need to install `soundworks` manually, consider using the [application template](#application-template) instead._
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
- ## Application Template
55
+ ## Share with Us
53
56
 
54
- The simplest way to start a new `soundworks` application is using the application template:
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 project _BeCoMe_
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": "3.7.0",
4
- "description": "full-stack javascript framework for distributed audio visual experiences on the web",
3
+ "version": "4.0.0-alpha.0",
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
- "clean": "rm -Rf client && rm -Rf server && rm -Rf common",
21
- "doc": "rm -Rf docs && jsdoc -c .jsdoc.json --verbose && cp -R assets docs/",
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
- "debug": "^4.3.4",
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
- "polka": "^0.5.2",
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.5.0"
73
+ "ws": "^8.11.0"
53
74
  },
54
75
  "devDependencies": {
55
- "@babel/cli": "^7.17.6",
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;