diff --git a/controller.html b/controller.html new file mode 100644 index 0000000..0caf018 --- /dev/null +++ b/controller.html @@ -0,0 +1,13 @@ + + + + + + + Segnapunti - Controller + + +
+ + + diff --git a/package-lock.json b/package-lock.json index 1a2ec93..e76b735 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,15 @@ "name": "segnapuntianto", "version": "0.0.0", "dependencies": { - "nosleep.js": "^0.12.0", + "express": "^5.2.1", "vue": "^3.2.47", - "wave-ui": "^3.3.0" + "vue-router": "^4.6.4", + "wave-ui": "^3.3.0", + "ws": "^8.19.0" }, "devDependencies": { "@vitejs/plugin-vue": "^4.1.0", + "concurrently": "^9.2.1", "vite": "^4.3.9", "vite-plugin-pwa": "^0.16.0" } @@ -384,19 +387,17 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", - "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", - "dev": true, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true, + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "engines": { "node": ">=6.9.0" } @@ -454,9 +455,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.4.tgz", - "integrity": "sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dependencies": { + "@babel/types": "^7.29.0" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -1701,14 +1705,12 @@ } }, "node_modules/@babel/types": { - "version": "7.22.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.4.tgz", - "integrity": "sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==", - "dev": true, + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dependencies": { - "@babel/helper-string-parser": "^7.21.5", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -2109,9 +2111,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.18", @@ -2314,106 +2316,112 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", - "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.28.tgz", + "integrity": "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==", "dependencies": { - "@babel/parser": "^7.21.3", - "@vue/shared": "3.3.4", + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.28", + "entities": "^7.0.1", "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", - "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.28.tgz", + "integrity": "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==", "dependencies": { - "@vue/compiler-core": "3.3.4", - "@vue/shared": "3.3.4" + "@vue/compiler-core": "3.5.28", + "@vue/shared": "3.5.28" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", - "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.28.tgz", + "integrity": "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==", "dependencies": { - "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.4", - "@vue/compiler-dom": "3.3.4", - "@vue/compiler-ssr": "3.3.4", - "@vue/reactivity-transform": "3.3.4", - "@vue/shared": "3.3.4", + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.28", + "@vue/compiler-dom": "3.5.28", + "@vue/compiler-ssr": "3.5.28", + "@vue/shared": "3.5.28", "estree-walker": "^2.0.2", - "magic-string": "^0.30.0", - "postcss": "^8.1.10", - "source-map-js": "^1.0.2" + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", - "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.28.tgz", + "integrity": "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==", "dependencies": { - "@vue/compiler-dom": "3.3.4", - "@vue/shared": "3.3.4" + "@vue/compiler-dom": "3.5.28", + "@vue/shared": "3.5.28" } }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==" + }, "node_modules/@vue/reactivity": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", - "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.28.tgz", + "integrity": "sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw==", "dependencies": { - "@vue/shared": "3.3.4" - } - }, - "node_modules/@vue/reactivity-transform": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", - "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", - "dependencies": { - "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.4", - "@vue/shared": "3.3.4", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.0" + "@vue/shared": "3.5.28" } }, "node_modules/@vue/runtime-core": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz", - "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.28.tgz", + "integrity": "sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ==", "dependencies": { - "@vue/reactivity": "3.3.4", - "@vue/shared": "3.3.4" + "@vue/reactivity": "3.5.28", + "@vue/shared": "3.5.28" } }, "node_modules/@vue/runtime-dom": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz", - "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.28.tgz", + "integrity": "sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA==", "dependencies": { - "@vue/runtime-core": "3.3.4", - "@vue/shared": "3.3.4", - "csstype": "^3.1.1" + "@vue/reactivity": "3.5.28", + "@vue/runtime-core": "3.5.28", + "@vue/shared": "3.5.28", + "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz", - "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.28.tgz", + "integrity": "sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg==", "dependencies": { - "@vue/compiler-ssr": "3.3.4", - "@vue/shared": "3.3.4" + "@vue/compiler-ssr": "3.5.28", + "@vue/shared": "3.5.28" }, "peerDependencies": { - "vue": "3.3.4" + "vue": "3.5.28" } }, "node_modules/@vue/shared": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", - "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.28.tgz", + "integrity": "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } }, "node_modules/acorn": { "version": "8.8.2", @@ -2443,6 +2451,15 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -2540,6 +2557,29 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -2611,6 +2651,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -2624,6 +2672,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001495", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001495.tgz", @@ -2658,6 +2733,20 @@ "node": ">=4" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2694,12 +2783,157 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/concurrently/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concurrently/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/core-js-compat": { "version": "3.30.2", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.2.tgz", @@ -2723,17 +2957,16 @@ } }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -2769,6 +3002,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, "node_modules/ejs": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", @@ -2790,6 +3049,31 @@ "integrity": "sha512-y4A7YfQcDGPAeSWM1IuoWzXpg9RY1nwHzHSwRtCSQFp9FgAVDgdWlFf0RbdWfLWQ2WUI+bddUgk5RgTjqRE6FQ==", "dev": true }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.21.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", @@ -2838,6 +3122,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", @@ -2915,6 +3226,11 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -2938,6 +3254,56 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3008,6 +3374,26 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -3017,6 +3403,22 @@ "is-callable": "^1.1.3" } }, + "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": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -3053,10 +3455,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.5", @@ -3094,16 +3498,33 @@ "node": ">=6.9.0" } }, - "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3115,6 +3536,18 @@ "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", "dev": true }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -3188,12 +3621,11 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3260,10 +3692,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "engines": { "node": ">= 0.4" }, @@ -3286,6 +3717,51 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/idb": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", @@ -3305,8 +3781,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/internal-slot": { "version": "1.0.5", @@ -3322,6 +3797,14 @@ "node": ">= 0.4" } }, + "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/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -3412,6 +3895,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3475,6 +3967,11 @@ "node": ">=0.10.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -3808,14 +4305,38 @@ } }, "node_modules/magic-string": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", - "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" - }, + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "engines": { - "node": ">=12" + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/merge-stream": { @@ -3846,6 +4367,29 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3869,15 +4413,14 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", @@ -3891,21 +4434,27 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-releases": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", "dev": true }, - "node_modules/nosleep.js": { - "version": "0.12.0", - "integrity": "sha512-9d1HbpKLh3sdWlhXMhU6MMH+wQzKkrgfRkYV0EBdvt99YJfj0ilCJrWRDYG2130Tm4GXbEoTCx5b34JSaP+HhA==" - }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true, + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3937,15 +4486,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } }, + "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-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -3961,10 +4528,19 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -3979,9 +4555,9 @@ } }, "node_modules/postcss": { - "version": "8.4.24", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", - "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -3997,9 +4573,9 @@ } ], "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -4017,6 +4593,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -4026,6 +4614,20 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4055,6 +4657,28 @@ "safe-buffer": "^5.1.0" } }, + "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": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -4143,6 +4767,15 @@ "jsesc": "bin/jsesc" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -4210,6 +4843,21 @@ "rollup": "^2.0.0" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4233,6 +4881,15 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4267,6 +4924,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -4276,6 +4938,31 @@ "semver": "bin/semver.js" } }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/serialize-javascript": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", @@ -4285,15 +4972,104 @@ "randombytes": "^2.1.0" } }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4312,9 +5088,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "engines": { "node": ">=0.10.0" } @@ -4345,6 +5121,28 @@ "deprecated": "Please use @jridgewell/sourcemap-codec instead", "dev": true }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", @@ -4423,6 +5221,18 @@ "node": ">=4" } }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", @@ -4501,15 +5311,6 @@ "node": ">=10" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4522,6 +5323,14 @@ "node": ">=8.0" } }, + "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/tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", @@ -4531,6 +5340,21 @@ "punycode": "^2.1.0" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, "node_modules/type-fest": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", @@ -4543,6 +5367,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", @@ -4633,6 +5470,14 @@ "node": ">= 10.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -4682,6 +5527,14 @@ "punycode": "^2.1.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { "version": "4.3.9", "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz", @@ -4770,15 +5623,37 @@ } }, "node_modules/vue": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz", - "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz", + "integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==", "dependencies": { - "@vue/compiler-dom": "3.3.4", - "@vue/compiler-sfc": "3.3.4", - "@vue/runtime-dom": "3.3.4", - "@vue/server-renderer": "3.3.4", - "@vue/shared": "3.3.4" + "@vue/compiler-dom": "3.5.28", + "@vue/compiler-sfc": "3.5.28", + "@vue/runtime-dom": "3.5.28", + "@vue/server-renderer": "3.5.28", + "@vue/shared": "3.5.28" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" } }, "node_modules/wave-ui": { @@ -5048,17 +5923,122 @@ "workbox-core": "7.0.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } } } } diff --git a/package.json b/package.json index 94f6c30..43157ae 100644 --- a/package.json +++ b/package.json @@ -6,16 +6,21 @@ "scripts": { "dev": "vite", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "start": "node server.js", + "serve": "vite build && node server.js" }, "dependencies": { - "nosleep.js": "^0.12.0", + "express": "^5.2.1", "vue": "^3.2.47", - "wave-ui": "^3.3.0" + "vue-router": "^4.6.4", + "wave-ui": "^3.3.0", + "ws": "^8.19.0" }, "devDependencies": { "@vitejs/plugin-vue": "^4.1.0", + "concurrently": "^9.2.1", "vite": "^4.3.9", "vite-plugin-pwa": "^0.16.0" } -} +} \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..8035738 --- /dev/null +++ b/server.js @@ -0,0 +1,82 @@ +import { createServer } from 'http' +import express from 'express' +import { WebSocketServer } from 'ws' +import { fileURLToPath } from 'url' +import { dirname, join } from 'path' +import { setupWebSocketHandler } from './src/websocket-handler.js' +import { printServerInfo } from './src/server-utils.js' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +// --- Configurazione del server --- + +const DISPLAY_PORT = process.env.PORT || 3000 +const CONTROLLER_PORT = process.env.CONTROLLER_PORT || 3001 + +// ======================================== +// Server Display (porta principale) +// ======================================== + +const displayApp = express() + +// Espone i file generati dalla build di Vite. +displayApp.use(express.static(join(__dirname, 'dist'))) + +// Fallback per SPA: restituisce `index.html` per tutte le route. +displayApp.get('/{*splat}', (_req, res) => { + res.sendFile(join(__dirname, 'dist', 'index.html')) +}) + +const displayServer = createServer(displayApp) + +// Inizializza il server WebSocket condiviso. +const wss = new WebSocketServer({ noServer: true }) +setupWebSocketHandler(wss) + +displayServer.on('upgrade', (request, socket, head) => { + const pathname = new URL(request.url, `http://${request.headers.host}`).pathname + if (pathname === '/ws') { + wss.handleUpgrade(request, socket, head, (ws) => { + wss.emit('connection', ws, request) + }) + } else { + socket.destroy() + } +}) + +displayServer.listen(DISPLAY_PORT, '0.0.0.0', () => { + console.log(`[Display] Server running on port ${DISPLAY_PORT}`) +}) + +// ======================================== +// Server Controller (porta separata) +// ======================================== + +const controllerApp = express() + +// Espone gli stessi file statici della build. +controllerApp.use(express.static(join(__dirname, 'dist'))) + +// Fallback: restituisce `controller.html` per tutte le route. +controllerApp.get('/{*splat}', (_req, res) => { + res.sendFile(join(__dirname, 'dist', 'controller.html')) +}) + +const controllerServer = createServer(controllerApp) + +// Gestisce l'upgrade WebSocket anche sulla porta del controller. +controllerServer.on('upgrade', (request, socket, head) => { + const pathname = new URL(request.url, `http://${request.headers.host}`).pathname + if (pathname === '/ws') { + wss.handleUpgrade(request, socket, head, (ws) => { + wss.emit('connection', ws, request) + }) + } else { + socket.destroy() + } +}) + +controllerServer.listen(CONTROLLER_PORT, '0.0.0.0', () => { + printServerInfo(DISPLAY_PORT, CONTROLLER_PORT) +}) diff --git a/src/App.vue b/src/App.vue index 8355651..98240ae 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,7 +1,3 @@ - - diff --git a/src/components/ControllerPage.vue b/src/components/ControllerPage.vue new file mode 100644 index 0000000..e6bc1c6 --- /dev/null +++ b/src/components/ControllerPage.vue @@ -0,0 +1,861 @@ + + + + + diff --git a/src/components/DisplayPage.vue b/src/components/DisplayPage.vue new file mode 100644 index 0000000..af90728 --- /dev/null +++ b/src/components/DisplayPage.vue @@ -0,0 +1,396 @@ + + + + + diff --git a/src/components/HomePage.vue b/src/components/HomePage.vue deleted file mode 100644 index 277b61d..0000000 --- a/src/components/HomePage.vue +++ /dev/null @@ -1,220 +0,0 @@ - - - diff --git a/src/components/HomePage/HomePage.html b/src/components/HomePage/HomePage.html deleted file mode 100644 index 4f9db8b..0000000 --- a/src/components/HomePage/HomePage.html +++ /dev/null @@ -1,240 +0,0 @@ -
- - Nome Home - Nome Guest - - - Modalità partita: - - 2/3 - - - 3/5 - - - - -
-
Formazione Home
-
- - - - - - - -
- - - - - - -
-
- -
-
Formazione Guest
-
- - - - - - - -
- - - - - - -
-
-
- - Inverti ordine - - Ok - -
- -
Scegli squadra
- - {{ sp.nomi.home }} - {{ sp.nomi.guest }} - -
- -
-
{{ sp.nomi[diaCambi.team] }}: CAMBIO
-
-
- - - -
-
- - - -
-
-
- - - CONFERMA - - -
-
- - - -
- - {{ sp.nomi.home }} - - - - {{ sp.punt.home }} - - set {{ sp.set.home }} -
- -
- - {{ sp.punt.guest }} - - - - {{ sp.nomi.guest }} - - set {{ sp.set.guest }} -
- - -
-
- {{ sp.form.home[x] }} -
-
-
-
- {{ sp.form.guest[x] }} -
-
-
- - - {{ sp.punt.home }} - {{ sp.punt.guest }} - - - -
- - - -
- - {{ sp.nomi.guest }} - - - - {{ sp.punt.guest }} - - set {{ sp.set.guest }} -
- -
- - {{ sp.punt.home }} - - - - {{ sp.nomi.home }} - - set {{ sp.set.home }} -
- - -
-
- {{ sp.form.guest[x] }} -
-
-
-
- {{ sp.form.home[x] }} -
-
-
- - - {{ sp.punt.guest }} - {{ sp.punt.home }} - - - -
- -
-
- {{ sp.nomi.home }} -
- {{String(h)}} -
-
-
- {{ sp.nomi.guest }} -
- {{String(h)}} -
-
-
- -
- - - - - - - - - - - - RESET - - - PUNTEGGIO - FORMAZIONI - - - CAMBI - - - STRISCIA - - - - - -
-
-
- diff --git a/src/components/HomePage/HomePage.js b/src/components/HomePage/HomePage.js deleted file mode 100644 index cb617fa..0000000 --- a/src/components/HomePage/HomePage.js +++ /dev/null @@ -1,479 +0,0 @@ -import NoSleep from "nosleep.js"; -export default { - name: "HomePage", - components: {}, - data() { - return { - order: true, - voices: null, - diaNomi: { - show: false, - home: "", - guest: "", - }, - diaCambi: { - show: false, - team: "home", - guest: { cambi: [{ in: "", out: "" }, { in: "", out: "" }] }, - home: { cambi: [{ in: "", out: "" }, { in: "", out: "" }] }, - }, - diaCambiTeam: { - show: false, - }, - visuForm: false, - visuButt: true, - visuStriscia: true, - modalitaPartita: "3/5", // "2/3" o "3/5" - sp: { - striscia: { home: [0], guest: [0] }, - servHome: true, - punt: { home: 0, guest: 0 }, - set: { home: 0, guest: 0 }, - nomi: { home: "Antoniana", guest: "Guest" }, - form: { - home: ["1", "2", "3", "4", "5", "6"], - guest: ["1", "2", "3", "4", "5", "6"], - }, - storicoServizio: [], // Stack per tracciare lo stato del servizio prima di ogni punto - }, - } - }, - mounted() { - this.voices = window.speechSynthesis.getVoices(); - if (this.isMobile()) { - this.speak(); - var noSleep = new NoSleep(); - noSleep.enable(); - document.documentElement.requestFullscreen(); - } - this.abilitaTastiSpeciali(); - }, - computed: { - isPunteggioZeroZero() { - return this.sp.punt.home === 0 && this.sp.punt.guest === 0; - }, - cambiConfermabili() { - const team = this.diaCambi.team; - const cambi = this.diaCambi[team].cambi || []; - let hasComplete = false; - let allValid = true; - - cambi.forEach((cambio) => { - const teamIn = (cambio.in || "").trim(); - const teamOut = (cambio.out || "").trim(); - - if (!teamIn && !teamOut) { - return; - } - if (!teamIn || !teamOut) { - allValid = false; - return; - } - hasComplete = true; - }); - - return allValid && hasComplete; - } - }, - methods: { - closeApp() { - var win = window.open("", "_self"); - win.close(); - }, - fullScreen() { - document.documentElement.requestFullscreen(); - }, - isMobile() { - if ( - /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( - navigator.userAgent - ) - ) { - return true; - } else { - return false; - } - }, - resetta() { - this.$waveui.notify("Punteggio
RESETTATO", "success"); - this.visuForm = false; - this.sp.punt.home = 0; - this.sp.punt.guest = 0; - this.sp.form = { - home: ["1", "2", "3", "4", "5", "6"], - guest: ["1", "2", "3", "4", "5", "6"], - } - this.sp.striscia = { home: [0], guest: [0] } - this.sp.storicoServizio = [] - }, - cambiaPalla() { - if (!this.isPunteggioZeroZero) { - this.$waveui.notify("Cambio palla consentito solo a inizio set (0-0)", "warning"); - return; - } - this.sp.servHome = !this.sp.servHome; - }, - incSet(team) { - if (this.sp.set[team] == 2) { - this.sp.set[team] = 0; - } else { - this.sp.set[team]++; - } - }, - incPunt(team) { - // Se il set è già terminato, evita ulteriori incrementi - if (this.checkVittoria()) { - return; - } - - // Salva lo stato del servizio PRIMA di modificarlo - this.sp.storicoServizio.push({ - servHome: this.sp.servHome, - cambioPalla: (team == "home" && !this.sp.servHome) || (team == "guest" && this.sp.servHome) - }); - - this.sp.punt[team]++; - if (team == 'home') { - this.sp.striscia.home.push(this.sp.punt.home) - this.sp.striscia.guest.push(' ') - } else { - this.sp.striscia.guest.push(this.sp.punt.guest) - this.sp.striscia.home.push(' ') - } - - // Ruota la formazione solo se c'è cambio palla (conquista del servizio) - const cambioPalla = (team == "home" && !this.sp.servHome) || (team == "guest" && this.sp.servHome); - - if (cambioPalla) { - this.sp.form[team].push(this.sp.form[team].shift()); - } - - this.sp.servHome = (team == "home"); - }, - checkVittoria() { - const puntHome = this.sp.punt.home; - const puntGuest = this.sp.punt.guest; - const setHome = this.sp.set.home; - const setGuest = this.sp.set.guest; - const totSet = setHome + setGuest; - - // Determina se siamo nel set decisivo in base alla modalità partita - let isSetDecisivo = false; - if (this.modalitaPartita === "2/3") { - // Tie-break al 3° set (quando totSet >= 2) - isSetDecisivo = totSet >= 2; - } else { - // Tie-break al 5° set (quando totSet >= 4) - isSetDecisivo = totSet >= 4; - } - - const punteggioVittoria = isSetDecisivo ? 15 : 25; - - // Vittoria con punteggio >= 25 (o 15 per set decisivo) e almeno 2 punti di vantaggio - if (puntHome >= punteggioVittoria && puntHome - puntGuest >= 2) { - return true; // Home ha vinto - } - if (puntGuest >= punteggioVittoria && puntGuest - puntHome >= 2) { - return true; // Guest ha vinto - } - - return false; - }, - decPunt() { - if (this.sp.striscia.home.length > 1 && this.sp.storicoServizio.length > 0) { - var tmpHome = this.sp.striscia.home.pop() - var tmpGuest = this.sp.striscia.guest.pop() - var statoServizio = this.sp.storicoServizio.pop() // Recupera lo stato completo del servizio - - if (tmpHome == ' ') { - this.sp.punt.guest-- - // Ruota indietro solo se c'era stato un cambio palla - if (statoServizio.cambioPalla) { - this.sp.form.guest.unshift(this.sp.form.guest.pop()); - } - } else { - this.sp.punt.home-- - // Ruota indietro solo se c'era stato un cambio palla - if (statoServizio.cambioPalla) { - this.sp.form.home.unshift(this.sp.form.home.pop()); - } - } - - // Ripristina il servizio allo stato precedente - this.sp.servHome = statoServizio.servHome; - - } - }, - // decPunt(team) { - // // decrementa il punteggio se è > 0. - // if (this.sp.punt[team] > 0) { - // this.sp.punt[team]--; - // this.sp.striscia.home.pop() - // this.sp.striscia.guest.pop() - // this.sp.form[team].unshift(this.sp.form[team].pop()); - // } - // }, - speak() { - const msg = new SpeechSynthesisUtterance(); - if (this.sp.punt.home + this.sp.punt.guest == 0) { - msg.text = "zero a zero"; - } else if (this.sp.punt.home == this.sp.punt.guest) { - msg.text = this.sp.punt.home + " pari"; - } else { - if (this.sp.servHome) { - msg.text = this.sp.punt.home + " a " + this.sp.punt.guest; - } else { - msg.text = this.sp.punt.guest + " a " + this.sp.punt.home; - } - } - // msg.volume = 1.0; // speech volume (default: 1.0) - // msg.pitch = 1.0; // speech pitch (default: 1.0) - // msg.rate = 1.0; // speech rate (default: 1.0) - // msg.lang = 'it_IT'; // speech language (default: 'en-US') - const voices = window.speechSynthesis.getVoices(); - msg.voice = voices.find(voice => voice.name === 'Google italiano'); - // voice URI (default: platform-dependent) - // msg.onboundary = function (event) { - // console.log('Speech reached a boundary:', event.name); - // }; - // msg.onpause = function (event) { - // console.log('Speech paused:', event.utterance.text.substring(event.charIndex)); - // }; - window.speechSynthesis.speak(msg); - }, - apriDialogConfig() { - this.disabilitaTastiSpeciali(); - this.diaNomi.show = true; - - // Aggiungi gestore Tab per il dialog - this.dialogConfigTabHandler = (e) => { - if (e.key === 'Tab' && this.diaNomi.show) { - e.preventDefault(); - e.stopPropagation(); - - const dialog = document.querySelector('.w-dialog'); - if (!dialog) return; - - const allInputs = Array.from(dialog.querySelectorAll('input[type="text"]')) - .sort((a, b) => { - const tabA = parseInt(a.closest('[tabindex]')?.getAttribute('tabindex') || '0'); - const tabB = parseInt(b.closest('[tabindex]')?.getAttribute('tabindex') || '0'); - return tabA - tabB; - }); - - if (allInputs.length === 0) return; - - // Verifica se il focus è già dentro il dialog - const focusInDialog = dialog.contains(document.activeElement); - - // Se non è nel dialog o non è in un input, vai al primo - if (!focusInDialog || !allInputs.includes(document.activeElement)) { - allInputs[0].focus(); - return; - } - - // Navigazione normale tra i campi - const currentIndex = allInputs.indexOf(document.activeElement); - let nextIndex; - - if (e.shiftKey) { - nextIndex = currentIndex <= 0 ? allInputs.length - 1 : currentIndex - 1; - } else { - nextIndex = currentIndex >= allInputs.length - 1 ? 0 : currentIndex + 1; - } - - if (allInputs[nextIndex]) { - allInputs[nextIndex].focus(); - } - } - }; - window.addEventListener('keydown', this.dialogConfigTabHandler, true); - - // Focus immediato + retry con timeout - this.$nextTick(() => { - const focusFirst = () => { - const dialog = document.querySelector('.w-dialog'); - if (dialog) { - const firstInput = dialog.querySelector('input[type="text"]'); - if (firstInput) { - firstInput.focus(); - firstInput.select(); - return true; - } - } - return false; - }; - - // Prova immediatamente - if (!focusFirst()) { - // Se fallisce, riprova dopo un breve delay - setTimeout(focusFirst, 100); - } - }); - }, - chiudiDialogConfig() { - if (this.dialogConfigTabHandler) { - window.removeEventListener('keydown', this.dialogConfigTabHandler, true); - this.dialogConfigTabHandler = null; - } - this.abilitaTastiSpeciali(); - }, - resettaCambi(team) { - const teams = team ? [team] : ["home", "guest"]; - teams.forEach((t) => { - this.diaCambi[t].cambi.forEach((cambio) => { - cambio.in = ""; - cambio.out = ""; - }); - }); - }, - apriDialogCambi() { - this.disabilitaTastiSpeciali(); - this.diaCambiTeam.show = true; - }, - apriDialogCambiTeam(team) { - this.disabilitaTastiSpeciali(); - this.diaCambi.team = team; - this.resettaCambi(team); - this.diaCambi.show = true; - }, - selezionaTeamCambi(team) { - this.diaCambiTeam.show = false; - this.apriDialogCambiTeam(team); - }, - chiudiDialogCambi() { - this.diaCambi.show = false; - this.resettaCambi(this.diaCambi.team); - this.abilitaTastiSpeciali(); - }, - confermaCambi() { - if (!this.cambiConfermabili) { - return; - } - - const team = this.diaCambi.team; - const cambi = (this.diaCambi[team].cambi || []) - .map((cambio) => ({ - team, - in: (cambio.in || "").trim(), - out: (cambio.out || "").trim(), - })) - .filter((cambio) => cambio.in || cambio.out); - - const form = this.sp.form[team].map((val) => String(val).trim()); - const formAggiornata = [...form]; - - for (const cambio of cambi) { - if (!/^\d+$/.test(cambio.in) || !/^\d+$/.test(cambio.out)) { - this.$waveui.notify("Inserisci solo numeri nei campi", "warning"); - return; - } - if (cambio.in === cambio.out) { - this.$waveui.notify(`Numero IN e OUT uguali per ${cambio.team}`, "warning"); - return; - } - if (formAggiornata.includes(cambio.in)) { - this.$waveui.notify(`Numero ${cambio.in} già presente in formazione ${cambio.team}`, "warning"); - return; - } - if (!formAggiornata.includes(cambio.out)) { - this.$waveui.notify(`Numero ${cambio.out} non presente in formazione ${cambio.team}`, "warning"); - return; - } - - const idx = formAggiornata.findIndex((val) => String(val).trim() === cambio.out); - if (idx !== -1) { - formAggiornata.splice(idx, 1, cambio.in); - } - } - - this.sp.form[team] = formAggiornata; - - this.chiudiDialogCambi(); - }, - disabilitaTastiSpeciali() { - window.removeEventListener("keydown", this.funzioneTastiSpeciali); - }, - abilitaTastiSpeciali() { - window.addEventListener("keydown", this.funzioneTastiSpeciali); - }, - funzioneTastiSpeciali(e) { - if (this.diaNomi.show || this.diaCambi.show || this.diaCambiTeam.show) { - return; - } - - const target = e.target; - const path = typeof e.composedPath === "function" ? e.composedPath() : []; - const elements = [target, ...path].filter(Boolean); - const isTypingField = elements.some((el) => { - if (!el || !el.tagName) { - return false; - } - const tag = String(el.tagName).toLowerCase(); - if (tag === "input" || tag === "textarea") { - return true; - } - if (el.isContentEditable) { - return true; - } - if (el.classList && (el.classList.contains("w-input") || el.classList.contains("w-textarea"))) { - return true; - } - const contentEditable = el.getAttribute && el.getAttribute("contenteditable"); - return contentEditable === "true"; - }); - if (isTypingField) { - return; - } - - let handled = false; - if (e.ctrlKey && e.key == "m") { - this.diaNomi.show = true - handled = true; - } else if (e.ctrlKey && e.key == "b") { - this.visuButt = !this.visuButt - handled = true; - } else if (e.ctrlKey && e.key == "f") { - document.documentElement.requestFullscreen(); - handled = true; - } else if (e.ctrlKey && e.key == "s") { - this.speak(); - handled = true; - } else if (e.ctrlKey && e.key == "z") { - this.visuForm = !this.visuForm - handled = true; - } else if (e.ctrlKey && e.key == "ArrowUp") { - this.incPunt("home") - handled = true; - } else if (e.ctrlKey && e.key == "ArrowDown") { - this.decPunt("home") - handled = true; - } else if (e.ctrlKey && e.key == "ArrowRight") { - this.incSet("home") - handled = true; - } else if (e.shiftKey && e.key == "ArrowUp") { - this.incPunt("guest") - handled = true; - } else if (e.shiftKey && e.key == "ArrowDown") { - this.decPunt("guest") - handled = true; - } else if (e.shiftKey && e.key == "ArrowRight") { - this.incSet("guest") - handled = true; - } else if (e.ctrlKey && e.key == "ArrowLeft") { - this.cambiaPalla() - handled = true; - } else if (e.ctrlKey && (e.key == "c" || e.key == "C")) { - this.apriDialogCambiTeam("home") - handled = true; - } else if (e.shiftKey && (e.key == "c" || e.key == "C")) { - this.apriDialogCambiTeam("guest") - handled = true; - } else { return false } - - if (handled) { - e.preventDefault(); - } - } - } -} diff --git a/src/components/HomePage/HomePage.scss b/src/components/HomePage/HomePage.scss deleted file mode 100644 index b1a9d99..0000000 --- a/src/components/HomePage/HomePage.scss +++ /dev/null @@ -1,112 +0,0 @@ -.homepage { - :root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - touch-action: pan-x pan-y; - height: 100% - } - - body { - overscroll-behavior-y: contain; - margin: 0; - place-items: center; - min-width: 320px; - width: 100%; - min-height: 100vh; - background-color: #000; - } - - button { - margin-left: 10px; - margin-right: 10px; - border-radius: 8px; - border: 1px solid #fff; - padding: 0.6em 1.2em; - font-size: 0.8em; - font-weight: 500; - font-family: inherit; - color: #fff; - background-color: #000; - cursor: pointer; - transition: border-color 0.25s; - } - button:hover { - border-color: #646cff; - background-color: #333; - } - button:focus, button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; - } - - #app { - margin: 0 auto; - text-align: center; - } - .campo { - user-select: none; - width: 100%; - display: table; - color: #fff; - } - .hea { - float: left; - width: 50%; - font-size: xx-large; - } - .hea span { - /* border: 1px solid #f3fb00; */ - padding-left: 10px; - padding-right: 10px; - border-radius: 5px; - } - .tal { - text-align: left; - } - .tar { - text-align: right; - } - .bot { - position: fixed; - left: 0; - bottom: 0; - width: 100%; - margin-top: 10px; - margin-bottom: 1px; - background-color: #111; - } - .col { - margin-left: auto; - margin-right: auto; - text-align: center; - float: left; - width: 50%; - } - .punt { - font-size: 60vh; - } - .form { - font-size: 5vh; - border-top: #fff dashed 25px; - padding-top: 50px; - } - .formtit { - font-size: 5vh; - margin-top: 30px; - margin-bottom: 20px; - } - .formdiv { - font-size: 20vh; - float: left; - width: 32%; - } - .home { - background-color: black; - color: yellow; - } - .guest { - background-color: blue; - color: white - } - .item-stri { - background-color: #fff; - } -} \ No newline at end of file diff --git a/src/components/HomePage/index.vue b/src/components/HomePage/index.vue deleted file mode 100644 index 156fdba..0000000 --- a/src/components/HomePage/index.vue +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/controller-main.js b/src/controller-main.js new file mode 100644 index 0000000..ff48a92 --- /dev/null +++ b/src/controller-main.js @@ -0,0 +1,9 @@ +import { createApp } from 'vue' +import './style.css' +import WaveUI from 'wave-ui' +import 'wave-ui/dist/wave-ui.css' +import ControllerPage from './components/ControllerPage.vue' + +const app = createApp(ControllerPage) +app.use(WaveUI) +app.mount('#app') diff --git a/src/gameState.js b/src/gameState.js new file mode 100644 index 0000000..66b980f --- /dev/null +++ b/src/gameState.js @@ -0,0 +1,204 @@ +/** + * Logica di gioco condivisa per il segnapunti. + * Utilizzata sia dal server WebSocket sia dal client per l'anteprima locale. + */ + +export function createInitialState() { + return { + order: true, + visuForm: false, + visuStriscia: true, + modalitaPartita: "3/5", + sp: { + striscia: { home: [0], guest: [0] }, + servHome: true, + punt: { home: 0, guest: 0 }, + set: { home: 0, guest: 0 }, + nomi: { home: "Antoniana", guest: "Guest" }, + form: { + home: ["1", "2", "3", "4", "5", "6"], + guest: ["1", "2", "3", "4", "5", "6"], + }, + storicoServizio: [], + }, + } +} + +export function checkVittoria(state) { + const puntHome = state.sp.punt.home + const puntGuest = state.sp.punt.guest + const setHome = state.sp.set.home + const setGuest = state.sp.set.guest + const totSet = setHome + setGuest + + let isSetDecisivo = false + if (state.modalitaPartita === "2/3") { + isSetDecisivo = totSet >= 2 + } else { + isSetDecisivo = totSet >= 4 + } + + const punteggioVittoria = isSetDecisivo ? 15 : 25 + + if (puntHome >= punteggioVittoria && puntHome - puntGuest >= 2) { + return true + } + if (puntGuest >= punteggioVittoria && puntGuest - puntHome >= 2) { + return true + } + + return false +} + +export function applyAction(state, action) { + // Esegue un deep clone per evitare mutazioni indesiderate dello stato lato server. + // Restituisce sempre un nuovo oggetto di stato. + const s = JSON.parse(JSON.stringify(state)) + + switch (action.type) { + case "incPunt": { + const team = action.team + if (checkVittoria(s)) break + + s.sp.storicoServizio.push({ + servHome: s.sp.servHome, + cambioPalla: (team === "home" && !s.sp.servHome) || (team === "guest" && s.sp.servHome), + }) + + s.sp.punt[team]++ + if (team === "home") { + s.sp.striscia.home.push(s.sp.punt.home) + s.sp.striscia.guest.push(" ") + } else { + s.sp.striscia.guest.push(s.sp.punt.guest) + s.sp.striscia.home.push(" ") + } + + const cambioPalla = (team === "home" && !s.sp.servHome) || (team === "guest" && s.sp.servHome) + if (cambioPalla) { + s.sp.form[team].push(s.sp.form[team].shift()) + } + + s.sp.servHome = team === "home" + break + } + + case "decPunt": { + if (s.sp.striscia.home.length > 1 && s.sp.storicoServizio.length > 0) { + const tmpHome = s.sp.striscia.home.pop() + s.sp.striscia.guest.pop() + const statoServizio = s.sp.storicoServizio.pop() + + if (tmpHome === " ") { + s.sp.punt.guest-- + if (statoServizio.cambioPalla) { + s.sp.form.guest.unshift(s.sp.form.guest.pop()) + } + } else { + s.sp.punt.home-- + if (statoServizio.cambioPalla) { + s.sp.form.home.unshift(s.sp.form.home.pop()) + } + } + s.sp.servHome = statoServizio.servHome + } + break + } + + case "incSet": { + const team = action.team + if (s.sp.set[team] === 2) { + s.sp.set[team] = 0 + } else { + s.sp.set[team]++ + } + break + } + + case "cambiaPalla": { + if (s.sp.punt.home === 0 && s.sp.punt.guest === 0) { + s.sp.servHome = !s.sp.servHome + } + break + } + + case "resetta": { + s.visuForm = false + s.sp.punt.home = 0 + s.sp.punt.guest = 0 + s.sp.form = { + home: ["1", "2", "3", "4", "5", "6"], + guest: ["1", "2", "3", "4", "5", "6"], + } + s.sp.striscia = { home: [0], guest: [0] } + s.sp.storicoServizio = [] + break + } + + case "toggleFormazione": { + s.visuForm = !s.visuForm + break + } + + case "toggleStriscia": { + s.visuStriscia = !s.visuStriscia + break + } + + case "toggleOrder": { + s.order = !s.order + break + } + + case "setNomi": { + if (action.home !== undefined) s.sp.nomi.home = action.home + if (action.guest !== undefined) s.sp.nomi.guest = action.guest + break + } + + case "setModalita": { + s.modalitaPartita = action.modalita + break + } + + case "setFormazione": { + if (action.team && action.form) { + s.sp.form[action.team] = [...action.form] + } + break + } + + case "confermaCambi": { + const team = action.team + const cambi = action.cambi || [] + const form = s.sp.form[team].map((val) => String(val).trim()) + const formAggiornata = [...form] + + let valid = true + for (const cambio of cambi) { + const cin = (cambio.in || "").trim() + const cout = (cambio.out || "").trim() + if (!cin || !cout) continue + if (!/^\d+$/.test(cin) || !/^\d+$/.test(cout)) { valid = false; break } + if (cin === cout) { valid = false; break } + if (formAggiornata.includes(cin)) { valid = false; break } + if (!formAggiornata.includes(cout)) { valid = false; break } + + const idx = formAggiornata.findIndex((val) => String(val).trim() === cout) + if (idx !== -1) { + formAggiornata.splice(idx, 1, cin) + } + } + + if (valid) { + s.sp.form[team] = formAggiornata + } + break + } + + default: + break + } + + return s +} diff --git a/src/main.js b/src/main.js index 5018ffd..a61ec98 100644 --- a/src/main.js +++ b/src/main.js @@ -3,8 +3,10 @@ import './style.css' import App from './App.vue' import WaveUI from 'wave-ui' import 'wave-ui/dist/wave-ui.css' +import DisplayPage from './components/DisplayPage.vue' -const app = createApp(App) - +// In modalità display-only, non serve il router. +// Il display viene montato direttamente. +const app = createApp(DisplayPage) app.use(WaveUI) app.mount('#app') diff --git a/src/server-utils.js b/src/server-utils.js new file mode 100644 index 0000000..70b346a --- /dev/null +++ b/src/server-utils.js @@ -0,0 +1,46 @@ +import { networkInterfaces } from 'os' + +/** + * Restituisce gli indirizzi IP di rete del sistema, escludendo loopback e bridge Docker. + * @returns {string[]} Elenco degli indirizzi IP disponibili. + */ +export function getNetworkIPs() { + const nets = networkInterfaces() + const networkIPs = [] + + for (const name of Object.keys(nets)) { + for (const net of nets[name]) { + // Esclude loopback (127.0.0.1), indirizzi non IPv4 e bridge Docker (172.17.x.x, 172.18.x.x). + if (net.family === 'IPv4' && + !net.internal && + !net.address.startsWith('172.17.') && + !net.address.startsWith('172.18.')) { + networkIPs.push(net.address) + } + } + } + + return networkIPs +} + +/** + * Stampa il riepilogo di avvio del server con gli URL di accesso. + * @param {number} displayPort - Porta del display. + * @param {number} controllerPort - Porta del controller. + */ +export function printServerInfo(displayPort = 5173, controllerPort = 3001) { + const networkIPs = getNetworkIPs() + + console.log(`\nSegnapunti Server`) + console.log(` Display: http://127.0.0.1:${displayPort}/`) + console.log(` Controller: http://127.0.0.1:${controllerPort}/`) + + if (networkIPs.length > 0) { + console.log(`\n Controller da dispositivi remoti:`) + networkIPs.forEach(ip => { + console.log(` http://${ip}:${controllerPort}/`) + }) + } + + console.log() +} diff --git a/src/style.css b/src/style.css index f00b946..9ee314f 100644 --- a/src/style.css +++ b/src/style.css @@ -53,7 +53,7 @@ button:focus-visible { font-size: xx-large; } .hea span { -/* border: 1px solid #f3fb00; */ +/* Bordo di debug: border: 1px solid #f3fb00; */ padding-left: 10px; padding-right: 10px; border-radius: 5px; diff --git a/src/websocket-handler.js b/src/websocket-handler.js new file mode 100644 index 0000000..5941bb8 --- /dev/null +++ b/src/websocket-handler.js @@ -0,0 +1,171 @@ +import { createInitialState, applyAction } from './gameState.js' + +/** + * Crea e configura il server WebSocket per la gestione dello stato di gioco. + * @param {WebSocketServer} wss - Istanza del server WebSocket. + * @returns {Object} Oggetto con metodi di gestione dello stato. + */ +export function setupWebSocketHandler(wss) { + // Stato globale della partita. + let gameState = createInitialState() + + // Mappa dei ruoli associati ai client connessi. + const clients = new Map() // ws -> { role: 'display' | 'controller' } + + /** + * Gestisce i messaggi in arrivo dal client + */ + function handleMessage(ws, data) { + try { + // Converte il payload in stringa in modo sicuro, anche se arriva come Buffer. + const dataStr = typeof data === 'string' ? data : data.toString('utf8') + const msg = JSON.parse(dataStr) + + if (msg.type === 'register') { + return handleRegister(ws, msg) + } + + if (msg.type === 'action') { + return handleAction(ws, msg) + } + } catch (err) { + console.error('Error processing message:', err, 'data:', data) + // Invia l'errore solo se la connessione e ancora aperta. + if (ws.readyState === 1) { // Stato WebSocket.OPEN + try { + sendError(ws, 'Invalid message format') + } catch (sendErr) { + console.error('Error sending error message:', sendErr) + } + } + } + } + + /** + * Gestisce la registrazione di un client (display o controller) + */ + function handleRegister(ws, msg) { + const role = msg.role || 'display' + + // Valida il ruolo dichiarato dal client. + if (!['display', 'controller'].includes(role)) { + sendError(ws, 'Invalid role') + return + } + + clients.set(ws, { role }) + console.log(`[WebSocket] Client registered as: ${role} (total clients: ${clients.size})`) + + // Invia subito lo stato corrente, se la connessione e aperta. + if (ws.readyState === 1) { // Stato WebSocket.OPEN + try { + ws.send(JSON.stringify({ type: 'state', state: gameState })) + } catch (err) { + console.error('Error sending initial state:', err) + } + } + } + + /** + * Gestisce un'azione di gioco dal controller + */ + function handleAction(ws, msg) { + // Solo i client controller possono inviare azioni. + const client = clients.get(ws) + if (!client || client.role !== 'controller') { + sendError(ws, 'Only controllers can send actions') + return + } + + // Verifica il formato dell'azione ricevuta. + if (!msg.action || !msg.action.type) { + sendError(ws, 'Invalid action format') + return + } + + // Applica l'azione allo stato della partita. + const previousState = gameState + try { + gameState = applyAction(gameState, msg.action) + } catch (err) { + console.error('Error applying action:', err) + sendError(ws, 'Failed to apply action') + gameState = previousState + return + } + + // Propaga il nuovo stato a tutti i client connessi. + broadcastState() + } + + /** + * Invia un messaggio di errore al client + */ + function sendError(ws, message) { + if (ws.readyState === 1) { // Stato WebSocket.OPEN + try { + ws.send(JSON.stringify({ type: 'error', message })) + } catch (err) { + console.error('Failed to send error message:', err) + } + } + } + + /** + * Invia lo stato corrente a tutti i client connessi. + */ + function broadcastState() { + const stateMsg = JSON.stringify({ type: 'state', state: gameState }) + wss.clients.forEach((client) => { + if (client.readyState === 1) { // Stato WebSocket.OPEN + client.send(stateMsg) + } + }) + } + + /** + * Gestisce la chiusura della connessione + */ + function handleClose(ws) { + const client = clients.get(ws) + const role = client?.role || 'unknown' + console.log(`[WebSocket] Client disconnected (role: ${role})`) + clients.delete(ws) + } + + /** + * Gestisce gli errori WebSocket + */ + function handleError(err, ws) { + console.error('WebSocket error:', err) + + // In caso di frame non validi, chiude forzatamente la connessione. + if (err.code === 'WS_ERR_INVALID_CLOSE_CODE' || err.code === 'WS_ERR_INVALID_UTF8') { + try { + if (ws && ws.readyState === 1) { // Stato WebSocket.OPEN + ws.terminate() // Chiusura forzata senza handshake di chiusura. + } + } catch (closeErr) { + console.error('Error closing connection:', closeErr) + } + } + } + + // Registra gli handler per ogni nuova connessione. + wss.on('connection', (ws) => { + // Imposta il tipo binario per ridurre i problemi di codifica. + ws.binaryType = 'arraybuffer' + + ws.on('message', (data) => handleMessage(ws, data)) + ws.on('close', () => handleClose(ws)) + ws.on('error', (err) => handleError(err, ws)) + }) + + // Espone un'API pubblica per controllo esterno, se necessario. + return { + getState: () => gameState, + setState: (newState) => { gameState = newState }, + broadcastState, + getClients: () => clients, + } +} diff --git a/vite-plugin-websocket.js b/vite-plugin-websocket.js new file mode 100644 index 0000000..9ba79aa --- /dev/null +++ b/vite-plugin-websocket.js @@ -0,0 +1,131 @@ +import { WebSocketServer } from 'ws' +import { createServer as createHttpServer, request as httpRequest } from 'http' +import { setupWebSocketHandler } from './src/websocket-handler.js' +import { printServerInfo } from './src/server-utils.js' + +const CONTROLLER_PORT = 3001 +const DEV_PROXY_HOST = process.env.DEV_PROXY_HOST || '127.0.0.1' + +/** + * Plugin Vite che integra un server WebSocket per la gestione dello stato di gioco + * e un server separato sulla porta 3001 per il controller. + * @returns {import('vite').Plugin} + */ +export default function websocketPlugin() { + return { + name: 'vite-plugin-websocket', + configureServer(server) { + // Inizializza un server WebSocket collegato al server HTTP di Vite. + const wss = new WebSocketServer({ noServer: true }) + + // Registra i gestori WebSocket con la logica di gioco. + setupWebSocketHandler(wss) + + // Intercetta le richieste di upgrade WebSocket solo sul path /ws. + server.httpServer.on('upgrade', (request, socket, head) => { + const pathname = new URL(request.url, `http://${request.headers.host}`).pathname + + if (pathname === '/ws') { + wss.handleUpgrade(request, socket, head, (ws) => { + wss.emit('connection', ws, request) + }) + } + }) + + // Avvia un server separato per il controller sulla porta 3001. + server.httpServer.once('listening', () => { + const viteAddr = server.httpServer.address() + const vitePort = viteAddr.port + + startControllerDevServer(vitePort, wss) + + setTimeout(() => printServerInfo(vitePort, CONTROLLER_PORT), 100) + }) + } + } +} + +/** + * Avvia il server di sviluppo per il controller. + * Fa da proxy verso il dev server di Vite per moduli ES, HMR, e asset. + */ +function startControllerDevServer(vitePort, wss) { + const controllerServer = createHttpServer((req, res) => { + // Se richiesta alla root, riscrive verso controller.html + let targetPath = req.url + if (targetPath === '/' || targetPath === '') { + targetPath = '/controller.html' + } + + // Proxy verso il dev server di Vite + const proxyReq = httpRequest( + { + hostname: DEV_PROXY_HOST, + port: vitePort, + path: targetPath, + method: req.method, + headers: { + ...req.headers, + host: `${DEV_PROXY_HOST}:${vitePort}`, + }, + }, + (proxyRes) => { + res.writeHead(proxyRes.statusCode, proxyRes.headers) + proxyRes.pipe(res, { end: true }) + } + ) + + proxyReq.on('error', (err) => { + console.error('[Controller Proxy] Error:', err.message) + if (!res.headersSent) { + res.writeHead(502) + res.end('Proxy error') + } + }) + + req.pipe(proxyReq, { end: true }) + }) + + // Gestisce l'upgrade WebSocket anche sulla porta del controller + controllerServer.on('upgrade', (request, socket, head) => { + const pathname = new URL(request.url, `http://${request.headers.host}`).pathname + + if (pathname === '/ws') { + wss.handleUpgrade(request, socket, head, (ws) => { + wss.emit('connection', ws, request) + }) + } else { + // Per l'HMR di Vite, proxare l'upgrade WebSocket verso Vite + const proxyReq = httpRequest({ + hostname: DEV_PROXY_HOST, + port: vitePort, + path: request.url, + method: 'GET', + headers: request.headers, + }) + + proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => { + socket.write( + `HTTP/1.1 101 Switching Protocols\r\n` + + Object.entries(proxyRes.headers) + .map(([k, v]) => `${k}: ${v}`) + .join('\r\n') + + '\r\n\r\n' + ) + proxySocket.pipe(socket) + socket.pipe(proxySocket) + }) + + proxyReq.on('error', (err) => { + console.error('[Controller Proxy] WS upgrade error:', err.message) + socket.destroy() + }) + + proxyReq.end() + } + }) + + controllerServer.listen(CONTROLLER_PORT, '0.0.0.0', () => { + console.log(`[Controller] Dev server running on port ${CONTROLLER_PORT}`) + }) +} diff --git a/vite.config.js b/vite.config.js index 0dc0206..2e94043 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,12 +1,26 @@ import { defineConfig } from 'vite' +import { resolve, dirname } from 'path' +import { fileURLToPath } from 'url' import vue from '@vitejs/plugin-vue' import { VitePWA } from 'vite-plugin-pwa' +import websocketPlugin from './vite-plugin-websocket.js' -// https://vitejs.dev/config/ +const __dirname = dirname(fileURLToPath(import.meta.url)) + +// Configurazione principale di Vite export default defineConfig({ - base: process.env.NODE_ENV === 'production' ? '/segnap' : '/', + base: '/', + build: { + rollupOptions: { + input: { + main: resolve(__dirname, 'index.html'), + controller: resolve(__dirname, 'controller.html'), + }, + }, + }, plugins: [ vue(), + websocketPlugin(), VitePWA({ registerType: 'autoUpdate', manifest: { @@ -32,4 +46,8 @@ export default defineConfig({ } }) ], + server: { + host: '0.0.0.0', + port: 5173, + }, })