Forráskód Böngészése

add emitter & receiver

bdestombes 3 éve
commit
48acb2736f

+ 12 - 0
emitter/.editorconfig

@@ -0,0 +1,12 @@
+root = true
+
+[*]
+indent_style = tab
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.yml,*.yaml]
+indent_style = space
+indent_size = 2

+ 3 - 0
emitter/.gitattributes

@@ -0,0 +1,3 @@
+* text=auto
+*.js text eol=lf
+

+ 4 - 0
emitter/.gitignore

@@ -0,0 +1,4 @@
+node_modules
+yarn.lock
+.nyc_output
+coverage

+ 69 - 0
emitter/backend/server.js

@@ -0,0 +1,69 @@
+const express = require('express');
+const { Bundle, Client } = require('node-osc');
+const path = require('path');
+
+const bodyParser = require('body-parser');
+const app = express();
+
+const oscClient = new Client('127.0.0.1', 3333);
+let fseq;
+const alive = [];
+
+const server = require('http').Server(app);
+
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({extended:true}));
+app.use(express.static('./frontend/assets'));
+
+app.get('/', function (req, res) {
+   res.sendFile('./frontend/index.html', { root: path.resolve(__dirname + '/..') })
+});
+
+app.post('/json', function (req, res) {
+	//console.log(req.body);
+	fseq = fseq ? fseq + 1 : 1;
+	let oscBundle;
+	if (req.body.event === 'touchend') {
+		oscBundle = new Bundle(
+			//[ '/tuio/2Dcur', 'source', 'touch@127.0.0.1:5001' ],
+			[ '/tuio/2Dcur', 'alive' ],
+			[ '/tuio/2Dcur', 'fseq', fseq ]
+		);
+	} else {
+		//if (req.body.changedTouches && req.body.changedTouches.length && req.body.changedTouches.length > 0) {
+			const touches = Object.keys(req.body.changedTouches);
+			touches.forEach(touch => {
+				if (!alive.includes(req.body.changedTouches[touch].identifier)) {
+					alive.push(req.body.changedTouches[touch].identifier);
+				}
+			});
+			oscBundle = new Bundle(
+				//[ '/tuio/2Dcur', 'source', 'touch@127.0.0.1:5001' ],
+				[ '/tuio/2Dcur', 'alive', alive.join(' ') ],
+				[ '/tuio/2Dcur', 'fseq', fseq ]
+			);
+			touches.forEach(touch => {
+				console.log(req.body.changedTouches[touch]);
+				oscBundle.append(
+					[
+						'/tuio/2Dcur',
+						'set',
+						req.body.changedTouches[touch].identifier,
+						req.body.changedTouches[touch].clientX / req.body.screenW,
+						req.body.changedTouches[touch].clientY / req.body.screenH,
+						0.0,
+						0.0
+					]
+				);
+			});
+			oscClient.send(oscBundle, () => {
+				res.status(200).json(JSON.stringify(req.body));
+			});
+		//}
+	}
+});
+
+server.listen(5001, function () {
+ console.log('Votre app est disponible sur localhost:5001 !')
+});
+

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 9404 - 0
emitter/frontend/assets/jquery-1.7.2.js


+ 160 - 0
emitter/frontend/index.html

