@ircam/comote-helpers 0.0.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 (5) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +157 -0
  3. package/package.json +45 -0
  4. package/qrcode.js +101 -0
  5. package/server.js +265 -0
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ Copyright (c) 2015 IRCAM, Paris, France
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in the
12
+ documentation and/or other materials provided with the distribution.
13
+
14
+ 3. Neither the name of the copyright holder nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # `@ircam/comote-helpers`
2
+
3
+ > Javascript and Max/MSP utilities to create applications compatible with iPhone
4
+ > and Android `CoMo.te` application.
5
+
6
+ ## Table of Contents
7
+
8
+ <!-- toc -->
9
+
10
+ - [Install](#install)
11
+ - [API](#api)
12
+ * [Classes](#classes)
13
+ * [Functions](#functions)
14
+ * [Typedefs](#typedefs)
15
+ * [getWifiInfos() ⇒ WiFiInfos \| null](#getwifiinfos-%E2%87%92-wifiinfos--null)
16
+ * [rawLink()](#rawlink)
17
+ * [terminal(config)](#terminalconfig)
18
+ * [dataURL(config)](#dataurlconfig)
19
+ * [WifiInfos : Object](#wifiinfos--object)
20
+ * [CoMoteConfig : Object](#comoteconfig--object)
21
+ * [CoMoteTarget : Object](#comotetarget--object)
22
+ - [License](#license)
23
+
24
+ <!-- tocstop -->
25
+
26
+ ## Install
27
+
28
+ ```sh
29
+ npm install --save @ircam/comote-helpers
30
+ ```
31
+
32
+ ## JS API
33
+
34
+ <!-- api -->
35
+
36
+ ### Classes
37
+
38
+ <dl>
39
+ <dt><a href="#Server">Server</a></dt>
40
+ <dd><p>Launch WebSocket and/or OSC server according to given <code>CoMoteConfig</code> object</p>
41
+ </dd>
42
+ </dl>
43
+
44
+ ### Functions
45
+
46
+ <dl>
47
+ <dt><a href="#getWifiInfos">getWifiInfos()</a> ⇒ <code>WiFiInfos</code> | <code>null</code></dt>
48
+ <dd><p>Retrieve the SSID and related IP of the first WiFi connection found, return
49
+ <code>null</code> if no WiFi connection found.</p>
50
+ </dd>
51
+ <dt><a href="#rawLink">rawLink()</a></dt>
52
+ <dd><p>Return the link to be encoded in the QRCode accroding to given <code>CoMoteConfig</code></p>
53
+ </dd>
54
+ <dt><a href="#terminal">terminal(config)</a></dt>
55
+ <dd><p>Create a qrcode to be logged in terminal according to given `CoMoteConfig``</p>
56
+ </dd>
57
+ <dt><a href="#dataURL">dataURL(config)</a></dt>
58
+ <dd><p>Create a qrcode to be used as in Image source according to given `CoMoteConfig``</p>
59
+ </dd>
60
+ </dl>
61
+
62
+ ### Typedefs
63
+
64
+ <dl>
65
+ <dt><a href="#WifiInfos">WifiInfos</a> : <code>Object</code></dt>
66
+ <dd></dd>
67
+ <dt><a href="#CoMoteConfig">CoMoteConfig</a> : <code>Object</code></dt>
68
+ <dd></dd>
69
+ <dt><a href="#CoMoteTarget">CoMoteTarget</a> : <code>Object</code></dt>
70
+ <dd></dd>
71
+ </dl>
72
+
73
+ <a name="getWifiInfos"></a>
74
+
75
+ ### getWifiInfos() ⇒ <code>WiFiInfos</code> \| <code>null</code>
76
+ Retrieve the SSID and related IP of the first WiFi connection found, return
77
+ `null` if no WiFi connection found.
78
+
79
+ **Kind**: global function
80
+ <a name="rawLink"></a>
81
+
82
+ ### rawLink()
83
+ Return the link to be encoded in the QRCode accroding to given `CoMoteConfig`
84
+
85
+ **Kind**: global function
86
+ <a name="terminal"></a>
87
+
88
+ ### terminal(config)
89
+ Create a qrcode to be logged in terminal according to given `CoMoteConfig``
90
+
91
+ **Kind**: global function
92
+
93
+ | Param | Type |
94
+ | --- | --- |
95
+ | config | [<code>CoMoteConfig</code>](#CoMoteConfig) |
96
+
97
+ **Example**
98
+ ```js
99
+ console(CoMoteQRCode.terminal(config));
100
+ ```
101
+ <a name="dataURL"></a>
102
+
103
+ ### dataURL(config)
104
+ Create a qrcode to be used as in Image source according to given `CoMoteConfig``
105
+
106
+ **Kind**: global function
107
+
108
+ | Param | Type |
109
+ | --- | --- |
110
+ | config | [<code>CoMoteConfig</code>](#CoMoteConfig) |
111
+
112
+ **Example**
113
+ ```js
114
+ <img src="${CoMoteQRCode.dataURL(config))}" />
115
+ ```
116
+ <a name="WifiInfos"></a>
117
+
118
+ ### WifiInfos : <code>Object</code>
119
+ **Kind**: global typedef
120
+ **Properties**
121
+
122
+ | Name | Type | Description |
123
+ | --- | --- | --- |
124
+ | ssid | <code>number</code> | SSID of the WiFi connection |
125
+ | ip | <code>number</code> | Related IP (IPV4) |
126
+
127
+ <a name="CoMoteConfig"></a>
128
+
129
+ ### CoMoteConfig : <code>Object</code>
130
+ **Kind**: global typedef
131
+ **Properties**
132
+
133
+ | Name | Type | Description |
134
+ | --- | --- | --- |
135
+ | id | <code>String</code> | id of the client CoMo.te |
136
+ | interval | <code>Number</code> | period in ms of the sensors for the client CoMo.te |
137
+ | osc | [<code>CoMoteTarget</code>](#CoMoteTarget) | OSC configuration |
138
+ | ws | [<code>CoMoteTarget</code>](#CoMoteTarget) | WebSocket configuration |
139
+
140
+ <a name="CoMoteTarget"></a>
141
+
142
+ ### CoMoteTarget : <code>Object</code>
143
+ **Kind**: global typedef
144
+ **Properties**
145
+
146
+ | Name | Type | Description |
147
+ | --- | --- | --- |
148
+ | hostname | <code>String</code> | hostname or ip of the WebSocket or OSC server |
149
+ | port | <code>Number</code> | listening port of the of the WebSocket or OSC server |
150
+ | autostart | <code>Boolean</code> | enable streaming on CoMo.te application |
151
+
152
+
153
+ <!-- apistop -->
154
+
155
+ ## License
156
+
157
+ BSD-3-Clause
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@ircam/comote-helpers",
3
+ "version": "0.0.0",
4
+ "description": "Server component & utilities for the CoMo.te application",
5
+ "authors": [
6
+ "Benjamin.Matuszewski@ircam.fr",
7
+ "Jean-Philippe.Lambert@ircam.fr"
8
+ ],
9
+ "license": "BSD-3-Clause",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/ircam-ismm/comote-helpers"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "scripts": {
18
+ "api": "jsdoc-to-readme --src src/server.js src/qrcode.js",
19
+ "build": "npm run clean && babel src --out-dir .",
20
+ "clean": "rm -f server.js qrcode.js",
21
+ "dev": "npm run build && chokidar src -c \"npm run build\"",
22
+ "doc": "npm run api && npm run toc",
23
+ "prepublishOnly": "npm run build",
24
+ "toc": "markdown-toc -i README.md --maxdepth 3"
25
+ },
26
+ "dependencies": {
27
+ "assign-deep": "^1.0.1",
28
+ "clone-deep": "^4.0.1",
29
+ "debug": "^4.3.1",
30
+ "node-osc": "^7.0.0",
31
+ "qrcode": "^1.5.0",
32
+ "systeminformation": "^5.11.15",
33
+ "utf-8-validate": "^5.0.9",
34
+ "ws": "^8.6.0"
35
+ },
36
+ "devDependencies": {
37
+ "@babel/cli": "^7.4.4",
38
+ "@babel/core": "^7.4.5",
39
+ "@babel/preset-env": "^7.14.7",
40
+ "chokidar": "^3.0.1",
41
+ "chokidar-cli": "^2.1.0",
42
+ "jsdoc-to-readme": "^1.0.2",
43
+ "markdown-toc": "^1.2.0"
44
+ }
45
+ }
package/qrcode.js ADDED
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.dataURL = dataURL;
7
+ exports.rawLink = rawLink;
8
+ exports.terminal = terminal;
9
+
10
+ var _qrcode = _interopRequireDefault(require("qrcode"));
11
+
12
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
+
14
+ function formatConfigToLink(config) {
15
+ let link = `comote://settings?`;
16
+ const query = [];
17
+
18
+ if (Number.isFinite(config.id) && config.id >= 0) {
19
+ query.push(`id=${config.id}`);
20
+ }
21
+
22
+ if (Number.isFinite(config.interval) && config.interval > 0) {
23
+ query.push(`interval=${config.interval}`);
24
+ }
25
+
26
+ if (config.ws) {
27
+ const {
28
+ hostname,
29
+ port
30
+ } = config.ws;
31
+
32
+ if (!hostname || !Number.isInteger(port)) {
33
+ throw new Error(`Invalid WebSocket config: ${config.ws}`);
34
+ }
35
+
36
+ query.push(`ws-url=ws://${hostname}:${port}`);
37
+
38
+ if (config.ws.autostart === true) {
39
+ query.push(`ws-enable=1`);
40
+ }
41
+ }
42
+
43
+ if (config.osc) {
44
+ const {
45
+ hostname,
46
+ port
47
+ } = config.osc;
48
+
49
+ if (!hostname || !Number.isInteger(port)) {
50
+ throw new Error(`Invalid WebSocket config: ${config.osc}`);
51
+ }
52
+
53
+ query.push(`osc-url=udp://${hostname}:${port}`);
54
+
55
+ if (config.osc.autostart === true) {
56
+ query.push(`osc-enable=1`);
57
+ }
58
+ }
59
+
60
+ link += query.join('&');
61
+ return link;
62
+ }
63
+ /**
64
+ * Return the link to be encoded in the QRCode accroding to given `CoMoteConfig`
65
+ */
66
+
67
+
68
+ function rawLink(config) {
69
+ return formatConfigToLink(config);
70
+ }
71
+ /**
72
+ * Create a qrcode to be logged in terminal according to given `CoMoteConfig``
73
+ *
74
+ * @param {CoMoteConfig} config
75
+ * @example
76
+ * console(await CoMoteQRCode.terminal(config));
77
+ */
78
+
79
+
80
+ async function terminal(config) {
81
+ const link = formatConfigToLink(config);
82
+ return await _qrcode.default.toString(link, {
83
+ type: 'terminal',
84
+ small: true
85
+ });
86
+ }
87
+ /**
88
+ * Create a qrcode to be used as in Image source according to given `CoMoteConfig``
89
+ *
90
+ * @param {CoMoteConfig} config
91
+ * @example
92
+ * const qrCode = await CoMoteQRCode.dataURL(config));
93
+ *
94
+ * <img src="${qrCode}" />
95
+ */
96
+
97
+
98
+ async function dataURL(config) {
99
+ const link = formatConfigToLink(config);
100
+ return await await _qrcode.default.toDataURL(link);
101
+ }
package/server.js ADDED
@@ -0,0 +1,265 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.Server = void 0;
7
+ exports.getWifiInfos = getWifiInfos;
8
+
9
+ var _systeminformation = _interopRequireDefault(require("systeminformation"));
10
+
11
+ var _ws = _interopRequireDefault(require("ws"));
12
+
13
+ var _utf8Validate = _interopRequireDefault(require("utf-8-validate"));
14
+
15
+ var _nodeOsc = require("node-osc");
16
+
17
+ var _cloneDeep = _interopRequireDefault(require("clone-deep"));
18
+
19
+ var _assignDeep = _interopRequireDefault(require("assign-deep"));
20
+
21
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
22
+
23
+ /**
24
+ * @typedef {Object} WifiInfos
25
+ * @property {number} ssid - SSID of the WiFi connection
26
+ * @property {number} ip - Related IP (IPV4)
27
+ */
28
+
29
+ /**
30
+ * @typedef {Object} CoMoteConfig
31
+ * @property {String} id - id of the client CoMo.te
32
+ * @property {Number} interval - period in ms of the sensors for the client CoMo.te
33
+ * @property {CoMoteTarget} osc - OSC configuration
34
+ * @property {CoMoteTarget} ws - WebSocket configuration
35
+ */
36
+
37
+ /**
38
+ * @typedef {Object} CoMoteTarget
39
+ * @property {String} hostname - hostname or ip of the WebSocket or OSC server
40
+ * @property {Number} port - listening port of the of the WebSocket or OSC server
41
+ * @property {Boolean} autostart - enable streaming on CoMo.te application
42
+ */
43
+
44
+ /**
45
+ * Retrieve the SSID and related IP of the first WiFi connection found, return
46
+ * `null` if no WiFi connection found.
47
+ *
48
+ * @async
49
+ * @return {WiFiInfos|null}
50
+ */
51
+ async function getWifiInfos() {
52
+ // find first wifi connection
53
+ const wifiConnections = await _systeminformation.default.wifiConnections();
54
+ const conn = wifiConnections[0];
55
+
56
+ if (!conn) {
57
+ return null;
58
+ } // find related interface
59
+
60
+
61
+ const interfaces = await _systeminformation.default.networkInterfaces();
62
+ const int = interfaces.find(int => int.iface === conn.iface);
63
+ return {
64
+ ssid: conn.ssid,
65
+ ip: int.ip4
66
+ };
67
+ }
68
+ /**
69
+ * Launch WebSocket and/or OSC server according to given `CoMoteConfig` object
70
+ *
71
+ * @param {CoMoteConfig} config - CoMote configuration
72
+ * @param {Object} options - options
73
+ * @param {Object} [options.verbose=false] - logs debug informations
74
+ */
75
+
76
+
77
+ class Server {
78
+ constructor(config, options) {
79
+ /**
80
+ * Configuration of the CoMote server
81
+ */
82
+ this.config = (0, _cloneDeep.default)((0, _assignDeep.default)({
83
+ id: null,
84
+ interval: null,
85
+ osc: null,
86
+ ws: null,
87
+ verbose: false
88
+ }, config));
89
+ this._verbose = !!options.verbose;
90
+
91
+ if (this._verbose) {
92
+ console.log('+ CoMo.te config:');
93
+ console.log(this.config, '\n');
94
+ }
95
+
96
+ this._websocketServer = null;
97
+ this._oscServer = null;
98
+ this._wsListeners = new Set();
99
+ this._oscListeners = new Set();
100
+ }
101
+
102
+ async start() {
103
+ let wsPromise = true;
104
+ let oscPromise = true;
105
+
106
+ if (this.config.ws !== null) {
107
+ const {
108
+ hostname,
109
+ port
110
+ } = this.config.ws;
111
+
112
+ if (!Number.isInteger(port)) {
113
+ throw new Error(`Invalid port "${port}" for WebSocket server`);
114
+ }
115
+
116
+ if (this._verbose) {
117
+ console.log(`> CoMo.te: Launching WebSocket server on port: ${port}`);
118
+ }
119
+
120
+ this._websocketServer = new _ws.default.Server({
121
+ port
122
+ });
123
+ const sockets = new Map();
124
+
125
+ this._websocketServer.on('connection', (socket, request) => {
126
+ // const ip = request.socket.remoteAddress;
127
+ socket.on('message', (data, isBinary) => {
128
+ if (isBinary) {// @todo
129
+ } else {
130
+ if ((0, _utf8Validate.default)(data)) {
131
+ // do we really need this check?
132
+ data = JSON.parse(data);
133
+
134
+ if (this._verbose) {
135
+ console.log(`> CoMo.te: new WebSocket message`, data);
136
+ } // console.log(data);
137
+ // console.log(this._wsListeners.size);
138
+
139
+
140
+ this._wsListeners.forEach(listener => listener(data));
141
+ }
142
+ }
143
+ }); // When a socket closes, or disconnects, remove it from the array.
144
+
145
+ socket.on('close', (code, data) => {
146
+ if (this._verbose) {
147
+ console.log('> CoMo.te: closed socket connection');
148
+ }
149
+ });
150
+ });
151
+
152
+ wsPromise = new Promise((resolve, reject) => {
153
+ this._websocketServer.on('listening', () => {
154
+ if (this._verbose) {
155
+ console.log(`> CoMo.te: WebSocket server listening`);
156
+ }
157
+
158
+ resolve();
159
+ });
160
+
161
+ this._websocketServer.on('error', err => {
162
+ console.log(`> CoMo.te: WebSocket server error`, err);
163
+ reject(err);
164
+ });
165
+ });
166
+ }
167
+
168
+ if (this.config.osc !== null) {
169
+ const {
170
+ hostname,
171
+ port
172
+ } = this.config.osc;
173
+
174
+ if (!Number.isInteger(port)) {
175
+ throw new Error(`Invalid port "${port}" for OSC server`);
176
+ } // fallback to broadcast
177
+
178
+
179
+ if (!hostname) {
180
+ hostname = '0.0.0.0';
181
+ }
182
+
183
+ if (this._verbose) {
184
+ console.log(`> CoMo.te: Launching OSC server udp://${hostname}:${port}`);
185
+ }
186
+
187
+ oscPromise = new Promise((resolve, reject) => {
188
+ this._oscServer = new _nodeOsc.Server(this.config.osc.port, hostname, err => {
189
+ if (err) {
190
+ console.log(`> CoMo.te: OSC server error`, err);
191
+ reject();
192
+ return;
193
+ }
194
+
195
+ if (this._verbose) {
196
+ console.log(`> CoMo.te: OSC server listening`);
197
+ }
198
+
199
+ resolve();
200
+ });
201
+
202
+ this._oscServer.on('message', data => {
203
+ let address = data.shift();
204
+
205
+ if (this._verbose) {
206
+ console.log(`> CoMo.te: new OSC message "${address}":`, data);
207
+ }
208
+
209
+ this._oscListeners.forEach(callback => callback(address, data));
210
+ });
211
+ });
212
+ }
213
+
214
+ return Promise.all([wsPromise, oscPromise]);
215
+ }
216
+
217
+ async stop() {
218
+ if (this._websocketServer) {
219
+ this._websocketServer.close();
220
+ }
221
+
222
+ if (this._oscServer) {
223
+ this._oscServer.close();
224
+ }
225
+ }
226
+ /**
227
+ * Add a listener for incomming WebSocket message
228
+ */
229
+
230
+
231
+ addWsListener(callback) {
232
+ this._wsListeners.add(callback);
233
+
234
+ return () => this._wsListeners.delete(callback);
235
+ }
236
+ /**
237
+ * Remove WebSocket listener
238
+ */
239
+
240
+
241
+ removeWsListener(callback) {
242
+ this._wsListeners.delete(callback);
243
+ }
244
+ /**
245
+ * Add a listener for incomming OSC message
246
+ */
247
+
248
+
249
+ addOscListener(callback) {
250
+ this._oscListeners.add(callback);
251
+
252
+ return () => this._oscListeners.delete(callback);
253
+ }
254
+ /**
255
+ * Remove OSC listener
256
+ */
257
+
258
+
259
+ removeOscListener(callback) {
260
+ this._oscListeners.delete(callback);
261
+ }
262
+
263
+ }
264
+
265
+ exports.Server = Server;