@soundworks/core 4.0.0-alpha.2 → 4.0.0-alpha.20

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 CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "extends": [
3
3
  "@ircam",
4
- "plugin:jsdoc/recommended"
5
4
  ],
6
5
  "settings": {
7
6
  "jsdoc": {
package/.jsdoc ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "source": {
3
+ "include": [
4
+ "misc/assets/DOC.md",
5
+ "src/client",
6
+ "src/server",
7
+ "src/common"
8
+ ]
9
+ },
10
+ "plugins": [
11
+ "plugins/markdown"
12
+ ],
13
+ "opts": {
14
+ "projectTitle": "Soundworks",
15
+ "destination": "./docs",
16
+ "encoding": "utf8",
17
+ "template": "./node_modules/docdash"
18
+ },
19
+ "templates": {
20
+ "default": {
21
+ "useLongnameInNav": true
22
+ },
23
+ "footer": ""
24
+ },
25
+ "docdash": {
26
+ "sectionOrder": [
27
+ "Namespaces",
28
+ "Classes",
29
+ "Modules",
30
+ "Externals",
31
+ "Events",
32
+ "Mixins",
33
+ "Tutorials",
34
+ "Interfaces"
35
+ ],
36
+ "scripts": [
37
+ "assets/custom.css"
38
+ ],
39
+ "meta": {
40
+ "title": "soundworks",
41
+ "description": "Documentation of soundworks, a full-stack JavaScript framework for distributed WebAudio and multimedia applications"
42
+ },
43
+ "search": true,
44
+ "menu": {
45
+ "Website": {
46
+ "href":"http://soundworks.dev/",
47
+ "target":"_blank",
48
+ "class":"menu-item"
49
+ },
50
+ "Github": {
51
+ "href": "https://github.com/collective-soundworks/soundworks",
52
+ "target":"_blank",
53
+ "class":"menu-item"
54
+ }
55
+ }
56
+ }
57
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
1
+ ## v4.0.0-alpha.20 - 19/02/2024
2
+
3
+ - Refactor heartbeat - #86
4
+ - Refactor state collection - #85
5
+ - Fix issue with self signed certs and config generated by wizard - #84
6
+
7
+ ## v4.0.0-alpha.19 - 17/01/2024
8
+
9
+ - Hotfix for #85
10
+
11
+ ## v4.0.0-alpha.18 - 15/01/2024
12
+
13
+ - Batch network message for SharedStates
14
+
15
+ ## v4.0.0-alpha.17 - 16/12/2023
16
+
17
+ - Support bundling the server to cjs for Max externals
18
+
19
+ ## v4.0.0-alpha.16 - 15/12/2023
20
+
21
+ - Improve StateCollection performances
22
+
23
+ ## v4.0.0-alpha.15 - 13/12/2023
24
+
25
+ - Fix several issues with shared states
26
+ - **Breaking Change**: by default, `StateManager::observe` and `SharedStateCollection` now retrieve locally created states
package/README.md CHANGED
@@ -1,90 +1,56 @@
1
- # `soundworks`
1
+ # soundworks
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/@soundworks%2Fcore.svg)](https://badge.fury.io/js/@soundworks%2Fcore)
4
4
  [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
5
5
 
6
- ![soundworks-logo](./assets/logo-200x200.png)
6
+ ![soundworks-logo](./misc/assets/logo-200x200.png)
7
7
 
8
8
  Open-source creative coding framework for distributed applications based on Web technologies.
9
9
 
10
- 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
+ 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.
11
11
 
12
- *__WARNING: The version 4 of `@soundworks/core` is under heavy development.__*
13
-
14
- ## Getting Started
15
-
16
- The best and most simple way to start using `soundworks` is to use the `@soundworks/create` wizard.
17
-
18
- ```sh
19
- npx @soundworks/create@latest
20
- ```
21
-
22
- ![./assets/soundworks-create-min.gif](./assets/soundworks-create-min.gif)
23
-
24
- 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`.
25
-
26
- <!--
27
12
  ## Documentation
28
13
 
29
14
  - Guides and Tutorials: [https://soundworks.dev](https://soundworks.dev)
30
15
  - API: [https://soundworks.dev/api](https://soundworks.dev/api)
31
- -->
32
16
 
33
- ## API
17
+ ## Getting started
34
18
 
35
- 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:
19
+ The best and most simple way to start using **soundworks** is to use the `@soundworks/create` wizard.
36
20
 
37
21
  ```sh
38
- git clone https://github.com/collective-soundworks/soundworks.git
39
- cd soundworks
40
- git checkout v4
41
- serve docs
22
+ npx @soundworks/create@latest
42
23
  ```
43
24
 
44
- 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.
25
+ ![soundworks-create](./misc/assets/soundworks-create-min.gif)
45
26
 
46
- ## Share with Us
27
+ See [https://soundworks.dev/tutorials/getting-started.html](https://soundworks.dev/tutorials/getting-started.html) for more information on the wizard and how to start using **soundworks**.
47
28
 
48
- If you made an application using `soundworks` please let us know here: https://github.com/collective-soundworks/soundworks/discussions/61
29
+ ## Misc
49
30
 
50
- ## TypeScript Support
31
+ ### TypeScript support
51
32
 
52
33
  Basic TypeScript support will be proposed in a (hopefully) near future.
53
34
 
54
35
  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.
55
36
 
56
- ## Install
37
+ ### Manual install
57
38
 
58
- 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.
39
+ 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 information on the **soundworks** wizard.
59
40
 
60
41
  ```
61
42
  npm install @soundworks/core
62
43
  ```
63
44
 
64
- ## Credits
65
-
66
- `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).
67
-
68
- 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.
69
-
70
- ### Supporting Research Projects
71
-
72
- Initial and futher developments has been supported by the following research projects:
45
+ ## Share with Us
73
46
 
74
- - The [DOTS](http://dots.ircam.fr/) project, funded by the French National Research Agency (ANR)
75
- - The Ircam projects _BeCoMe_ and _SO(a)P_
76
- - The _Constella(c)tions_ residency, funded by the STARTS program of the European Commission
77
- - The [RAPID-MIX project](http://rapidmix.goldsmithsdigital.com/), funded by the European Union’s Horizon 2020 research and innovation program
78
- - The [CoSiMa](http://cosima.ircam.fr/) project, funded by the French National Research Agency (ANR)
47
+ If you made an application using **soundworks** please let us know here: https://github.com/collective-soundworks/soundworks/discussions/61
79
48
 
80
- ### Academic Papers
49
+ ## Credits
81
50
 
82
- - 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)>
83
- - 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)>
84
- - Benjamin Matuszewski, Norbert Schnell, Frédéric Bevilacqua. Interaction Topologies in Mobile-Based Situated Networked Music Systems. Wireless Communications and Mobile Computing, Hindawi Publishing Corporation, 2019, 2019, pp.9142490. ⟨10.1155/2019/9142490⟩. <[hal-02086673](https://hal.archives-ouvertes.fr/hal-02086673)>
85
- - Norbert Schnell, Sébastien Robaszkiewicz. Soundworks – A playground for artists and developers to create collaborative mobile web performances. `Proceedings of the Web Audio Conference (WAC'15), 2015, Paris, France. <[hal-01580797](https://hal.archives-ouvertes.fr/hal-01580797)>
51
+ [https://soundworks.dev/credits.html](https://soundworks.dev/credits.html)
86
52
 
87
53
  ## License
88
54
 
89
- [BSD-3-Clause](https://github.com/collective-soundworks/soundworks/blob/master/LICENSE)
55
+ [BSD-3-Clause](./LICENSE)
90
56
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soundworks/core",
3
- "version": "4.0.0-alpha.2",
3
+ "version": "4.0.0-alpha.20",
4
4
  "description": "Open-source creative coding framework for distributed applications based on Web technologies",
5
5
  "authors": [
6
6
  "Benjamin Matuszewski",
@@ -43,52 +43,45 @@
43
43
  "node": ">=16.0.0"
44
44
  },
45
45
  "scripts": {
46
- "doc": "npm run jsdoc",
47
- "jsdoc": "rm -Rf docs && jsdoc -c .jsdoc.json --verbose && cp -R assets docs/",
46
+ "doc": "rm -Rf docs && jsdoc -c .jsdoc --verbose && cp -R misc/assets docs/",
48
47
  "lint": "npx eslint src",
49
- "test": "mocha",
50
- "toc": "markdown-toc -i README.md",
48
+ "test": "npx mocha",
49
+ "test:all": "npx mocha tests/*/*.spec.js",
51
50
  "types": "rm -Rf types && tsc",
52
- "version": "npm run toc && npm run doc && git add docs"
51
+ "postversion": "node ./misc/scripts/check-changelog.js"
53
52
  },
54
53
  "dependencies": {
55
- "@ircam/sc-gettime": "^1.0.0",
56
- "chalk": "^4.1.2",
54
+ "@ircam/sc-utils": "^1.3.2",
55
+ "chalk": "^5.3.0",
57
56
  "columnify": "^1.6.0",
58
57
  "compression": "^1.7.1",
59
58
  "express": "^4.18.1",
60
59
  "fast-deep-equal": "^3.1.3",
61
60
  "fast-text-encoding": "^1.0.0",
62
- "is-plain-obj": "3.0.0",
63
- "isomorphic-ws": "^4.0.1",
64
- "keyv": "^4.2.2",
65
- "keyv-file": "^0.2.0",
66
- "lodash.clonedeep": "^4.5.0",
67
- "lodash.merge": "^4.6.2",
68
- "pem": "^1.14.6",
69
- "serve-static": "^1.15.0",
61
+ "isomorphic-ws": "^5.0.0",
62
+ "keyv": "^4.5.4",
63
+ "keyv-file": "^0.3.0",
64
+ "lodash": "^4.17.21",
65
+ "pem": "^1.14.8",
70
66
  "template-literal": "^1.0.4",
71
- "uuid": "^8.3.2",
72
- "window-or-global": "^1.0.1",
73
- "ws": "^8.11.0"
67
+ "uuid": "^9.0.1",
68
+ "ws": "^8.15.0"
74
69
  },
75
70
  "devDependencies": {
76
- "@ircam/eslint-config": "^1.0.0",
77
- "chai": "^4.3.6",
71
+ "@ircam/eslint-config": "^1.3.0",
72
+ "chai": "^4.3.10",
78
73
  "chai-shallow-deep-equal": "^1.4.6",
79
74
  "commitizen": "^4.2.5",
80
75
  "cz-conventional-changelog": "^3.3.0",
81
- "docdash": "^1.2.0",
82
- "dotenv": "^16.0.3",
83
- "eslint": "^8.24.0",
84
- "eslint-plugin-jsdoc": "^39.6.4",
85
- "generate-changelog": "^1.8.0",
86
- "jsdoc": "^3.6.10",
87
- "markdown-toc": "^1.2.0",
88
- "mocha": "^9.2.2",
89
- "puppeteer": "^18.0.5",
76
+ "docdash": "^2.0.2",
77
+ "dotenv": "^16.3.1",
78
+ "eslint": "^8.55.0",
79
+ "eslint-plugin-jsdoc": "^46.9.0",
80
+ "jsdoc": "^4.0.0",
81
+ "mocha": "^10.2.0",
82
+ "puppeteer": "^21.6.0",
90
83
  "tcp-ping": "^0.1.1",
91
- "typescript": "^4.9.3"
84
+ "typescript": "^5.3.3"
92
85
  },
93
86
  "optionalDependencies": {
94
87
  "bufferutil": "^4.0.5",
@@ -1,4 +1,4 @@
1
- import isPlainObject from 'is-plain-obj';
1
+ import { isBrowser, isPlainObject } from '@ircam/sc-utils';
2
2
 
3
3
  import ContextManager from './ContextManager.js';
4
4
  import PluginManager from './PluginManager.js';
@@ -10,7 +10,6 @@ import {
10
10
  CLIENT_HANDSHAKE_ERROR,
11
11
  AUDIT_STATE_NAME,
12
12
  } from '../common/constants.js';
13
- import { isBrowser } from '../common/utils.js';
14
13
  import logger from '../common/logger.js';
15
14
 
16
15
  /**
@@ -199,6 +198,12 @@ class Client {
199
198
  */
200
199
  this.status = 'idle';
201
200
 
201
+ /**
202
+ * Token of the client if connected through HTTP authentication.
203
+ * @private
204
+ */
205
+ this.token = null;
206
+
202
207
  /** @private */
203
208
  this._onStatusChangeCallbacks = new Set();
204
209
  /** @private */
@@ -241,9 +246,10 @@ class Client {
241
246
  try {
242
247
  await new Promise((resolve, reject) => {
243
248
  // wait for handshake response before starting stateManager and pluginManager
244
- this.socket.addListener(CLIENT_HANDSHAKE_RESPONSE, async ({ id, uuid }) => {
249
+ this.socket.addListener(CLIENT_HANDSHAKE_RESPONSE, async ({ id, uuid, token }) => {
245
250
  this.id = id;
246
251
  this.uuid = uuid;
252
+ this.token = token;
247
253
 
248
254
  resolve();
249
255
  });
@@ -251,7 +257,7 @@ class Client {
251
257
  this.socket.addListener(CLIENT_HANDSHAKE_ERROR, (err) => {
252
258
  let msg = ``;
253
259
 
254
- switch(err.type) {
260
+ switch (err.type) {
255
261
  case 'invalid-client-type':
256
262
  msg = `[soundworks:Client] ${err.message}`;
257
263
  break;
@@ -275,7 +281,7 @@ class Client {
275
281
 
276
282
  this.socket.send(CLIENT_HANDSHAKE_REQUEST, payload);
277
283
  });
278
- } catch(err) {
284
+ } catch (err) {
279
285
  throw new Error(err);
280
286
  }
281
287
 
@@ -1,4 +1,5 @@
1
1
  import Client from './Client.js';
2
+ import PromiseStore from '../common/PromiseStore.js';
2
3
  import {
3
4
  CONTEXT_ENTER_REQUEST,
4
5
  CONTEXT_ENTER_RESPONSE,
@@ -7,11 +8,9 @@ import {
7
8
  CONTEXT_EXIT_RESPONSE,
8
9
  CONTEXT_EXIT_ERROR,
9
10
  } from '../common/constants.js';
10
- import {
11
- storeRequestPromise,
12
- resolveRequest,
13
- rejectRequest,
14
- } from '../common/promise-store.js';
11
+
12
+ // share between all context, as channels are common to all contexts
13
+ const promiseStore = new PromiseStore('Context');
15
14
 
16
15
  /**
17
16
  * Base class to extend in order to implement a new Context.
@@ -86,7 +85,7 @@ class Context {
86
85
  return;
87
86
  }
88
87
 
89
- resolveRequest(reqId);
88
+ promiseStore.resolve(reqId);
90
89
  });
91
90
 
92
91
  this.client.socket.addListener(CONTEXT_ENTER_ERROR, (reqId, contextName, msg) => {
@@ -94,7 +93,7 @@ class Context {
94
93
  return;
95
94
  }
96
95
 
97
- rejectRequest(reqId, msg);
96
+ promiseStore.reject(reqId, msg);
98
97
  });
99
98
 
100
99
  this.client.socket.addListener(CONTEXT_EXIT_RESPONSE, (reqId, contextName) => {
@@ -102,7 +101,7 @@ class Context {
102
101
  return;
103
102
  }
104
103
 
105
- resolveRequest(reqId);
104
+ promiseStore.resolve(reqId);
106
105
  });
107
106
 
108
107
  this.client.socket.addListener(CONTEXT_EXIT_ERROR, (reqId, contextName, msg) => {
@@ -110,7 +109,7 @@ class Context {
110
109
  return;
111
110
  }
112
111
 
113
- rejectRequest(reqId, msg);
112
+ promiseStore.reject(reqId, msg);
114
113
  });
115
114
 
116
115
  this.client.contextManager.register(this);
@@ -206,10 +205,10 @@ class Context {
206
205
  // we need the try/catch block to change the promise rejection into proper error
207
206
  try {
208
207
  await new Promise((resolve, reject) => {
209
- const reqId = storeRequestPromise(resolve, reject);
208
+ const reqId = promiseStore.add(resolve, reject, 'enter-context');
210
209
  this.client.socket.send(CONTEXT_ENTER_REQUEST, reqId, this.name);
211
210
  });
212
- } catch(err) {
211
+ } catch (err) {
213
212
  throw new Error(err);
214
213
  }
215
214
  }
@@ -240,10 +239,10 @@ class Context {
240
239
  // we need the try/catch block to change the promise rejection into proper error
241
240
  try {
242
241
  await new Promise((resolve, reject) => {
243
- const reqId = storeRequestPromise(resolve, reject);
242
+ const reqId = promiseStore.add(resolve, reject, 'exit-context');
244
243
  this.client.socket.send(CONTEXT_EXIT_REQUEST, reqId, this.name);
245
244
  });
246
- } catch(err) {
245
+ } catch (err) {
247
246
  throw new Error(err);
248
247
  }
249
248
  }
@@ -78,7 +78,7 @@ class ContextManager {
78
78
  this._contextStartPromises.set(contextName, startPromise);
79
79
  await startPromise;
80
80
  context.status = 'started';
81
- } catch(err) {
81
+ } catch (err) {
82
82
  context.status = 'errored';
83
83
  throw new Error(err);
84
84
  }
@@ -83,7 +83,7 @@ class PluginManager extends BasePluginManager {
83
83
  */
84
84
  constructor(client) {
85
85
  if (!(client instanceof Client)) {
86
- throw new Error(`[soundworks.PluginManager] Invalid argument, "new PluginManager(client)" should receive an instance of "soundworks.Client as argument"`);
86
+ throw new Error(`[soundworks.PluginManager] Invalid argument, "new PluginManager(client)" should receive an instance of "soundworks.Client" as argument`);
87
87
  }
88
88
 
89
89
  super(client);
@@ -0,0 +1,38 @@
1
+ import BaseSharedStateCollection from '../common/BaseSharedStateCollection.js';
2
+
3
+ // this file exists only for documentation purposesSharedStateCollection.js
4
+
5
+ /**
6
+ * @callback client.SharedStateCollection~onUpdateCallback
7
+ * @param {client.SharedState} state - State that triggered the update.
8
+ * @param {Object} newValues - Key / value pairs of the updates that have been
9
+ * applied to the state.
10
+ * @param {Object} oldValues - Key / value pairs of the updated params before
11
+ * the updates has been applied to the state.
12
+ * @param {Mixed} [context=null] - Optionnal context object that has been passed
13
+ * with the values updates in the `set` call.
14
+ */
15
+
16
+ /**
17
+ * The `SharedStateCollection` interface represent a collection of all states
18
+ * created from a given schema name on the network.
19
+ *
20
+ * It can optionnaly exclude the states created by the current node.
21
+ *
22
+ * See {@link client.StateManager#getCollection} for factory method API
23
+ *
24
+ * ```
25
+ * const collection = await client.stateManager.getCollection('my-schema');
26
+ * const allValues = collection.getValues();
27
+ * collection.onUpdate((state, newValues, oldValues, context) => {
28
+ * // do something
29
+ * });
30
+ * ```
31
+ * @memberof client
32
+ * @extends BaseSharedStateCollection
33
+ * @inheritdoc
34
+ * @hideconstructor
35
+ */
36
+ class SharedStateCollection extends BaseSharedStateCollection {}
37
+
38
+ export default SharedStateCollection;
@@ -1,13 +1,19 @@
1
+ import { isBrowser } from '@ircam/sc-utils';
1
2
  import WebSocket from 'isomorphic-ws';
2
3
 
4
+ import {
5
+ PING_INTERVAL,
6
+ PING_LATENCY_TOLERANCE,
7
+ PING_MESSAGE,
8
+ PONG_MESSAGE,
9
+ } from '../common/constants.js';
10
+ import logger from '../common/logger.js';
3
11
  import {
4
12
  packBinaryMessage,
5
13
  unpackBinaryMessage,
6
14
  packStringMessage,
7
15
  unpackStringMessage,
8
16
  } from '../common/sockets-utils.js';
9
- import logger from '../common/logger.js';
10
- import { isBrowser } from '../common/utils.js';
11
17
 
12
18
  // WebSocket events:
13
19
  //
@@ -64,6 +70,8 @@ class Socket {
64
70
  this._stringListeners = new Map();
65
71
  /** @private */
66
72
  this._binaryListeners = new Map();
73
+ /** @private */
74
+ this._heartbeatId = null;
67
75
  }
68
76
 
69
77
  /**
@@ -110,7 +118,11 @@ class Socket {
110
118
  };
111
119
  }
112
120
 
113
- const queryParams = `role=${role}&key=${key}`;
121
+ let queryParams = `role=${role}&key=${key}`;
122
+
123
+ if (config.token) {
124
+ queryParams += `&token=${config.token}`;
125
+ }
114
126
 
115
127
  // ----------------------------------------------------------
116
128
  // init string socket
@@ -126,39 +138,73 @@ class Socket {
126
138
  ws.addEventListener('open', connectEvent => {
127
139
  // parse incoming messages for pubsub
128
140
  this.ws = ws;
141
+ // ping / pong to check connection
142
+ const heartbeat = () => {
143
+ clearTimeout(this._heartbeatId);
144
+
145
+ this._heartbeatId = setTimeout(() => {
146
+ this.terminate();
147
+ }, PING_INTERVAL + PING_LATENCY_TOLERANCE);
148
+ };
149
+
150
+ heartbeat();
151
+
129
152
  this.ws.addEventListener('message', e => {
153
+ if (e.data === PING_MESSAGE) {
154
+ heartbeat();
155
+ this.ws.send(PONG_MESSAGE);
156
+ // do not propagate ping / pong messages
157
+ return;
158
+ }
159
+
130
160
  const [channel, args] = unpackStringMessage(e.data);
131
161
  this._emit(false, channel, ...args);
132
162
  });
133
163
 
134
164
  // broadcast all `WebSocket` native events
135
- ['close', 'error', 'upgrade', 'message'].forEach(eventName => {
136
- this.ws.addEventListener(eventName, (e) => {
137
- this._emit(false, eventName, e);
138
- });
165
+ this.ws.addEventListener('close', (e) => {
166
+ clearTimeout(this._heartbeatId);
167
+ this._emit(false, 'close', e);
168
+ });
169
+
170
+ this.ws.addEventListener('error', (e) => {
171
+ clearTimeout(this._heartbeatId);
172
+ this._emit(false, 'error', e);
173
+ });
174
+
175
+ this.ws.addEventListener('upgrade', (e) => {
176
+ this._emit(false, 'upgrade', e);
177
+ });
178
+
179
+ this.ws.addEventListener('message', (e) => {
180
+ this._emit(false, 'message', e);
139
181
  });
140
182
 
141
183
  // forward open event
142
184
  this._emit(false, 'open', connectEvent);
185
+
143
186
  // continue with raw socket
144
187
  resolve();
145
188
  });
146
189
 
147
190
  // cf. https://github.com/collective-soundworks/soundworks/issues/17
148
191
  ws.addEventListener('error', e => {
149
- if (e.type === 'error' && e.error.code === 'ECONNREFUSED') {
150
- if (!connectionRefusedLogged) {
151
- logger.log('[soundworks.Socket] Connection refused, waiting for the server to start');
152
- connectionRefusedLogged = true;
153
- }
154
-
192
+ if (e.type === 'error') {
155
193
  if (ws.terminate) {
156
194
  ws.terminate();
157
195
  } else {
158
196
  ws.close();
159
197
  }
160
198
 
161
- setTimeout(trySocket, 1000);
199
+ // for node clients, retry connection
200
+ if (e.error && e.error.code === 'ECONNREFUSED') {
201
+ if (!connectionRefusedLogged) {
202
+ logger.log('[soundworks.Socket] Connection refused, waiting for the server to start');
203
+ connectionRefusedLogged = true;
204
+ }
205
+
206
+ setTimeout(trySocket, 1000);
207
+ }
162
208
  }
163
209
  });
164
210
  };
@@ -166,36 +212,6 @@ class Socket {
166
212
  trySocket();
167
213
  });
168
214
 
169
- // @todo - review/fix
170
- // - the `ws.on` method only exists on node implementation, and the 'ping'
171
- // message is not received on addEventListener
172
- // - there seems to be no way to access the ping event in browsers...
173
- //
174
- // let pingTimeoutId = null;
175
- // const pingInterval = config.env.websockets.pingInterval;
176
- // // detect broken connection
177
- // // cf. https://github.com/websockets/ws#how-to-detect-and-close-broken-connections
178
- // const heartbeat = () => {
179
- // try {
180
- // console.log('ping received');
181
- // clearTimeout(pingTimeoutId);
182
-
183
- // // pingTimeoutId = setTimeout(() => {
184
- // // console.log('terminate');
185
- // // this.terminate();
186
- // // }, pingInterval + 2000);
187
- // } catch (err) {
188
- // console.error(err);
189
- // }
190
- // };
191
- //
192
- // this.ws.on('ping', heartbeat);
193
- // this.ws.addEventListener('close', () => {
194
- // clearTimeout(pingTimeoutId);
195
- // });
196
-
197
- // heartbeat();
198
-
199
215
  // ----------------------------------------------------------
200
216
  // init binary socket
201
217
  // ----------------------------------------------------------