@@ -0,0 +1,160 @@
+<!DOCTYPE html>
+<html style="height: 100%;">
+<head>
+	<meta charset="utf-8">
+	<meta name="viewport" content="width=device-width">
+	<title>Touch detect and send to Node server</title>
+</head>
+
+<body style="height: 100%;">
+	<div id="box" style="width: 100%; height: 100%; background-color: #EEEEEE;"></div>
+	<script src="/jquery-1.7.2.js"></script>
+	<script>
+		/* simulation de touch events */
+		/*
+		function sendTouchEvent(x, y, element, eventType) {
+			const touchObj = new Touch({
+				identifier: Date.now(),
+				target: element,
+				clientX: x,
+				clientY: y,
+				radiusX: 2.5,
+				radiusY: 2.5,
+				rotationAngle: 10,
+				force: 0.5,
+			});
+
+			const touchEvent = new TouchEvent(eventType, {
+				cancelable: true,
+				bubbles: true,
+				touches: [touchObj],
+				targetTouches: [],
+				changedTouches: [touchObj],
+				shiftKey: true,
+			});
+
+			element.dispatchEvent(touchEvent);
+		}
+		*/
+
+		const box = document.getElementById('box');
+
+		const urlParams = new URLSearchParams(window.location.search);
+		const sectionParam = urlParams.get('section');
+		const section = sectionParam ? parseInt(sectionParam, 10) : 0;
+		console.log(section);
+
+
+		box.addEventListener('touchstart', function(evt) {
+				console.log(evt);
+				box.innerHTML = evt.touches.length;
+				fetch('http://localhost:5001/json', {
+					method: 'POST',
+					mode: 'cors',
+					cache: 'no-cache',
+					credentials: 'same-origin',
+					headers: {
+						'Content-Type': 'application/json'
+					},
+					redirect: 'follow',
+					referrerPolicy: 'no-referrer',
+					body: JSON.stringify({
+						event: 'touchstart',
+						clientX: evt.changedTouches[0].clientX,
+						clientY: evt.changedTouches[0].clientY,
+						force: evt.changedTouches[0].force,
+						identifier: section + evt.changedTouches[0].identifier,
+						pageX: evt.changedTouches[0].pageX,
+						pageY: evt.changedTouches[0].pageY,
+						radiusX: evt.changedTouches[0].radiusX,
+						radiusY: evt.changedTouches[0].radiusY,
+						rotationAngle: evt.changedTouches[0].rotationAngle,
+						screenX: evt.changedTouches[0].screenX,
+						screenY: evt.changedTouches[0].screenY
+					})
+				});
+				evt.preventDefault();
+		});
+		box.addEventListener('touchmove', function(evt) {
+				console.log(evt);
+				const touches = [];
+				for (var i = 0; i < evt.changedTouches.length; i++) {
+					touches[i] = {
+						clientX: evt.changedTouches[i].clientX,
+						clientY: evt.changedTouches[i].clientY,
+						force: evt.changedTouches[i].force,
+						identifier: section + evt.changedTouches[i].identifier,
+						pageX: evt.changedTouches[i].pageX,
+						pageY: evt.changedTouches[i].pageY,
+						radiusX: evt.changedTouches[i].radiusX,
+						radiusY: evt.changedTouches[i].radiusY,
+						rotationAngle: evt.changedTouches[i].rotationAngle,
+						screenX: evt.changedTouches[i].screenX,
+						screenY: evt.changedTouches[i].screenY
+					};
+				};
+				box.innerHTML = evt.touches.length;
+				fetch('http://localhost:5001/json', {
+					method: 'POST',
+					mode: 'cors',
+					cache: 'no-cache',
+					credentials: 'same-origin',
+					headers: {
+						'Content-Type': 'application/json'
+					},
+					redirect: 'follow',
+					referrerPolicy: 'no-referrer',
+					body: JSON.stringify({
+						event: 'touchmove',
+						screenW: $(document).width(),
+						screenH: $(document).height(),
+						changedTouches: touches
+					})
+				});
+				evt.preventDefault();
+		});
+
+		box.addEventListener('touchend', function(evt) {
+				console.log(evt);
+				box.innerHTML = evt.touches.length;
+				fetch('http://localhost:5001/json', {
+					method: 'POST',
+					mode: 'cors',
+					cache: 'no-cache',
+					credentials: 'same-origin',
+					headers: {
+						'Content-Type': 'application/json'
+					},
+					redirect: 'follow',
+					referrerPolicy: 'no-referrer',
+					body: JSON.stringify({
+						event: 'touchend',
+						clientX: evt.changedTouches[0].clientX,
+						clientY: evt.changedTouches[0].clientY,
+						force: evt.changedTouches[0].force,
+						identifier: section + evt.changedTouches[0].identifier,
+						pageX: evt.changedTouches[0].pageX,
+						pageY: evt.changedTouches[0].pageY,
+						radiusX: evt.changedTouches[0].radiusX,
+						radiusY: evt.changedTouches[0].radiusY,
+						rotationAngle: evt.changedTouches[0].rotationAngle,
+						screenX: evt.changedTouches[0].screenX,
+						screenY: evt.changedTouches[0].screenY
+					})
+				});
+				evt.preventDefault();
+		});
+
+		if (!(typeof box.ontouchstart != 'undefined')) {
+				box.style.border = '1px solid red';
+		}
+
+		/*
+		sendTouchEvent(150, 150, box, 'touchstart');
+		sendTouchEvent(220, 200, box, 'touchmove');
+		sendTouchEvent(220, 200, box, 'touchend');
+		*/
+
+		</script>
+</body>
+</html>

+ 10 - 0
emitter/license

@@ -0,0 +1,10 @@
+MIT License
+
+Copyright (c) t1st3 <contact@t1st3.com> (t1st3.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+

+ 926 - 0
emitter/package-lock.json

@@ -0,0 +1,926 @@
+{
+  "name": "http-keep-alive",
+  "version": "0.1.0",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "http-keep-alive",
+      "version": "0.1.0",
+      "license": "MIT",
+      "dependencies": {
+        "body-parser": "^1.19.0",
+        "express": "^4.17.1",
+        "node-osc": "^6.1.11"
+      }
+    },
+    "node_modules/accepts": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+      "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+      "dependencies": {
+        "mime-types": "~2.1.24",
+        "negotiator": "0.6.2"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+    },
+    "node_modules/binpack": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/binpack/-/binpack-0.1.0.tgz",
+      "integrity": "sha1-vT0JdMPyoERuF99PYLVacqIFqX4="
+    },
+    "node_modules/body-parser": {
+      "version": "1.19.1",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz",
+      "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==",
+      "dependencies": {
+        "bytes": "3.1.1",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "http-errors": "1.8.1",
+        "iconv-lite": "0.4.24",
+        "on-finished": "~2.3.0",
+        "qs": "6.9.6",
+        "raw-body": "2.4.2",
+        "type-is": "~1.6.18"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/bytes": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz",
+      "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/content-disposition": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "dependencies": {
+        "safe-buffer": "5.2.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
+      "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+    },
+    "node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/destroy": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "node_modules/encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/express": {
+      "version": "4.17.2",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz",
+      "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==",
+      "dependencies": {
+        "accepts": "~1.3.7",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.19.1",
+        "content-disposition": "0.5.4",
+        "content-type": "~1.0.4",
+        "cookie": "0.4.1",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "~1.1.2",
+        "fresh": "0.5.2",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.7",
+        "qs": "6.9.6",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.2.1",
+        "send": "0.17.2",
+        "serve-static": "1.14.2",
+        "setprototypeof": "1.2.0",
+        "statuses": "~1.5.0",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      }
+    },
+    "node_modules/finalhandler": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "dependencies": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "statuses": "~1.5.0",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
+      "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
+      "dependencies": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": ">= 1.5.0 < 2",
+        "toidentifier": "1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+    },
+    "node_modules/methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.51.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
+      "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.34",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
+      "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
+      "dependencies": {
+        "mime-db": "1.51.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+    },
+    "node_modules/negotiator": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+      "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/node-osc": {
+      "version": "6.1.11",
+      "resolved": "https://registry.npmjs.org/node-osc/-/node-osc-6.1.11.tgz",
+      "integrity": "sha512-bwFV/UKBBaSNK5pLYeA8Qx0GVc49fKRjmfMwMoypfPwb7RdhyRccW+tTNHcwr/OJ2b01uVKpnTPurSNs4fWnvw==",
+      "dependencies": {
+        "osc-min": "^1.1.1"
+      },
+      "engines": {
+        "node": "^12.22 || ^14.13 || >=16"
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/osc-min": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/osc-min/-/osc-min-1.1.2.tgz",
+      "integrity": "sha512-8DbiO8ME85R75stgNVCZtHxB9MNBBNcyy+isNBXrsFeinXGjwNAauvKVmGlfRas5VJWC/mhzIx7spR2gFvWxvg==",
+      "dependencies": {
+        "binpack": "~0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+    },
+    "node_modules/proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.9.6",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz",
+      "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==",
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/raw-body": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz",
+      "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==",
+      "dependencies": {
+        "bytes": "3.1.1",
+        "http-errors": "1.8.1",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "node_modules/send": {
+      "version": "0.17.2",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
+      "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
+      "dependencies": {
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "destroy": "~1.0.4",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "1.8.1",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "~2.3.0",
+        "range-parser": "~1.2.1",
+        "statuses": "~1.5.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/send/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+    },
+    "node_modules/serve-static": {
+      "version": "1.14.2",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz",
+      "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==",
+      "dependencies": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.17.2"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+    },
+    "node_modules/statuses": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+      "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "dependencies": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    }
+  },
+  "dependencies": {
+    "accepts": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+      "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+      "requires": {
+        "mime-types": "~2.1.24",
+        "negotiator": "0.6.2"
+      }
+    },
+    "array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+    },
+    "binpack": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/binpack/-/binpack-0.1.0.tgz",
+      "integrity": "sha1-vT0JdMPyoERuF99PYLVacqIFqX4="
+    },
+    "body-parser": {
+      "version": "1.19.1",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz",
+      "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==",
+      "requires": {
+        "bytes": "3.1.1",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "http-errors": "1.8.1",
+        "iconv-lite": "0.4.24",
+        "on-finished": "~2.3.0",
+        "qs": "6.9.6",
+        "raw-body": "2.4.2",
+        "type-is": "~1.6.18"
+      }
+    },
+    "bytes": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz",
+      "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg=="
+    },
+    "content-disposition": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "requires": {
+        "safe-buffer": "5.2.1"
+      }
+    },
+    "content-type": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
+    },
+    "cookie": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
+      "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+    },
+    "debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
+    "depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+    },
+    "destroy": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+    },
+    "etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+    },
+    "express": {
+      "version": "4.17.2",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz",
+      "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==",
+      "requires": {
+        "accepts": "~1.3.7",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.19.1",
+        "content-disposition": "0.5.4",
+        "content-type": "~1.0.4",
+        "cookie": "0.4.1",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "~1.1.2",
+        "fresh": "0.5.2",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.7",
+        "qs": "6.9.6",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.2.1",
+        "send": "0.17.2",
+        "serve-static": "1.14.2",
+        "setprototypeof": "1.2.0",
+        "statuses": "~1.5.0",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      }
+    },
+    "finalhandler": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "statuses": "~1.5.0",
+        "unpipe": "~1.0.0"
+      }
+    },
+    "forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
+    },
+    "fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+    },
+    "http-errors": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
+      "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
+      "requires": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": ">= 1.5.0 < 2",
+        "toidentifier": "1.0.1"
+      }
+    },
+    "iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+    },
+    "ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
+    },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+    },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+    },
+    "mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+    },
+    "mime-db": {
+      "version": "1.51.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
+      "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g=="
+    },
+    "mime-types": {
+      "version": "2.1.34",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
+      "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
+      "requires": {
+        "mime-db": "1.51.0"
+      }
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+    },
+    "negotiator": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+      "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
+    },
+    "node-osc": {
+      "version": "6.1.11",
+      "resolved": "https://registry.npmjs.org/node-osc/-/node-osc-6.1.11.tgz",
+      "integrity": "sha512-bwFV/UKBBaSNK5pLYeA8Qx0GVc49fKRjmfMwMoypfPwb7RdhyRccW+tTNHcwr/OJ2b01uVKpnTPurSNs4fWnvw==",
+      "requires": {
+        "osc-min": "^1.1.1"
+      }
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "osc-min": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/osc-min/-/osc-min-1.1.2.tgz",
+      "integrity": "sha512-8DbiO8ME85R75stgNVCZtHxB9MNBBNcyy+isNBXrsFeinXGjwNAauvKVmGlfRas5VJWC/mhzIx7spR2gFvWxvg==",
+      "requires": {
+        "binpack": "~0"
+      }
+    },
+    "parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+    },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+    },
+    "proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "requires": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      }
+    },
+    "qs": {
+      "version": "6.9.6",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz",
+      "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ=="
+    },
+    "range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+    },
+    "raw-body": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz",
+      "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==",
+      "requires": {
+        "bytes": "3.1.1",
+        "http-errors": "1.8.1",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "send": {
+      "version": "0.17.2",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
+      "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
+      "requires": {
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "destroy": "~1.0.4",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "1.8.1",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "~2.3.0",
+        "range-parser": "~1.2.1",
+        "statuses": "~1.5.0"
+      },
+      "dependencies": {
+        "ms": {
+          "version": "2.1.3",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+        }
+      }
+    },
+    "serve-static": {
+      "version": "1.14.2",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz",
+      "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==",
+      "requires": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.17.2"
+      }
+    },
+    "setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+    },
+    "statuses": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+      "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
+    },
+    "toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
+    },
+    "type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      }
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+    },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
+    },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
+    }
+  }
+}

+ 16 - 0
emitter/package.json

@@ -0,0 +1,16 @@
+{
+  "name": "http-keep-alive",
+  "version": "0.1.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "t1st3 <contact@t1st3.com> (https://t1st3.com/)",
+  "license": "MIT",
+  "dependencies": {
+    "body-parser": "^1.19.0",
+    "express": "^4.17.1",
+    "node-osc": "^6.1.11"
+  }
+}

+ 45 - 0
emitter/readme.md

@@ -0,0 +1,45 @@
+# objets tangibles - emitter
+
+> Application basée sur Node.js et Express 4 qui envoie des datagrammes TUIO en fonction des touch-events récupérés sur une page web
+
+## Description
+
+Le projet fourni est composé de 2 parties:
+* un backend
+* un frontend
+
+Le backend est une application Node.js basée sur Express; il officie comme émetteur de données TUIO qui sont récupérées à chaque touch-event.
+
+Le frontend est une page web, servie par le backend, qui envoie les touch-events au backend via requête HTTP.
+
+Chaque partie est contenue dans le dossier correspondant.
+
+
+## Pré-requis
+
+* Installer Node.js (v16 ou v17)
+* Installer les dépendances:
+
+```sh
+cd receiver
+npm install
+```
+
+
+## Lancer le serveur
+
+Dans le dossier du projet, lancer la commande suivante:
+
+```sh
+node ./backend/server.js
+```
+
+* La page du frontend sera accessible à l'adresse http://localhost:5001
+* Le serveur envoie les messages TUIO sur le port UDP 3333.
+
+
+## Gestion de la "section" d'identifiants de points
+
+Utiliser le paramètre d'URL `section` pour déterminer la "tranche" dans laquelle les identifiants de points seront compris, par exemple: http://localhost:5001?section=10000
+
+Idéalement, ne pas utiliser des centaines, après quelques minutes de tests, des identifiants supérieurs à 200 pouvaient déjà être notés, d'où l'exemple avec 10000.

+ 12 - 0
receiver/.editorconfig

@@ -0,0 +1,12 @@
+root = true
+
+[*]
+indent_style = tab
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.yml,*.yaml]
+indent_style = space
+indent_size = 2

+ 3 - 0
receiver/.gitattributes

@@ -0,0 +1,3 @@
+* text=auto
+*.js text eol=lf
+

+ 4 - 0
receiver/.gitignore

@@ -0,0 +1,4 @@
+node_modules
+yarn.lock
+.nyc_output
+coverage

+ 110 - 0
receiver/backend/osc-parser.js

@@ -0,0 +1,110 @@
+// OSC parsing based on node-osc
+
+module.exports = (function() {
+    var jspack = require("jspack").jspack,
+
+    decode = function(data) {
+        var message = [],
+        address = decodeString(data);
+        data = address.data;
+
+        if (address.value === "#bundle") {
+            data = decodeBundle(data, message);
+        } else if (data.length > 0) {
+            data = decodeMessage(address, data, message);
+        }
+
+        return message;
+    },
+
+    decodeBundle = function(data, message) {
+        var time = decodeTime(data),
+        bundleSize,
+        content;
+        
+        data = time.data;
+
+        message.push("#bundle");
+        message.push(time.value);
+
+        while (data.length > 0) {
+            bundleSize = decodeInt(data);
+            data = bundleSize.data;
+
+            content = data.slice(0, bundleSize.value);
+            message.push(decode(content));
+
+            data = data.slice(bundleSize.value, data.length);
+        }
+
+        return data;
+    },
+
+    decodeMessage = function(address, data, message) {
+        message.push(address.value);
+
+        var typeTags = decodeString(data);
+        data = typeTags.data;
+        typeTags = typeTags.value;
+
+        if (typeTags[0] === ",") {
+            for (var i = 1; i < typeTags.length; i++) {
+                var arg = decodeByTypeTag(typeTags[i], data);
+                data = arg.data;
+                message.push(arg.value);
+            }
+        }
+
+        return data;
+    },
+
+    decodeByTypeTag = function(typeTag, data) {
+        switch (typeTag) {
+            case "i":
+                return decodeInt(data);
+            case "f":
+                return decodeFloat(data);
+            case "s":
+                return decodeString(data);
+        }
+    },
+
+    decodeInt = function(data) {
+        return {
+            value: jspack.Unpack(">i", data.slice(0, 4))[0],
+            data: data.slice(4)
+        };
+    },
+
+    decodeString = function(data) {
+        var end = 0;
+        while (data[end] && end < data.length) {
+            end++;
+        }
+        return {
+            value: data.toString("ascii", 0, end),
+            data: data.slice(Math.ceil((end + 1) / 4) * 4)
+        };
+    },
+
+    decodeFloat = function(data) {
+        return {
+            value: jspack.Unpack(">f", data.slice(0, 4))[0],
+            data: data.slice(4)
+        };
+    },
+
+    decodeTime = function(data) {
+        var time = jspack.Unpack(">LL", data.slice(0, 8)),
+        seconds = time[0],
+        fraction = time[1];
+        return {
+            value: seconds + fraction / 4294967296,
+            data: data.slice(8)
+        };
+    };
+
+    return {
+        decode: decode
+    };
+}());

+ 64 - 0
receiver/backend/server.js

@@ -0,0 +1,64 @@
+const express = require('express');
+const dgram = require('dgram');
+const oscParser = require('./osc-parser');
+const bodyParser = require('body-parser');
+const path = require('path');
+const app = express();
+
+const server = require('http').Server(app);
+const io = require('socket.io')(server, {
+	transports: ['websocket']
+});
+
+const onSocketListening = function() {
+	const address = udpSocket.address();
+	console.log("Serveur TUIO en écoute sur : " + address.address + ":" + address.port);
+};
+
+const onSocketConnection = function(socket) {
+	udpSocket.on("message", function(msg) {
+		socket.emit("osc", oscParser.decode(msg));
+	});
+};
+
+const udpSocket = dgram.createSocket('udp4');
+udpSocket.on('listening', onSocketListening);
+udpSocket.bind(3333, '127.0.0.1');
+
+//io.listen(server);
+
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({extended:true}));
+app.use(express.static('./frontend/assets'));
+
+app.get('/', function (req, res) {
+   res.sendFile('./frontend/index.html', { root: path.resolve(__dirname + '/..') })
+});
+
+app.get('/json', function (req, res) {
+   res.status(200).json({"message":"ok"})
+});
+
+io.sockets.on('connection', (socket) =>{
+   console.log(`Connecté au client ${socket.id}`);
+	 const dgramCallback = function (buf) {
+		console.log(oscParser.decode(buf));
+    socket.emit("osc", oscParser.decode(buf));
+  };
+
+  // forward UDP packets via socket.io
+  udpSocket.on("message", dgramCallback);
+
+  // prevent memory leak on disconnect
+  socket.on('disconnect', function (socket) {
+    udpSocket.removeListener('message', dgramCallback);
+  });
+});
+
+//io.sockets.on("connection", onSocketConnection);
+
+// on change app par server
+server.listen(5000, function () {
+ console.log('App frontend disponible sur localhost:5000 !')
+});
+

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 9404 - 0
receiver/frontend/assets/jquery-1.7.2.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 2976 - 0
receiver/frontend/assets/lodash.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 4240 - 0
receiver/frontend/assets/socket.io/socket.io.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1144 - 0
receiver/frontend/assets/tuio.js


+ 118 - 0
receiver/frontend/index.html

@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<meta charset="utf-8">
+	<meta name="viewport" content="width=device-width">
+	<title>Proof of concept - Objets tangibles</title>
+</head>
+
+<body>
+	<script src="/jquery-1.7.2.js"></script>
+	<script src="/lodash.js"></script>
+	<script src="/socket.io/socket.io.js"></script>
+	<script src="/tuio.js"></script>
+	<style>
+		body {
+			background: #000;
+			margin: 0;
+		}
+		.tuioCursor {
+			background: #fff;
+			height: 8px;
+			left: 0;
+			position: absolute;
+			top: 0;
+			width: 8px;
+		}
+	</style>
+
+	<script>
+				function getHypotenuse(touch1, touch2, screenW, screenH) {
+					var w = Math.abs(touch1.w - touch2.w);
+					var h = Math.abs(touch1.h - touch2.h);
+					return Math.sqrt(Math.pow(w, 2) + Math.pow(h, 2));
+				}
+				$(function() {
+						var client = new Tuio.Client({
+								host: 'http://localhost:5000'
+						}),
+						screenW = $(document).width(),
+						screenH = $(document).height()
+
+						cursors = {},
+
+						onConnect = function() {
+							console.log('connected');
+						},
+
+						onAddTuioCursor = function(addCursor) {
+							var $addCursor = $('<div class="tuioCursor"></div>');
+							$('body').append($addCursor);
+							cursors[addCursor.getCursorId()] = $addCursor;
+							onUpdateTuioCursor(addCursor);
+						},
+
+						onUpdateTuioCursor = function(updateCursor) {
+							var $updateCursor = cursors[updateCursor.getCursorId()];
+							$updateCursor.css({
+								left: updateCursor.getScreenX(screenW),
+								top: updateCursor.getScreenY(screenH)
+							});
+
+							var cursorIds = Object.keys(cursors);
+							var dots = [];
+							cursorIds.forEach(function(cursor) {
+								dots.push({
+									id: cursor,
+									w: cursors[cursor][0].offsetLeft,
+									h: cursors[cursor][0].offsetTop
+								});
+							});
+
+							var groups = {};
+							for (var i = 0; i < dots.length; i++) {
+								for (var j = 0; j < dots.length; j++) {
+									if (j !== i) {
+										var hyp = getHypotenuse(dots[i], dots[j]);
+										console.log(i, j, hyp);
+									}
+								}
+							}
+						},
+
+						onRemoveTuioCursor = function(removeCursor) {
+							//console.log('remove', removeCursor);
+							var $removeCursor = cursors[removeCursor.getCursorId()];
+							$removeCursor.remove();
+							delete[removeCursor.getCursorId()];
+						},
+
+						onAddTuioObject = function(addObject) {
+							console.log(addObject);
+						},
+
+						onUpdateTuioObject = function(updateObject) {
+							console.log(updateObject);
+						},
+
+						onRemoveTuioObject = function(removeObject) {
+							console.log(removeObject);
+						},
+
+						onRefresh = function(time) {
+
+						};
+
+						client.on("connect", onConnect);
+						client.on("addTuioCursor", onAddTuioCursor);
+						client.on("updateTuioCursor", onUpdateTuioCursor);
+						client.on("removeTuioCursor", onRemoveTuioCursor);
+						client.on("addTuioObject", onAddTuioObject);
+						client.on("updateTuioObject", onUpdateTuioObject);
+						client.on("removeTuioObject", onRemoveTuioObject);
+						client.on("refresh", onRefresh);
+						client.connect();
+					});
+		</script>
+</body>
+</html>

+ 10 - 0
receiver/license

@@ -0,0 +1,10 @@
+MIT License
+
+Copyright (c) t1st3 <contact@t1st3.com> (t1st3.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1262 - 0
receiver/package-lock.json


+ 13 - 0
receiver/package.json

@@ -0,0 +1,13 @@
+{
+  "name": "poc-objets-tangibles",
+  "version": "0.1.0",
+  "description": "",
+  "main": "index.js",
+  "dependencies": {
+    "body-parser": "^1.19.0",
+    "dgram": "^1.0.1",
+    "express": "^4.17.1",
+    "jspack": "0.0.4",
+    "socket.io": "^4.3.2"
+  }
+}

+ 61 - 0
receiver/readme.md

@@ -0,0 +1,61 @@
+# objets tangibles - receiver
+
+> Application basée sur Node.js et Express 4 qui reçoit des datagrammes TUIO et les transmet à une page web via web-sockets
+
+## Description
+
+Le projet fourni est composé de 2 parties:
+* un backend
+* un frontend
+
+Le backend est une application Node.js basée sur Express; il officie comme récepteur de données TUIO et les renvoie vers le frontend au moyen d'un web-socket.
+
+Le frontend est une page web, servie par le backend, qui reçoit les événements émis par le serveur grâce au web-socket.
+
+Chaque partie est contenue dans le dossier correspondant.
+
+
+## Pré-requis
+
+* Installer Node.js (v16 ou v17)
+* Installer les dépendances:
+
+```sh
+cd receiver
+npm install
+```
+
+
+## Lancer le serveur
+
+Dans le dossier du projet, lancer la commande suivante:
+
+```sh
+node ./backend/server.js
+```
+
+* La page du frontend sera accessible à l'adresse http://localhost:5000
+* Le serveur écoute les messages TUIO sur le port UDP 3333.
+
+## Tester la réception de messages TUIO
+
+On utilise le simulateur TUIO pour envoyer des messages. Par défaut, celui-ci émet également sur le port UDP 3333.
+Ce simulateur a été inclus à la racine du projet.
+
+Sur GNU/linux, le simulateur peut être lancé avec la commande suivante:
+
+```sh
+java -Djavax.accessibility.assistive_technologies=" " -jar TuioSimulator.jar
+```
+
+Note: le laguage Java doit être installé sur la machine.
+
+
+
+## État des lieux
+
+Lors de la réception des messages TUIO, la page web calcule la distance entre chaque point enregistré (via théorème de Pythagore), et l'affiche en console (!! la console du navigateur, accessible via F12 sur la page web).
+
+C'est à partir de ces distances entre points qu'on pourra déterminer si ils font partie d'un même groupe. Cette partie là n'est finalement pas fournie ce soir...
+
+